Skip to content

Releases

v0.3.4

Choose a tag to compare

@yudelevi yudelevi released this 09 Oct 16:51
2f6a54a
  • Python 3.15 is supported and tested in CI.

v0.3.3

Choose a tag to compare

@yudelevi yudelevi released this 08 Oct 00:20
5264daf
  • The archive download behind mpat diff and mpat check --diff treats the
    package index as untrusted input. It only picks archives whose name is the
    locked project's, so a filename like ../../x-1.0.tar.gz can no longer write
    outside MPAT_CACHE_DIR. It only follows http and https links, so an
    index cannot point it at a local file:// path. Error messages drop query
    strings and credentials from URLs, so a signed download URL does not end up
    in a CI log.

v0.3.2

Choose a tag to compare

@yudelevi yudelevi released this 08 Oct 00:13
2802dfb
  • mpat check lists only the targets that are not ok and ends with an
    N ok count, so a CI log shows the drift instead of burying it in a table
    of every watched symbol. --all restores the full table. --json,
    --gitlab and the exit code are unchanged.
  • A target deleted upstream is reported as missing. Before, patch() and
    watch() resolved their target while mpat check was collecting, so the
    deletion aborted the run with exit 2, no table and an empty GitLab report.
    Outside collection a missing target still raises TargetNotFound.
  • Rows with a changed signature, value, location or kind list the change under
    them, e.g. value_repr: 16 -> 2, and the GitLab description carries the
    same text. --json gains a changes list.
  • GitLab Code Quality findings point at the line of the patch() or watch()
    call instead of the first line quoting the target, which could be an
    unrelated string. --json gains declared_line.
  • A change confined to docstrings reports the new docstring status instead of
    body, with its own footer line and minor GitLab severity. It still exits 1.
    The lock gains a code_hash field for this; the first mpat lock after the
    upgrade lists every function and class as changed (~) while it records it,
    and until then a docstring change keeps reporting body.
  • mpat diff and mpat check --diff show how a drifted target's upstream
    source changed, as a diff in the style of a ty diagnostic, with the locked
    and installed line numbers side by side. The lock holds hashes, not source,
    so the locked side comes from the release archive on the package index
    (UV_DEFAULT_INDEX, UV_INDEX_URL, PIP_INDEX_URL, else PyPI), cached in
    MPAT_CACHE_DIR. Index credentials never follow a redirect to another
    host. A fetch failure prints why and leaves the exit code alone. mpat diff
    without a target covers every drifted target, and now imports the
    configured modules like mpat check.
  • A target used by several declarations, such as a shared depends_on, lists
    the others under its row and in --json (used_by), and GitLab gets one
    finding per declaration, so every patch that depends on the drift is marked.

v0.3.1

Choose a tag to compare

@yudelevi yudelevi released this 17 Sep 15:49
ad4cb32
  • A watch can name a file shipped inside an installed package:
    file = "nicegui/elements/select.js" in a [[tool.mpat.watch]] table, or
    watch("nicegui/elements/select.js") from code. The lock records the sha256
    of the file's bytes with kind file, and mpat check reports the new
    content status when they change, whitespace included, so a fork of an
    upstream JavaScript or template file gets re-diffed on the bump that touched
    it instead of when someone notices. Files can be watched, not patched or
    overridden.
  • mpat check --gitlab renders the non-OK results as a GitLab Code Quality
    report, so drift is listed in the merge request instead of only in the job
    log. location.path is resolved against the repository root rather than the
    mpat root, because GitLab reads it relative to the checkout: a run inside
    services/api reports services/api/src/patches.py, not src/patches.py.
    The root is the nearest ancestor holding .git, which --repo-root PATH
    overrides. --gitlab and --json are mutually exclusive, and the exit code
    is unchanged, so the CI job needs artifacts: when: always to upload the
    report on the run that fails.

