Skip to content
Open
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
Binary file removed .coverage
Binary file not shown.
18 changes: 2 additions & 16 deletions .github/actions/release-to-pypi-uv/tests/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,15 @@
from __future__ import annotations

import importlib.util
import os
import sys
import typing as typ
from pathlib import Path

if typ.TYPE_CHECKING: # pragma: no cover - imported for annotations only
from types import ModuleType

if _ACTION_PATH := os.environ.get("GITHUB_ACTION_PATH"):
_action_root = Path(_ACTION_PATH).resolve()
scripts_candidate = _action_root / "scripts"
if scripts_candidate.is_dir():
SCRIPTS_DIR = scripts_candidate
try:
REPO_ROOT = _action_root.parents[2]
except IndexError:
REPO_ROOT = scripts_candidate.parents[3]
else:
SCRIPTS_DIR = Path(__file__).resolve().parents[1] / "scripts"
REPO_ROOT = SCRIPTS_DIR.parents[3]
else:
SCRIPTS_DIR = Path(__file__).resolve().parents[1] / "scripts"
REPO_ROOT = SCRIPTS_DIR.parents[3]
SCRIPTS_DIR = Path(__file__).resolve().parents[1] / "scripts"
REPO_ROOT = SCRIPTS_DIR.parents[3]


def load_script_module(name: str) -> ModuleType:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ __pycache__/
venv/
.env.*
.pytest_cache/
.coverage
.mypy_cache/
*.egg-info/

Expand Down
1 change: 1 addition & 0 deletions .rules/python-00.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ def login_user(username: str, password: str) -> bool:
def test_login_success():
assert login_user("alice", "correct-password") is True


def test_login_failure():
assert not login_user("alice", "wrong-password")
```
Expand Down
3 changes: 3 additions & 0 deletions .rules/python-context-managers.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Use this for straightforward procedural setup/teardown:
```python
from contextlib import contextmanager


@contextmanager
def managed_file(path: str, mode: str):
f = open(path, mode)
Expand All @@ -31,6 +32,7 @@ def managed_file(path: str, mode: str):
finally:
f.close()


# Usage:
with managed_file("/tmp/data.txt", "w") as f:
f.write("hello")
Expand All @@ -53,6 +55,7 @@ class Resource:
def __exit__(self, exc_type, exc_val, exc_tb):
self.conn.close()


# Usage:
with Resource() as conn:
conn.send("ping")
Expand Down
13 changes: 8 additions & 5 deletions .rules/python-exception-design-raising-handling-and-logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class enables callers to catch all domain failures without vendor leakage.
class PaymentsError(Exception):
"""All payment-layer errors."""


class CardDeclinedError(PaymentsError): # ✅ ends with Error (N818)
def __init__(self, code: str, *, retry_after: int | None = None):
super().__init__(f"Card declined ({code})")
Expand Down Expand Up @@ -114,13 +115,14 @@ clarifies intent.

```python
import logging

logger = logging.getLogger(__name__)

# ❌ LOG issues
logging.warning(f"failed for {user_id}") # f-string (LOG004/LOG014)
logging.warning("failed for %s" % user_id) # %-formatting (LOG007)
logging.warn("deprecated") # warn() (LOG009)
logging.error("bad root logger") # root logger usage (LOG015)
logging.warning(f"failed for {user_id}") # f-string (LOG004/LOG014)
logging.warning("failed for %s" % user_id) # %-formatting (LOG007)
logging.warn("deprecated") # warn() (LOG009)
logging.error("bad root logger") # root logger usage (LOG015)

# ✅ Correct
logger.warning("Failed for user_id=%s", user_id) # lazy interpolation
Expand Down Expand Up @@ -203,7 +205,7 @@ def charge(amount_pennies: int, card_token: str) -> str:
try:
return gateway.charge(amount_pennies, card_token)
except gateway.Timeout as exc:
raise PaymentsError("Gateway timeout") from exc # ✅ TRY201
raise PaymentsError("Gateway timeout") from exc # ✅ TRY201
except gateway.CardDeclined as exc:
raise CardDeclinedError(exc.code, retry_after=60) from exc
```
Expand All @@ -227,6 +229,7 @@ def must_have_key(d: dict, key: str) -> None:
msg = f"Missing required key: {key!r}"
raise KeyError(msg)


