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
3 changes: 3 additions & 0 deletions .github/workflows/rw-python-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ jobs:
- name: pep8 and formatting check
run: |
make format
- name: lint check
run: |
make lint
docs-scripts-tests:
runs-on: ubuntu-latest
if: ${{inputs.changed-python-modules == 'true'}}
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ repos:
args: [ '--maxkb=890' ]
- id: check-case-conflict
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.1
rev: v0.15.20
hooks:
# Run the linter.
- id: ruff
Expand Down
33 changes: 26 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# (C) 2021 GoodData Corporation
ARG PY_TAG
FROM ghcr.io/astral-sh/uv:0.12 AS uv
FROM ghcr.io/astral-sh/uv:0.12.5 AS uv
FROM python:${PY_TAG}

ARG PY_TAG
Expand Down Expand Up @@ -40,16 +40,35 @@ WORKDIR /data
COPY pyproject.toml uv.lock ./

# Install tox and tox-uv as system packages so they're available globally.
# NOTE: `uv pip install --group` reads the group's requirements from pyproject.toml but
# resolves them FRESH from the index -- it does NOT read uv.lock. Every version that must
# stay fixed therefore needs an explicit bound in the group itself; in particular `uv`,
# whose console script installs over the binary copied above.
# Via `uv export` and not `uv pip install --group`: the latter re-resolves fresh from the
# index, while export reads uv.lock, so the image gets exactly the pinned versions.
# The group uses tox-uv-bare, so nothing here installs a `uv` console script over the
# binary COPYed above -- that COPY is the image's only uv, hence its exact pin.
# Clean up dependency files after installation to reduce image size
RUN set -x \
&& uv pip install --system --group tox \
&& rm -f pyproject.toml uv.lock \
&& uv export --frozen --only-group tox -o /tmp/tox-requirements.txt \
&& uv pip install --system -r /tmp/tox-requirements.txt \
&& rm -f pyproject.toml uv.lock /tmp/tox-requirements.txt \
&& true

# Any uv command here must not REWRITE the bind-mounted host uv.lock if it thinks it is
# stale -- fail instead. Not UV_FROZEN: tox-uv reads that and downgrades its own --locked
# to --frozen, silently accepting a stale lock. Must be set AFTER the export above, which
# is rejected in combination with UV_LOCKED and has to stay --frozen because only the root
# pyproject.toml and uv.lock exist at that layer for --locked to validate against.
ENV UV_LOCKED=1

# Use the lock-pinned tox installed system-wide above rather than project_common.mk's
# default `uv run tox`, which would first sync the whole workspace into a throwaway
# in-container project env (measured: 58 packages, ~7s) just to obtain the same tox.
ENV TOX=tox

# The repo is bind-mounted at /data, so the default project environment (/data/.venv) is
# the developer's host venv; a `uv run` here would rebuild it against this image's Linux
# interpreter. Redirect it somewhere container-local (/tmp, not a home dir: the runtime
# user is created by entrypoint.sh, so no home exists when this ENV is evaluated).
ENV UV_PROJECT_ENVIRONMENT=/tmp/uv-project-venv

COPY .docker/entrypoint.sh /entrypoint.sh

LABEL image_name="GoodData Python SDK test image with python, tox and make"
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,13 @@ test-staging:
clean-staging:
@test -n "$(STAGING_ADMIN_TOKEN)" || (echo "ERROR: STAGING_ADMIN_TOKEN is required. Set it in .env or pass on CLI." && exit 1)
@test -n "$(STAGING_DS_PASSWORD)" || (echo "ERROR: STAGING_DS_PASSWORD is required. Set it in .env or pass on CLI." && exit 1)
cd packages/tests-support && STAGING=1 TOKEN="$(STAGING_ADMIN_TOKEN)" DS_PASSWORD="$(STAGING_DS_PASSWORD)" python clean_staging.py
cd packages/tests-support && STAGING=1 TOKEN="$(STAGING_ADMIN_TOKEN)" DS_PASSWORD="$(STAGING_DS_PASSWORD)" uv run --locked python clean_staging.py

