Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: "3.14"

- name: Install coverage tool
run: pip install coverage
Expand Down Expand Up @@ -1364,7 +1364,9 @@ jobs:
working-directory: saas
run: |
echo "🔄 Updating baserow submodule to ${{ github.sha }}..."
git submodule update --init
git config --global url."https://github.com/".insteadOf "git@github.com:"
git submodule sync --recursive
git submodule update --init --recursive
cd baserow
git fetch origin
git checkout ${{ github.sha }}
Expand Down
43 changes: 27 additions & 16 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ ARG GID="9999"
# Builder for su-exec (replaces gosu, safer with smaller footprint)
# Pinned to v0.2 (commit 212b75144bbc) with SHA256 verification
# =============================================================================
FROM debian:bookworm-slim AS tool-builder
FROM debian:trixie-slim AS tool-builder

SHELL ["/bin/bash", "-o", "pipefail", "-c"]

Expand All @@ -28,7 +28,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
# =============================================================================
# Production base builder stage: builds runtime dependencies only
# =============================================================================
FROM python:3.11.14-slim-bookworm AS builder-prod-base
FROM python:3.14.3-slim-trixie AS builder-prod-base
ARG UID
ARG GID

Expand All @@ -38,7 +38,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
apt-get update && \
apt-get upgrade -y && \
apt-get install -y --no-install-recommends \
build-essential
build-essential \
libpq-dev

ENV UV_PYTHON_DOWNLOADS=0 \
UV_COMPILE_BYTECODE=1 \
Expand Down Expand Up @@ -158,22 +159,22 @@ COPY --chown=$UID:$GID enterprise/backend /baserow/enterprise/backend
RUN dos2unix /baserow/backend/docker/docker-entrypoint.sh \
&& chmod a+x /baserow/backend/docker/docker-entrypoint.sh