logger.info("Dispatching order_id=%s to shop_id=%s", order_id, shop_id) # structured
```

Expand Down
6 changes: 3 additions & 3 deletions .rules/python-generators.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def iter_user_names(users):
if user.active and user.name:
yield user.name.upper()


def get_names(users):
return list(iter_user_names(users))
```
Expand All @@ -50,11 +51,10 @@ def get_names(users):
```python
from itertools import islice


def top_active_emails(users):
emails = (
user.email.lower()
for user in users
if user.active and user.email is not None
user.email.lower() for user in users if user.active and user.email is not None
)
return list(islice(emails, 10))
```
Expand Down
4 changes: 4 additions & 0 deletions .rules/python-return.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Follow these rules:
def func():
return None


# GOOD:
def func():
return
Expand All @@ -30,6 +31,7 @@ def func(x):
return x
# implicitly returns None (bad)


# GOOD:
def func(x):
if x > 0:
Expand All @@ -50,6 +52,7 @@ def func(x):
return x
# no return (bad)


# GOOD:
def func(x):
if x > 0:
Expand All @@ -69,6 +72,7 @@ def func():
result = compute()
return result


# GOOD:
def func():
return compute()
Expand Down
11 changes: 9 additions & 2 deletions .rules/python-typing.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ with integers or strings is required (e.g. for database or JSON serialization).
```python
import enum


class Status(enum.Enum):
PENDING = enum.auto()
COMPLETE = enum.auto()


class ErrorCode(enum.IntEnum):
OK = 0
NOT_FOUND = 404


class Role(enum.StrEnum):
ADMIN = enum.auto()
GUEST = enum.auto()
Expand Down Expand Up @@ -65,6 +68,7 @@ returns the same instance.
```python
import typing


class Builder:
def add(self, value: int) -> typing.Self:
self.values.append(value)
Expand All @@ -81,9 +85,10 @@ enables static analysis tools to detect typos and signature mismatches.
```python
import typing


class Base:
def run(self) -> None:
...
def run(self) -> None: ...


class Child(Base):
@typing.override
Expand All @@ -101,6 +106,7 @@ checkers.
```python
import typing


def is_str_list(val: list[object]) -> typing.TypeIs[list[str]]:
return all(isinstance(x, str) for x in val)
```
Expand All @@ -116,6 +122,7 @@ type is provided.
```python
T = typing.TypeVar("T", default=int)


class Box[T]:
def __init__(self, value: T = T()):
self.value = value
Expand Down
11 changes: 6 additions & 5 deletions docs/cmd-mox-users-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,19 @@ behaviour.
Combine methods to describe how a command should be invoked:

```python
cmd_mox.mock("git") \
.with_args("clone", "https://example.com/repo.git") \
.returns(exit_code=0)
cmd_mox.mock("git").with_args("clone", "https://example.com/repo.git").returns(
exit_code=0
)
```

Arguments can be matched more flexibly using comparators:

```python
from cmd_mox import Regex, Contains

cmd_mox.mock("curl") \
.with_matching_args(Regex(r"--header=User-Agent:.*"), Contains("example"))
cmd_mox.mock("curl").with_matching_args(
Regex(r"--header=User-Agent:.*"), Contains("example")
)
```

The design document lists the available comparators:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,7 @@ The implementation introduces one new error type and a guard function:
```python
class ArchitectureBoundaryError(Exception):
"""Raised when orchestration code violates hexagonal architecture boundaries."""

pass
```

Expand Down Expand Up @@ -757,6 +758,7 @@ from episodic.orchestration._checkpoint_payload import (
_planner_result_from_payload,
)