.PHONY: load-staging
load-staging:
@test -n "$(STAGING_ADMIN_TOKEN)" || (echo "ERROR: STAGING_ADMIN_TOKEN is required. Set it in .env or pass on CLI." && exit 1)
@test -n "$(STAGING_DS_PASSWORD)" || (echo "ERROR: STAGING_DS_PASSWORD is required. Set it in .env or pass on CLI." && exit 1)
cd packages/tests-support && STAGING=1 TOKEN="$(STAGING_ADMIN_TOKEN)" DS_PASSWORD="$(STAGING_DS_PASSWORD)" python upload_demo_layout.py
cd packages/tests-support && STAGING=1 TOKEN="$(STAGING_ADMIN_TOKEN)" DS_PASSWORD="$(STAGING_DS_PASSWORD)" uv run --locked python upload_demo_layout.py

.PHONY: release
release:
Expand Down
1 change: 0 additions & 1 deletion packages/gooddata-dbt/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14}
[testenv]
runner = uv-venv-lock-runner
package = wheel
wheel_build_env = .pkg
dependency_groups =
test
setenv =
Expand Down
8 changes: 8 additions & 0 deletions packages/gooddata-eval/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# (C) 2026 GoodData Corporation

# Unit test / coverage reports
.tox/
.coverage
.coverage.*
coverage.xml
.json-report-*.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,11 @@ def _is_retryable_exc(exc: Exception) -> bool:
return True
if isinstance(exc, httpx.HTTPStatusError):
return exc.response.status_code in _RETRYABLE_STATUS_CODES
if isinstance(exc, httpx.RemoteProtocolError):
# Mid-stream disconnect ("peer closed connection without sending complete
# message body") -- pure network flake, not a real agent/content failure.
# Confirmed live: contaminated ~1-4% of visualization runs with a hard
# fail and zero retry attempts.
return True
return False
# Mid-stream disconnect ("peer closed connection without sending complete
# message body") -- pure network flake, not a real agent/content failure.
# Confirmed live: contaminated ~1-4% of visualization runs with a hard
# fail and zero retry attempts.
return isinstance(exc, httpx.RemoteProtocolError)


def _retry_transient(operation: Callable[[], T], *, is_retryable: Callable[[Exception], bool]) -> T:
Expand Down
6 changes: 1 addition & 5 deletions packages/gooddata-eval/tests/test_agentic_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from unittest.mock import patch

import pytest
from gooddata_eval.cli.agentic_runner import _dispatch_agentic, run_agentic_items
from gooddata_eval.cli.agentic_runner import AGENTIC_TEST_KINDS, _dispatch_agentic, run_agentic_items
from gooddata_eval.core.agentic.alert_skill import AlertSkillAssertionError
from gooddata_eval.core.models import AgenticEvalOutcome, DatasetItem

Expand Down Expand Up @@ -81,8 +81,6 @@ def test_all_agentic_kind_cases_covers_every_registered_kind():
"""Guards the two parametrized tests below against silently going stale: a kind added
to AGENTIC_TEST_KINDS without a matching case here would otherwise just not get tested,
not fail loudly."""
from gooddata_eval.cli.agentic_runner import AGENTIC_TEST_KINDS

covered = {kind for kind, _, _ in _ALL_AGENTIC_KIND_CASES}
assert covered == set(AGENTIC_TEST_KINDS)

