Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6b830ec
feat(py): semantic_layer over inline measures
jat255 Sep 1, 2026
ab279bb
fix: add suggestion line to duplicate measure names error
jat255 Sep 1, 2026
3c5b683
fix(py): nested list collection matches top-level first-definition-wins
jat255 Sep 2, 2026
3e7b949
style(py): restore blank-line spacing lost in the rebase's conflict r…
jat255 Sep 2, 2026
169c80c
feat(py): read measures from modules, files, and directories
jat255 Sep 2, 2026
f3d599d
fix(py): first-definition-wins for all source merges, clean sys.modul…
jat255 Sep 2, 2026
02a2191
fix(py): pop, don't del, sys.modules entry on measure-file import fai…
jat255 Sep 2, 2026
2da1374
feat(py): allow sibling-file imports for path-loaded measures, guard …
jat255 Sep 2, 2026
0aeb13f
fix(py): catch installed-but-unimported name collisions, serialize im…
jat255 Sep 2, 2026
14bd9dc
fix(py): make the import lock reentrant to avoid deadlock on nested s…
jat255 Sep 2, 2026
728d70f
fix(py): keep loaded measure files out of the commons namespace
jat255 Sep 2, 2026
19a89b8
fix(py): cache loaded measure modules by path and mtime instead of a …
jat255 Sep 3, 2026
5ac659a
fix(py): verify file identity on a cache hit, use nanosecond mtimes
jat255 Sep 3, 2026
57897c1
fix(py): invalidate the stale bytecode cache on a detected reload
jat255 Sep 3, 2026
a664efe
fix(py): invalidate the bytecode cache unconditionally, not only on a…
jat255 Sep 3, 2026
24c84b6
fix(py): review follow-ups for semantic_layer() collection
jat255 Sep 4, 2026
c66c0ad
fix(py): review follow-ups for measure collection
jat255 Sep 4, 2026
0f3eb2b
test(py): write the planted reexporter module through its __dict__
jat255 Sep 4, 2026
098b6a0
docs: list the full pkg-py check gate in AGENTS.md
jat255 Sep 4, 2026
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ This repository is a monorepo holding two implementations of commons: the R pack

If you worked in this repository before the two packages were split apart, `MIGRATING.md` covers what moved and what to change in your setup.

Work from the relevant package's directory, not the repository root: `pkg-r/` for R (`devtools::load_all()`, `R CMD check`) and `pkg-py/` for Python (`uv run pytest`, `uv run ruff check`). CI is scoped the same way.
Work from the relevant package's directory, not the repository root: `pkg-r/` for R (`devtools::load_all()`, `R CMD check`) and `pkg-py/` for Python (`uv run ruff check`, `uv run pyrefly check src tests`, `uv run pytest`). CI is scoped the same way. Run all of a package's checks before pushing; the pyrefly invocation needs its explicit `src tests` paths, because with none it consults the repo's git ignore files and a worktree checked out under an ignored directory silently type-checks nothing.

Neither package has been widely adopted or publicly released; changes can be made without a deprecation cycle (or even reference to the way that it used to work).

Expand Down
460 changes: 451 additions & 9 deletions pkg-py/src/commons/_measures.py

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pkg-py/tests/measure_sources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Two traps for the next person editing this directory: the top level is itself a fixture case, so adding any `.py` file here changes the expected measure list in `test_semantic_layer_reads_a_directory_without_recursing`; and the collision check scans every sibling file and subdirectory, so adding a top-level file or directory named after any importable module breaks every path-loading test at once.
11 changes: 11 additions & 0 deletions pkg-py/tests/measure_sources/aliased/aliased.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from commons._measures import measure


@measure(description="Count of orders.")
def aliased_measure() -> int:
return 1


# Re-exporting a measure under a second name in the same module is one
# measure, not a name collision.
also_known_as = aliased_measure
18 changes: 18 additions & 0 deletions pkg-py/tests/measure_sources/bare_record/total.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from pydantic import create_model

from commons._measures import Measure


def total() -> int:
return 1


# A measure need not be a decorated function; a bare record at module level
# is harvested too.
grand_total = Measure(
name="grand_total",
title="Grand total",
description="Total of everything.",
func=total,
params=create_model("grand_total"),
)
6 changes: 6 additions & 0 deletions pkg-py/tests/measure_sources/broken/broken_import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Raises at import time, to test that a failed load does not dirty sys.modules.

Lives in a subdirectory so a non-recursive directory scan never reaches it.
"""

raise RuntimeError("boom")
11 changes: 11 additions & 0 deletions pkg-py/tests/measure_sources/broken/self_removing_import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Deletes its own sys.modules entry, then raises.

Regression fixture for _load_module_from_path's cleanup: it must not turn
this into a KeyError and swallow the real import error.
"""

import sys

del sys.modules[__name__]

raise RuntimeError("boom")
5 changes: 5 additions & 0 deletions pkg-py/tests/measure_sources/collision_a/shared_lib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Shares a file name with collision_b/shared_lib.py on purpose."""


def value() -> int:
return 1
12 changes: 12 additions & 0 deletions pkg-py/tests/measure_sources/collision_a/uses_shared.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Imports shared_lib by its bare name, registering it in sys.modules under
that name for the rest of the process.
"""

from shared_lib import value # type: ignore[missing-import]

from commons._measures import measure