0.2.2

  • Configuration can live in a standalone mpat.toml next to pyproject.toml.
    It carries the same keys without the [tool.mpat] prefix, so
    [[tool.mpat.watch]] becomes [[watch]]. When both files are in the same
    directory mpat.toml wins and the [tool.mpat] section is ignored, the rule
    ruff and ty follow. Error messages, declared_in and the source column of
    mpat check name whichever file the declaration came from.
  • New mpat diff <target> prints the drift status of one locked target and every
    fingerprint field as locked -> current. It reads mpat.lock and resolves the
    target without importing [tool.mpat] modules. Exit 2 when the target is not
    in the lock.
  • on_drift and MPAT_STRICT=1 now cover depends_on at import. A dependency
    whose lock entry no longer matches warns, skips or raises together with its
    patch, and the message says which entry drifted. Before, only mpat check and
    the pytest plugin looked at dependencies.
  • [tool.mpat] modules and allow must be lists of strings, tool.mpat must
    be a table, and unknown keys in the section are configuration errors. A bare
    string in modules used to be split into single characters.
  • A source file that cannot be read or parsed no longer crashes mpat lock or
    mpat check; the target is fingerprinted by signature only and marked
    no_source, like a .pyc-only install.
  • A name defined more than once in one file, in if/else or try/except
    branches, is now hashed at the definition that is actually live, matched by
    the code object's first line. The first same-named definition used to win.
  • A target whose module raises ImportError on import now fails as a named
    configuration error (exit 2) instead of a bare traceback, and says that a
    module that has to be imported first belongs in [tool.mpat] modules. The new
    TargetImportError subclasses both MpatError and ImportError, so it is
    still caught by anything that caught the raw error, and it is not
    TargetNotFound, so mpat check never reports a broken environment as
    missing.

0.2.1

  • New patch(..., when_imported=True). The declaration registers without
    importing the target; the patch is applied by a post-import hook the first
    time the target's top-level module is imported, or immediately if it already
    is. This makes optional dependencies patchable: a target whose distribution is
    not installed no longer raises at declaration, it just never fires.
    mpat lock and mpat check are unchanged, so the target still has to be
    importable when you lock.
  • New mpat.apply_all(), which imports every module a when_imported patch is
    still waiting on and so applies them all now.
  • New watch(..., track_value=False) records a scalar's existence and kind but not
    its value. Use it for an upstream setting your application assigns itself,
    where mpat lock sees upstream's default and the pytest plugin sees your
    override. The lock entry carries track_value = false and no value_repr, and
    changing track_value on a locked target reports unlocked until the next
    mpat lock, like a role or depends_on change.
  • Watches can be declared in pyproject.toml as [[tool.mpat.watch]] tables
    with target, depends_on, review_by and note, so a project with no
    patch module still gets mpat lock, mpat check and the pytest items. The
    entry is locked with declared_in = "pyproject.toml". A target declared
    both in code and in pyproject.toml is refused as declared twice.
  • New [[tool.mpat.override]] tables assign a scalar upstream attribute from
    pyproject.toml, and new mpat.apply_overrides() applies them once per
    process; that call is the only code an override needs. The target must be an
    existing attribute of exactly the value's type, bool and int included,
    and it goes through the same drift check as a watch() before assignment.
    Each override is locked as a watch with track_value = false, so mpat check
    is green whether or not the override was applied in that process.
  • watch() accepts until, the same Version, Probe, | and & as
    patch. A watch applies nothing, so the condition has no runtime effect; it
    gives the workaround a generated still-needed[<target>] test, which is what
    a per-instance setattr wrapper or an attribute-creation shim needed instead
    of a hand-written one. [[tool.mpat.watch]] entries take until as a
    string: a requirement with a version specifier, or a dotted path to a
    zero-argument callable that is imported when first evaluated.
  • Fixed: the pytest plugin looked for [tool.mpat] only at pytest's rootdir,
    so in a monorepo with one pyproject.toml per service and none at the root,
    pytest services/api/tests from the root silently collected nothing. The
    plugin now walks up from each directory argument to its nearest
    pyproject.toml and collects one set of items per [tool.mpat] it finds,
    each reading the lockfile next to its own pyproject.toml. A project that is
    not the rootdir's own is collected as mpat[services/api]::...; the rootdir's
    own project keeps the mpat::... node ids it had.