Expand Down Expand Up @@ -194,8 +192,6 @@ def test_dispatch_agentic_returns_a_real_outcome_for_every_kind(kind, expected_o
evaluator produced -- not None, not the outcome's reasoning_steps list alone, not any
other bare value the old `isinstance(outcome, tuple)`/`isinstance(outcome, AgenticEvalOutcome)`
fallback could silently swallow."""
from gooddata_eval.core.models import AgenticEvalOutcome

item = DatasetItem(
id="q1",
dataset_name="ds",
Expand Down
1 change: 0 additions & 1 deletion packages/gooddata-eval/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14}
[testenv]
runner = uv-venv-lock-runner
package = wheel
wheel_build_env = .pkg
extras =
llm-judge
dependency_groups =
Expand Down
1 change: 0 additions & 1 deletion packages/gooddata-fdw/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14}
[testenv]
runner = uv-venv-lock-runner
package = wheel
wheel_build_env = .pkg
dependency_groups =
test
setenv =
Expand Down
1 change: 0 additions & 1 deletion packages/gooddata-flexconnect/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14}
[testenv]
runner = uv-venv-lock-runner
package = wheel
wheel_build_env = .pkg
dependency_groups =
test
setenv =
Expand Down
1 change: 0 additions & 1 deletion packages/gooddata-flight-server/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14}
[testenv]
runner = uv-venv-lock-runner
package = wheel
wheel_build_env = .pkg
dependency_groups =
test
setenv =
Expand Down
1 change: 0 additions & 1 deletion packages/gooddata-pandas/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14}
[testenv]
runner = uv-venv-lock-runner
package = wheel
wheel_build_env = .pkg
dependency_groups =
test
setenv =
Expand Down
8 changes: 4 additions & 4 deletions packages/gooddata-pipelines/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ line-length = 80

[dependency-groups]
test = [
"pytest (>=8.3.5,<9.0.0)",
"pytest~=8.3.4",
"pytest-cov~=6.0.0",
"pytest-json-report==1.5.0",
"pytest-mock (>=3.14.0,<4.0.0)",
"moto (>=5.1.6,<6.0.0)",
"orjson (>=3.11.3,<4.0.0)",
"pytest-mock>=3.14.0,<4.0.0",
"moto>=5.1.6,<6.0.0",
"orjson>=3.11.3,<4.0.0",
]

[tool.ty.analysis]
Expand Down
1 change: 0 additions & 1 deletion packages/gooddata-pipelines/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14}
[testenv]
runner = uv-venv-lock-runner
package = wheel
wheel_build_env = .pkg
dependency_groups =
test
setenv =
Expand Down
1 change: 0 additions & 1 deletion packages/gooddata-sdk/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ envlist = py3{10,11,12,13,14}
[testenv]
runner = uv-venv-lock-runner
package = wheel
wheel_build_env = .pkg
dependency_groups =
test
pass_env =
Expand Down
8 changes: 6 additions & 2 deletions project_common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ ROOT_DIR = ../..
RUFF = ./.venv/bin/ruff
# ty needs uv run (unlike ruff) because it resolves imports from installed packages
TY = uv run ty
# `uv run` so a host dev gets tox+tox-uv from the lock without activating the venv. The
# test image overrides this to bare `tox` (it installs the same lock-pinned versions
# system-wide), which skips an entire redundant project sync inside every container.
TOX ?= uv run tox
PKG_PATH = packages/$(CURR_DIR_BASE_NAME)

TOX_FLAGS =
Expand Down Expand Up @@ -59,7 +63,7 @@ types: type-check

.PHONY: test
test:
uv run tox -v $(TOX_FLAGS) $(LOCAL_TEST_ENVS) $(LOCAL_ADD_ARGS)
$(TOX) -v $(TOX_FLAGS) $(LOCAL_TEST_ENVS) $(LOCAL_ADD_ARGS)

.PHONY: test-ci
test-ci:
Expand All @@ -69,7 +73,7 @@ test-ci:
.PHONY: test-staging
test-staging:
@test -n "$(TOKEN)" || (echo "ERROR: TOKEN is required." && exit 1)
TOKEN=$(TOKEN) DS_PASSWORD=$(DS_PASSWORD) GD_TEST_ENV=staging uv run tox -v $(TOX_FLAGS) $(LOCAL_TEST_ENVS) $(LOCAL_ADD_ARGS)
TOKEN=$(TOKEN) DS_PASSWORD=$(DS_PASSWORD) GD_TEST_ENV=staging $(TOX) -v $(TOX_FLAGS) $(LOCAL_TEST_ENVS) $(LOCAL_ADD_ARGS)

# this is effective for gooddata-sdk only now - it should be part of test fixtures
# remove this target once implemented in pytest global fixture
Expand Down
65 changes: 41 additions & 24 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ dependencies = [

[tool.uv]
required-version = "~=0.12.0"
# The root only aggregates the members; it is never built or published.
package = false
# So a bare `uv sync` can run every make target (ruff, ty, pytest, tox). Not "all":
# `release` would then be installed by every CI `uv sync --group ...` too.
default-groups = ["dev", "lint", "type", "test", "tox"]

[tool.uv.sources]
gooddata-sdk = { workspace = true }
Expand All @@ -34,7 +39,20 @@ gooddata-api-client = { path = "gooddata-api-client", editable = true }

[tool.uv.workspace]
# note gooddata-api-client is not a workspace package, it is a dependency of other packages
members = ["packages/*"]
# Listed explicitly, not as a "packages/*" glob: any stray directory under packages/ would
# match and, lacking a pyproject.toml, break every uv command in the repo. A new package
# needs three entries -- here, [project].dependencies above, and [tool.uv.sources].
members = [
"packages/gooddata-dbt",
"packages/gooddata-eval",
"packages/gooddata-fdw",
"packages/gooddata-flexconnect",
"packages/gooddata-flight-server",
"packages/gooddata-pandas",
"packages/gooddata-pipelines",
"packages/gooddata-sdk",
"packages/tests-support",
]

[dependency-groups]
dev = [
Expand All @@ -48,41 +66,40 @@ type = [
"ty~=0.0.55",
]
test = [
# Common test dependencies used across all workspace packages
# Declared only here: consumed by the scripts/docs/ tests (`make test-docs-scripts`).
"pytest~=8.3.4",
# Required by scripts/docs/ tests
"toml~=0.10.2",
"griffe>=1.0",
"docstring_parser~=0.15",
"jinja2~=3.1",
"pytest-cov~=6.0.0",
"pytest-json-report==1.5.0",
# Additional test dependencies used by multiple packages
"pytest-snapshot==0.9.0",
"pytest-order~=1.3.0",
"vcrpy~=8.2.1",
"urllib3~=2.6.0",
"python-dotenv~=1.0.0",
"deepdiff~=8.5.0",
"pytest-mock>=3.14.0",
# Package-specific dependencies (only needed by some packages)
# but included here for convenience when working on the full workspace
"moto>=5.1.6",
"orjson>=3.11.3",
# Mirror of the members' `test` groups so the root venv can run any package's tests
# (`uv run pytest -k ...`, see scripts/validate_python.sh). Add to it when a member
# adds a test dependency, or that member's tests stop collecting from the root venv.
# Unbounded on purpose: the authoritative bounds live in each member's own group, and
# the single workspace lock resolves one version for everything anyway.
"pytest-cov",
"jsonschema",
"pytest-json-report",
"pytest-snapshot",
"pytest-order",
"pytest-mock",
"vcrpy",
"urllib3",
"python-dotenv",
"deepdiff",
"moto",
"orjson",
]
release = [
"tbump~=6.11.0",
"tomlkit>=0.11"
]
tox = [
"tox~=4.56.1",
"tox-uv~=1.35.2",
# tox-uv depends on the uv PyPI package without a version bound, and the Dockerfile
# installs this group with `uv pip install`, which resolves fresh instead of reading
# uv.lock. Without this bound the resolver picks the newest uv, whose console script
# then shadows the pinned binary in the image and trips required-version at runtime.
# Keep in sync with [tool.uv] required-version above.
"uv~=0.12.0",
# tox-uv-bare carries the actual plugin; the `tox-uv` wrapper only adds a dependency on
# the 58MB `uv` PyPI package, which the image already has as a binary and the host has
# on PATH. Requires `uv` to be on PATH, which holds in both places.
"tox-uv-bare~=1.35.2",
]

[tool.ruff]
Expand Down
Loading
Loading