@given(
# Strategy TBD based on actual DTO types
st.just(...)
Expand All @@ -765,6 +767,7 @@ def test_checkpoint_payload_round_trip(payload: dict) -> None:
"""Assert checkpoint payloads round-trip without data loss."""
# Implementation TBD


def test_checkpoint_payload_boundary_purity(payload: dict) -> None:
"""Assert checkpoint payloads contain no adapter types."""
# Implementation TBD
Expand Down
2 changes: 1 addition & 1 deletion docs/execplans/support-cranelift-codegen.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ Inspect each log file. All must pass. If any fail, fix the issue and re-run.
cargo_config_dir = tmp_path / ".cargo"
cargo_config_dir.mkdir()
(cargo_config_dir / "config.toml").write_text(
'[unstable]\ncodegen-backend = true\n\n'
"[unstable]\ncodegen-backend = true\n\n"
'[profile.dev]\ncodegen-backend = "cranelift"\n\n'
'[profile.test]\ncodegen-backend = "cranelift"\n',
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ loop:
```python
from cmd_mox import CmdMox


def test_record(tmp_path: Path) -> None:
artifact_dir = tmp_path / "act-artifacts"
with CmdMox() as mox:
Expand Down
3 changes: 1 addition & 2 deletions docs/python-action-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ app.config = (*tuple(getattr(app, "config", ())), _env_config)


@app.default
def main(*, bin_name: str, version: str, formats: list[str] | None = None) -> None:
...
def main(*, bin_name: str, version: str, formats: list[str] | None = None) -> None: ...


if __name__ == "__main__":
Expand Down
12 changes: 8 additions & 4 deletions docs/scripting-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,16 @@ def main(
# Required parameters
bin_name: Annotated[str, Parameter(required=True)],
version: Annotated[str, Parameter(required=True)],

# Optional scalars
package_name: Optional[str] = None,
target: Optional[str] = None,
outdir: Optional[Path] = None,
dry_run: bool = False,

# Lists (whitespace/newline separated by default)
formats: list[str] | None = None,
man_paths: Annotated[list[Path] | None, Parameter(env_var="INPUT_MAN_PATHS")] = None,
man_paths: Annotated[
list[Path] | None, Parameter(env_var="INPUT_MAN_PATHS")
] = None,
deb_depends: list[str] | None = None,
rpm_depends: list[str] | None = None,
):
Expand Down Expand Up @@ -257,7 +257,9 @@ f.write_text("1.2.3\n", encoding="utf-8")
version = f.read_text(encoding="utf-8").strip()

# Atomic write pattern (tmp → replace)
with tempfile.NamedTemporaryFile("w", delete=False, dir=f.parent, encoding="utf-8") as tmp:
with tempfile.NamedTemporaryFile(
"w", delete=False, dir=f.parent, encoding="utf-8"
) as tmp:
tmp.write("new-contents\n")
tmp_path = Path(tmp.name)

Expand Down Expand Up @@ -302,6 +304,7 @@ from plumbum.cmd import git

app = App(config=cyclopts.config.Env("INPUT_", command=False))


@app.default
def main(
*,
Expand All @@ -326,6 +329,7 @@ def main(
"dist": str(dist),
})


if __name__ == "__main__":
app()
```
Expand Down
16 changes: 14 additions & 2 deletions workflow_scripts/mutation_detect_changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,11 +274,22 @@ def full_run_matrix(config: DetectionConfig) -> list[MatrixEntry]:
return entries


def _root_first_key(item: tuple[str, list[str]]) -> tuple[bool, str]:
"""Sort key placing the root (``.``) target first, then alphabetical.

``.`` sorts before every directory name, so the root-first order also
falls out of a plain alphabetical sort.
"""
root_rank = item[0] != "."
alphabetical_key = item[0] # pragma: no mutate - plain sorting is equivalent
return (root_rank, alphabetical_key)


def scoped_run_matrix(
buckets: dict[str, list[str]], config: DetectionConfig
) -> list[MatrixEntry]:
"""Build the single-shard matrix for a scoped (scheduled) run."""
ordered = sorted(buckets.items(), key=lambda item: (item[0] != ".", item[0]))
ordered = sorted(buckets.items(), key=_root_first_key) # pragma: no mutate
del config # scoped runs never shard; kept for signature symmetry
return [
MatrixEntry(
Expand Down Expand Up @@ -311,7 +322,8 @@ def _write_skip_summary(config: DetectionConfig) -> None:
summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
if not summary_path:
return
with Path(summary_path).open("a", encoding="utf-8") as handle:
encoding = "utf-8" # pragma: no mutate - locale-independent UTF-8 alias
with Path(summary_path).open("a", encoding=encoding) as handle:
handle.write(
SKIP_SUMMARY_TEMPLATE.format(
base_ref=config.base_ref, window_hours=config.window_hours
Expand Down
Loading
Loading