@measure(description="From directory a.")
def a_measure() -> int:
return value()
9 changes: 9 additions & 0 deletions pkg-py/tests/measure_sources/collision_b/shared_lib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Shares a file name with collision_a/shared_lib.py on purpose.

Loading anything from this directory must fail once collision_a's
shared_lib.py has already been imported under the bare name "shared_lib".
"""


def value() -> int:
return 2
6 changes: 6 additions & 0 deletions pkg-py/tests/measure_sources/dotted/email.mime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from commons._measures import measure


@measure(description="A measure in a file whose name carries a dot.")
def dotted_measure() -> int:
return 1
13 changes: 13 additions & 0 deletions pkg-py/tests/measure_sources/duplicate_helpers/a_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""First file, sorted before b_file.py in this directory."""

from commons._measures import measure


def helper() -> int:
"""A helper this file's measure calls."""
return 1


@measure(description="Measure a.")
def measure_a() -> int:
return helper()
17 changes: 17 additions & 0 deletions pkg-py/tests/measure_sources/duplicate_helpers/b_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Second file, sorted after a_file.py; defines a same-named helper.

Proves directory scanning keeps the first file's source for a colliding
helper name.
"""

from commons._measures import measure


def helper() -> int:
"""A colliding helper name; this definition must lose to a_file's."""
return 2


@measure(description="Measure b.")
def measure_b() -> int:
return helper()
6 changes: 6 additions & 0 deletions pkg-py/tests/measure_sources/duplicate_measures/a_measure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from commons._measures import measure


@measure(description="First of two distinct measures named dup.", name="dup")
def dup_from_a() -> int:
return 1
6 changes: 6 additions & 0 deletions pkg-py/tests/measure_sources/duplicate_measures/b_measure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from commons._measures import measure


@measure(description="Second of two distinct measures named dup.", name="dup")
def dup_from_b() -> int:
return 2
4 changes: 4 additions & 0 deletions pkg-py/tests/measure_sources/has_init/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
raise RuntimeError(
"__init__.py in a measure directory must never be imported; "
"a directory of measure files is not a package."
)
6 changes: 6 additions & 0 deletions pkg-py/tests/measure_sources/has_init/orders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from commons._measures import measure


@measure(description="Count of orders.")
def has_init_measure() -> int:
return 1
12 changes: 12 additions & 0 deletions pkg-py/tests/measure_sources/nested/orders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Shares a file name with the parent directory's orders.py on purpose.

Directory loading must not reach it, and loading it explicitly must not
collide with the other orders.py in sys.modules.
"""

from commons._measures import measure


@measure(description="Count of nested orders.")
def nested_order_count() -> int:
return 1
20 changes: 20 additions & 0 deletions pkg-py/tests/measure_sources/orders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Measures loaded from a path by the test suite."""

from typing import Annotated, Any

from pydantic import Field

from commons._measures import Injected, measure


def double(x: int) -> int:
"""A helper the measure calls. Not a measure itself."""
return x * 2


@measure(description="Count of orders.")
def order_count(
region: Annotated[str, Field(description="The sales region.")],
warehouse: Injected[Any],
) -> int:
return double(1)
2 changes: 2 additions & 0 deletions pkg-py/tests/measure_sources/pkg_collision/json/data.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Stand-in so this otherwise-empty directory is tracked: json/ is importable
as a namespace package once its parent directory is on sys.path.
6 changes: 6 additions & 0 deletions pkg-py/tests/measure_sources/pkg_collision/orders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from commons._measures import measure


@measure(description="Count of orders.")
def pkg_collision_measure() -> int:
return 1
17 changes: 17 additions & 0 deletions pkg-py/tests/measure_sources/reentrant/composes_a_sibling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Calls semantic_layer() on another path during its own import.

A non-reentrant lock around the import machinery would deadlock here: this
module's own load already holds _IMPORT_LOCK when the line below tries to
acquire it again on the same thread.
"""

from pathlib import Path

from commons._measures import measure, semantic_layer

NESTED_LAYER = semantic_layer(Path(__file__).parent.parent / "nested" / "orders.py")


@measure(description="Outer measure.")
def outer_measure() -> int:
return 1
8 changes: 8 additions & 0 deletions pkg-py/tests/measure_sources/revenue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""A second file in the same directory, to prove directory loading."""

from commons._measures import measure


@measure(description="Total revenue.")
def total_revenue() -> int:
return 100
6 changes: 6 additions & 0 deletions pkg-py/tests/measure_sources/sibling_imports/helper_lib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""A helper file a sibling measure file imports directly."""


def double(x: int) -> int:
"""Doubles a value; imported by a sibling file, not a measure itself."""
return x * 2
12 changes: 12 additions & 0 deletions pkg-py/tests/measure_sources/sibling_imports/uses_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Imports a sibling file by plain absolute import, the way an ordinary
Python module does.
"""

from helper_lib import double # type: ignore[missing-import]

from commons._measures import measure


@measure(description="Doubled count.")
def doubled_count() -> int:
return double(21)
10 changes: 10 additions & 0 deletions pkg-py/tests/measure_sources/stdlib_collision/json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Named after a standard library module on purpose: loading this must fail
before the directory ever goes on sys.path.
"""

from commons._measures import measure


@measure(description="Should never load.")
def unreachable_measure() -> int:
return 1
Loading
Loading