0.2.0

  • The generated drift tests are now collected by a pytest plugin. Installing
    mpat is enough: a project with [tool.mpat] in its pyproject.toml gets
    mpat::drift[...] and mpat::still-needed[...] items in its normal pytest
    run, with no test module to write. A project with no [tool.mpat] collects
    nothing.
  • The generated items are collected by pytest and by pytest <dir>. An
    argument that names a file or a nodeid selects only what it names, since items
    that belong to no file cannot be narrowed by a path.
  • Fixed: collecting the generated tests imports your patch modules normally, so
    the patches apply and later tests in the session see patched behaviour.
    Previously both the plugin and mpat.testing imported them in the collect-only
    mode the CLI uses, which cached them unpatched and made a suite pass or fail
    depending on file order.
  • New --no-mpat flag, and a mpat = false ini option, to turn the generated
    items off.
  • from mpat.testing import test_upstream_drift, test_patch_still_needed keeps
    working. When that module is collected, the plugin adds nothing, so the checks
    never run twice.

0.1.0

First release. See https://github.com/yudelevi/mpat/releases/tag/v0.1.0.

mpat 0.3.0

Choose a tag to compare

@yudelevi yudelevi released this 14 Sep 19:20
  • mpat check --gitlab renders the non-OK results as a GitLab Code Quality
    report, so drift is listed in the merge request instead of only in the job
    log. location.path is resolved against the repository root rather than the
    mpat root, because GitLab reads it relative to the checkout: a run inside
    services/api reports services/api/src/patches.py, not src/patches.py.
    The root is the nearest ancestor holding .git, which --repo-root PATH
    overrides. --gitlab and --json are mutually exclusive, and the exit code
    is unchanged, so the CI job needs artifacts: when: always to upload the
    report on the run that fails.

mpat 0.2.2

Choose a tag to compare

@yudelevi yudelevi released this 10 Sep 16:33
  • Configuration can live in a standalone mpat.toml next to pyproject.toml.
    It carries the same keys without the [tool.mpat] prefix, so
    [[tool.mpat.watch]] becomes [[watch]]. When both files are in the same
    directory mpat.toml wins and the [tool.mpat] section is ignored, the rule
    ruff and ty follow. Error messages, declared_in and the source column of
    mpat check name whichever file the declaration came from.
  • New mpat diff <target> prints the drift status of one locked target and every
    fingerprint field as locked -> current. It reads mpat.lock and resolves the
    target without importing [tool.mpat] modules. Exit 2 when the target is not
    in the lock.
  • on_drift and MPAT_STRICT=1 now cover depends_on at import. A dependency
    whose lock entry no longer matches warns, skips or raises together with its
    patch, and the message says which entry drifted. Before, only mpat check and
    the pytest plugin looked at dependencies.
  • [tool.mpat] modules and allow must be lists of strings, tool.mpat must
    be a table, and unknown keys in the section are configuration errors. A bare
    string in modules used to be split into single characters.
  • A source file that cannot be read or parsed no longer crashes mpat lock or
    mpat check; the target is fingerprinted by signature only and marked
    no_source, like a .pyc-only install.
  • A name defined more than once in one file, in if/else or try/except
    branches, is now hashed at the definition that is actually live, matched by
    the code object's first line. The first same-named definition used to win.
  • A target whose module raises ImportError on import now fails as a named
    configuration error (exit 2) instead of a bare traceback, and says that a
    module that has to be imported first belongs in [tool.mpat] modules. The new
    TargetImportError subclasses both MpatError and ImportError, so it is
    still caught by anything that caught the raw error, and it is not
    TargetNotFound, so mpat check never reports a broken environment as
    missing.

mpat 0.2.1

Choose a tag to compare

@yudelevi yudelevi released this 10 Sep 04:15
fdf4733