# Install the project with premium and enterprise workspaces, removing tests and unneeded files
# Install the project with premium and enterprise workspaces
RUN --mount=type=cache,target=$UV_CACHE_DIR,sharing=locked,uid=$UID,gid=$GID \
uv sync --frozen --no-dev \
&& find /baserow/venv -type f \( -name "*.c" -o -name "*.h" \) -not -path "*/autobahn/*" -delete \
# Strip test/doc directories from site-packages (keep django/drf tests - used at runtime)
# Keep botocore/docs and boto3/docs - they're imported at runtime
&& (find /baserow/ -type d \( -name "test" -o -name "tests" -o -name "email_compiler" \) \
&& (find /baserow/venv/lib -type d \( -name "test" -o -name "tests" \) \
-not -path "*/django/*" -not -path "*/rest_framework/*" -prune -exec rm -rf {} \; || true) \
&& (find /baserow/ -type d \( -name "doc" -o -name "docs" -o -name "example" -o -name "examples" \) \
&& (find /baserow/venv/lib -type d \( -name "doc" -o -name "docs" -o -name "example" -o -name "examples" \) \
-not -path "*/botocore/*" -not -path "*/boto3/*" -prune -exec rm -rf {} \; || true)


# =============================================================================
# CI Target - lightweight image for pytest and E2E tests
# =============================================================================
FROM python:3.11.14-slim-bookworm AS ci
FROM python:3.14.3-slim-trixie AS ci
ARG UID
ARG GID

Expand Down Expand Up @@ -221,7 +222,7 @@ ENTRYPOINT ["/usr/bin/tini", "--", "/bin/bash", "/baserow/backend/docker/docker-
# Only works mounting the source code as a bind mount. See docker-compose.dev.yml for usage.
# =============================================================================

FROM python:3.11.14-slim-bookworm AS dev
FROM python:3.14.3-slim-trixie AS dev
ARG UID="9999"
ARG GID="9999"

Expand All @@ -232,9 +233,13 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
apt-get upgrade -y && \
apt-get install -y --no-install-recommends \
apt-get install -y --no-install-recommends curl ca-certificates gnupg \
&& curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --batch --dearmor -o /usr/share/keyrings/pgdg.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] https://apt.postgresql.org/pub/repos/apt trixie-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
python3.11-dev \
python3-dev \
ca-certificates \
postgresql-client-15 \
gettext \
Expand Down Expand Up @@ -316,7 +321,7 @@ CMD ["django-dev"]
# =============================================================================
# Production target
# =============================================================================
FROM python:3.11.14-slim-bookworm AS local
FROM python:3.14.3-slim-trixie AS local
ARG UID
ARG GID

Expand All @@ -334,12 +339,14 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
apt-get upgrade -y && \
apt-get install -y --no-install-recommends \
ca-certificates \
curl \
postgresql-client-${POSTGRES_VERSION} \
apt-get install -y --no-install-recommends curl ca-certificates gnupg \
&& curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --batch --dearmor -o /usr/share/keyrings/pgdg.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] https://apt.postgresql.org/pub/repos/apt trixie-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
xmlsec1 \
gettext
gettext \
postgresql-client-${POSTGRES_VERSION}

RUN groupadd --system --gid $GID ${DOCKER_USER} && \
useradd --shell /bin/bash -l -u $UID -g $GID -o -c "" -d /baserow -m ${DOCKER_USER}
Expand All @@ -360,6 +367,10 @@ COPY --chown=$UID:$GID --from=builder-prod /baserow/premium /baserow/premium/
COPY --chown=$UID:$GID --from=builder-prod /baserow/enterprise /baserow/enterprise/
COPY --chown=$UID:$GID ./docs /baserow/docs/

# Remove application tests and dev-only directories
RUN rm -rf /baserow/backend/tests /baserow/premium/backend/tests \
/baserow/enterprise/backend/tests /baserow/backend/email_compiler

COPY --chown=$UID:$GID deploy/plugins/*.sh /baserow/plugins/

USER $UID:$GID
Expand Down
4 changes: 2 additions & 2 deletions backend/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ alias dev := run-dev-server
alias serve := run-dev-server

# Variables
python_version := "3.11"
python_version := "3.14"

# Venv location: use UV_PROJECT_ENVIRONMENT if set (e.g., in Docker), otherwise default to project root
venv_dir := env("UV_PROJECT_ENVIRONMENT", justfile_directory() / "../.venv")
Expand Down Expand Up @@ -137,7 +137,7 @@ activate:
@echo ""
@echo " source {{ venv_dir }}/bin/activate"

# Create Python 3.11 virtual environment with uv
# Create Python virtual environment with uv
_create-venv:
#!/usr/bin/env bash
set -euo pipefail
Expand Down
2 changes: 1 addition & 1 deletion backend/mypy.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# mypy.ini

[mypy]
python_version = 3.11
python_version = 3.14
exclude = "[a-zA-Z_]+.migrations.|[a-zA-Z_]+.tests.|[a-zA-Z_]+.testing."
allow_redefinition = false
mypy_path=backend/src:premium/backend/src:enterprise/backend/src
Expand Down
6 changes: 3 additions & 3 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description = """Baserow is an open source no-code database tool and Airtable \
technical expertise. Build a table and define custom fields \
like text, number, file and many more."""
license = { file = "../LICENSE" }
requires-python = "==3.11.*"
requires-python = "==3.14.*"
dynamic = ["version"]
classifiers = []

Expand Down Expand Up @@ -81,10 +81,10 @@ dependencies = [
"django-cachalot==2.8.0",
"celery-singleton==0.3.1",
"posthog==7.4.2",
"prosemirror @ https://github.com/fellowapp/prosemirror-py/archive/refs/tags/v0.3.5.zip",
"prosemirror==0.5.2",
"rich==14.3.2",
"tzdata==2025.3",
"sentry-sdk==2.48.0",
"sentry-sdk==2.52.0",
"typing_extensions>=4.14.1",
"ollama==0.6.1",
"langchain==0.3.27",
Expand Down
8 changes: 4 additions & 4 deletions backend/src/advocate/test_advocate.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ def test_advocate_requests_api_wrapper(self):
local_wrapper.get("http://127.0.0.1:1/")
# Check that we got a connection exception instead of a validation one
# This might be either exception depending on the requests version
self.assertRegexpMatches(
self.assertRegex(
cm.exception.__class__.__name__,
r"\A(Connection|Protocol)Error",
)
Expand All @@ -518,7 +518,7 @@ def test_wrapper_session_pickle(self):

with self.assertRaises(Exception) as cm:
sess_instance.get("http://127.0.0.1:1/")
self.assertRegexpMatches(
self.assertRegex(
cm.exception.__class__.__name__,
r"\A(Connection|Protocol)Error",
)
Expand All @@ -534,7 +534,7 @@ def _check_instance(instance):

with self.assertRaises(Exception) as cm:
instance.get("http://127.0.0.1:1/")
self.assertRegexpMatches(
self.assertRegex(
cm.exception.__class__.__name__,
r"\A(Connection|Protocol)Error",
)
Expand Down Expand Up @@ -671,7 +671,7 @@ def test_advocate_wrapper_futures(self):
sess.get("http://127.0.0.1:1/").result()
# Check that we got a connection exception instead of a validation one
# This might be either exception depending on the requests version
self.assertRegexpMatches(
self.assertRegex(
cm.exception.__class__.__name__,
r"\A(Connection|Protocol)Error",
)
Expand Down
12 changes: 12 additions & 0 deletions backend/src/baserow/config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1300,9 +1300,21 @@ def __setitem__(self, key, value):

if SENTRY_DSN:
import sentry_sdk
import sentry_sdk.integrations as _sentry_integrations
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.scrubber import DEFAULT_DENYLIST, EventScrubber

# Exclude the langchain integration from auto-discovery: its module-level
# imports are incompatible with Python 3.14 (langchain/pydantic type
# evaluation crash), and the import happens before disabled_integrations
# can take effect.

_sentry_integrations._AUTO_ENABLING_INTEGRATIONS[:] = [
entry
for entry in _sentry_integrations._AUTO_ENABLING_INTEGRATIONS
if "langchain" not in entry
]

SENTRY_DENYLIST = DEFAULT_DENYLIST + ["username", "email", "name"]

sentry_sdk.init(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Generated from BaserowFormulaLexer.g4 by ANTLR 4.9
from antlr4 import *
from io import StringIO
from typing.io import TextIO
from typing import TextIO
import sys


Expand Down
2 changes: 1 addition & 1 deletion backend/src/baserow/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ def remove_invalid_surrogate_characters(
) -> str:
"""
Removes illegal unicode characters from the provided content. If you for example
run something like `b"\ud83d".encode("utf-8")`, it will result in a
run something like `b"\\ud83d".encode("utf-8")`, it will result in a
UnicodeEncodeError. This function removed the illegal characters, it keeps the
valid emoji's.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1933,7 +1933,7 @@ def test_runtime_contains_execute(args, expected):
if expected is None:
with pytest.raises(TypeError) as e:
RuntimeContains().execute({}, parsed_args)
assert "is not iterable" in str(e)
assert "iterable" in str(e)
else:
result = RuntimeContains().execute({}, parsed_args)
assert result == expected
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_notification_creation_on_creating_group_invitation(
response_json = response.json()
invitation_id = response_json["id"]

assert mocked_notification_created.called_once()
mocked_notification_created.assert_called_once()
args = mocked_notification_created.call_args
assert args == call(
sender=NotificationHandler,
Expand Down Expand Up @@ -119,7 +119,7 @@ def test_notification_creation_on_accepting_group_invitation(

assert response.status_code == HTTP_200_OK

assert mocked_notification_created.called_once()
mocked_notification_created.assert_called_once()
args = mocked_notification_created.call_args
assert args == call(
sender=NotificationHandler,
Expand Down Expand Up @@ -192,7 +192,7 @@ def test_notification_creation_on_rejecting_group_invitation(

assert response.status_code == HTTP_204_NO_CONTENT

assert mocked_notification_created.called_once()
mocked_notification_created.assert_called_once()
args = mocked_notification_created.call_args
assert args == call(
sender=NotificationHandler,
Expand Down Expand Up @@ -254,7 +254,7 @@ def test_baserow_version_upgrade_is_sent_as_broadcast_notification(
"1.19", "/blog/release-notes/1.19"
)

assert mocked_notification_created.called_once()
mocked_notification_created.assert_called_once()
args = mocked_notification_created.call_args
notification = NotificationHandler.get_notification_by(user_1, broadcast=True)
assert args == call(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ def test_all_users_can_see_and_clear_broadcast_notifications(
assert NotificationRecipient.objects.count() == 0


@pytest.mark.django_db
@pytest.mark.django_db(transaction=True)
@patch("baserow.core.notifications.tasks.send_queued_notifications_to_users.delay")
def test_queued_notifications_are_not_visible_to_the_users(
mocked_send_queued_notifications_to_users,
Expand Down Expand Up @@ -290,7 +290,7 @@ def test_queued_notifications_are_not_visible_to_the_users(
assert user_notifications.count() == 0
assert other_user_notifications.count() == 0

assert mocked_send_queued_notifications_to_users.called_once()
mocked_send_queued_notifications_to_users.assert_called_once()

qs = NotificationRecipient.objects.filter(queued=True)
assert qs.filter(recipient=user).count() == 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ def test_daily_report_is_sent_up_to_max_limit_per_task_and_log_the_error(
assert len(weekly_summary_emails) == 1
assert weekly_summary_emails[0].to in [[user_3.email], [user_4.email]]

mock_logger_error.called_once_with(
mock_logger_error.assert_called_once_with(
"The maximum number of email of notifications was reached.\n"
"Daily sent: 1.\n"
"Daily remaining: 1.\n"
Expand Down Expand Up @@ -409,7 +409,7 @@ def test_daily_report_is_sent_up_to_max_limit_per_task_log_the_error_and_retry_a
assert len(weekly_summary_emails) == 1
assert weekly_summary_emails[0].to in [[user_3.email], [user_4.email]]

mock_logger_error.called_once_with(
mock_logger_error.assert_called_once_with(
"The maximum number of email of notifications was reached.\n"
"Daily sent: 1.\n"
"Daily remaining: 1.\n"
Expand Down
Loading
Loading