0.2.1

  • New patch(..., when_imported=True). The declaration registers without
    importing the target; the patch is applied by a post-import hook the first
    time the target's top-level module is imported, or immediately if it already
    is. This makes optional dependencies patchable: a target whose distribution is
    not installed no longer raises at declaration, it just never fires.
    mpat lock and mpat check are unchanged, so the target still has to be
    importable when you lock.
  • New mpat.apply_all(), which imports every module a when_imported patch is
    still waiting on and so applies them all now.
  • New watch(..., track_value=False) records a scalar's existence and kind but not
    its value. Use it for an upstream setting your application assigns itself,
    where mpat lock sees upstream's default and the pytest plugin sees your
    override. The lock entry carries track_value = false and no value_repr, and
    changing track_value on a locked target reports unlocked until the next
    mpat lock, like a role or depends_on change.
  • Watches can be declared in pyproject.toml as [[tool.mpat.watch]] tables
    with target, depends_on, review_by and note, so a project with no
    patch module still gets mpat lock, mpat check and the pytest items. The
    entry is locked with declared_in = "pyproject.toml". A target declared
    both in code and in pyproject.toml is refused as declared twice.
  • New [[tool.mpat.override]] tables assign a scalar upstream attribute from
    pyproject.toml, and new mpat.apply_overrides() applies them once per
    process; that call is the only code an override needs. The target must be an
    existing attribute of exactly the value's type, bool and int included,
    and it goes through the same drift check as a watch() before assignment.
    Each override is locked as a watch with track_value = false, so mpat check
    is green whether or not the override was applied in that process.
  • watch() accepts until, the same Version, Probe, | and & as
    patch. A watch applies nothing, so the condition has no runtime effect; it
    gives the workaround a generated still-needed[<target>] test, which is what
    a per-instance setattr wrapper or an attribute-creation shim needed instead
    of a hand-written one. [[tool.mpat.watch]] entries take until as a
    string: a requirement with a version specifier, or a dotted path to a
    zero-argument callable that is imported when first evaluated.
  • Fixed: the pytest plugin looked for [tool.mpat] only at pytest's rootdir,
    so in a monorepo with one pyproject.toml per service and none at the root,
    pytest services/api/tests from the root silently collected nothing. The
    plugin now walks up from each directory argument to its nearest
    pyproject.toml and collects one set of items per [tool.mpat] it finds,
    each reading the lockfile next to its own pyproject.toml. A project that is
    not the rootdir's own is collected as mpat[services/api]::...; the rootdir's
    own project keeps the mpat::... node ids it had.

mpat 0.2.0

Choose a tag to compare

@yudelevi yudelevi released this 09 Sep 23:24

0.2.0

  • The generated drift tests are now collected by a pytest plugin. Installing
    mpat is enough: a project with [tool.mpat] in its pyproject.toml gets
    mpat::drift[...] and mpat::still-needed[...] items in its normal pytest
    run, with no test module to write. A project with no [tool.mpat] collects
    nothing.
  • The generated items are collected by pytest and by pytest <dir>. An
    argument that names a file or a nodeid selects only what it names, since items
    that belong to no file cannot be narrowed by a path.
  • Fixed: collecting the generated tests imports your patch modules normally, so
    the patches apply and later tests in the session see patched behaviour.
    Previously both the plugin and mpat.testing imported them in the collect-only
    mode the CLI uses, which cached them unpatched and made a suite pass or fail
    depending on file order.
  • New --no-mpat flag, and a mpat = false ini option, to turn the generated
    items off.
  • from mpat.testing import test_upstream_drift, test_patch_still_needed keeps
    working. When that module is collected, the plugin adds nothing, so the checks
    never run twice.

mpat 0.1.0

Choose a tag to compare

@yudelevi yudelevi released this 09 Sep 21:19

First usable release.

  • @patch(target, depends_on=..., until=..., on_drift=..., review_by=..., note=...) and watch(target, ...)
  • Fingerprints from the installed source file via ast, never from live objects
  • mpat.lock: TOML, keyed tables, atomic write
  • mpat lock, mpat check [--json], mpat show; exit codes 0/1/2
  • Drift statuses: ok, missing, moved, signature, body, value, no_source, unlocked, stale, review
  • mpat.testing: generated pytest tests for drift and for patches whose until condition flipped
  • Denylist for security-critical modules, overridable only via [tool.mpat] allow
  • Python 3.11 to 3.14, typed (py.typed)

Docs: https://monkeypat.ch