Skip to main content

Runtime final guard for methods/properties across projects

Project description

Access Guard

A super lightweight runtime guard that provides a @final decorator, final_class, and an Access metaclass. It prevents methods/properties (optionally magic methods) marked as final from being overridden in subclasses, and blocks dynamic overriding after a class is created.

Features

  • Supports regular methods, @staticmethod, @classmethod, and @property (including getter / setter / deleter).
  • Optionally lock magic methods: @final_class(include_magic=True).
  • No third-party dependencies; standard library only, zero runtime external deps.
  • Validates at class creation time; violations raise RuntimeError (fail fast).
  • Prevents monkey patching: reassigning a final member after class creation is blocked.
  • PEP 561: ships py.typed, friendly to type checkers.
  • O(member count) lightweight scan; no added overhead on runtime calls.

Installation

Supports Python 3.10+.

Using uv:

uv add access-guard

Or using pip:

pip install access-guard

Quick Examples

final

from access_guard import Access, final

class Base(metaclass=Access):
    @final
    def stable(self):
        return "v1"

    @property
    @final
    def value(self):
        return 42

class Child(Base):
    pass

Child().stable()  # OK

# Any of the following will raise RuntimeError:
# class Bad(Base):
#     def stable(self):  # overriding a final method
#         return "v2"

# Bad.stable = lambda self: "hack"   # dynamic override after class creation

final_class

Mark all currently defined non-magic members in the class as final in one go:

from access_guard import Access, final_class

@final_class
class Service(metaclass=Access):
    def create(self):
        return "created"

    def delete(self):
        return "deleted"

class Child(Service):
    pass  # OK

# The following will fail:
# class Bad(Service):
#     def create(self):  # RuntimeError
#         return "x"

Note: Members added dynamically after the class is created will not be final automatically; it’s recommended to use @final on those members individually when you define them.

Parameters

from access_guard import Access, final_class

# Exclude some methods from being locked
@final_class(exclude={"debug", "open"})
class Service(metaclass=Access):
    def create(self): ...  # final
    def debug(self): ...   # not final
    def open(self): ...    # not final

# Lock including magic methods
@final_class(include_magic=True)
class Model(metaclass=Access):
    def __repr__(self):
        return "Model()"   # final
    def run(self):
        return 1           # final

# Use both together
@final_class(exclude=["__repr__"], include_magic=True)
class Partial(metaclass=Access):
    def __repr__(self):    # not final
        return "P()"
    def calc(self):        # final
        return 42

Parameter details:

  • exclude: iterable[str]. Names that should NOT be marked final; can be list / set / tuple.
  • include_magic: when True, magic methods like __repr__ are also marked (builtin low-level __dict__ and __weakref__ are always ignored).

Behavior Details

  1. @final marks a special attribute on the actual function/property/descriptor object; for properties it also marks their accessors.
  2. final_class collects all members in the class at decoration time and marks them; exclude and include_magic control the scope.
  3. Access metaclass in __new__:
    • Collects all final names from base classes (including magic);
    • Checks whether the subclass overrides them;
    • Merges and records them to the subclass.
  4. Reassigning a final name after class creation is rejected in __setattr__.
  5. If a base class uses final_class(include_magic=True), its magic methods are locked and propagated to all subclasses.

Limitations / Notes

  • Protection only applies at class definition and dynamic assignment levels; instance attributes are not handled.
  • Alias references are not tracked (you can still copy a function object and assign it under another name).
  • With multiple inheritance: if different base classes define different member names, everything works as usual; if there’s a name conflict where one base marks it final, the subclass cannot override it.
  • Does not attempt to intercept complex descriptor dynamics beyond types.FunctionType (e.g., some dynamic __getattr__ patterns).
  • Does not attempt to block low-level metaclass-level direct __dict__ manipulation (deliberate attack scenarios).

Tests

After installing dev dependencies:

uv sync --extra dev
uv run pytest -q

Or using pip:

pip install -e .[dev]
pytest -q

Versioning

Semantic Versioning. In the early phase (<1.0.0), breaking changes may occur.
Current version: 0.1.3.

Roadmap (potential)

  • Add an opt-in mode for locking instance-level resources.
  • Integrate with type checkers (mypy/pyright) via plugin (static early warnings).
  • Provide a CLI quick scan (list which names are locked).
  • Track override intent: allow explicit opt-out via @allow_override (in design).

License

MIT License. See LICENSE.

Feedback / Contribution

Issues and PRs are welcome:

  • Describe your use case and expected behavior
  • Provide a minimal reproducible example
  • Specify Python version and package version

For performance topics, please include a simple benchmark (timeit or pyperf).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

access_guard-0.1.6.tar.gz (7.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

access_guard-0.1.6-py3-none-any.whl (7.1 kB view details)

Uploaded Python 3

File details

Details for the file access_guard-0.1.6.tar.gz.

File metadata

  • Download URL: access_guard-0.1.6.tar.gz
  • Upload date:
  • Size: 7.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.0

File hashes

Hashes for access_guard-0.1.6.tar.gz
Algorithm Hash digest
SHA256 46824d9a07a9fa4f75bd745bca4c007e277a4d4f9e8f7c3f285ce2840715908a
MD5 06872b3015c05d61c462a940870853ea
BLAKE2b-256 10b39fdcbf2d7cc041680119c5a83d2233f60973aa9141d8b74ebcea15426e07

See more details on using hashes here.

File details

Details for the file access_guard-0.1.6-py3-none-any.whl.

File metadata

  • Download URL: access_guard-0.1.6-py3-none-any.whl
  • Upload date:
  • Size: 7.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.0

File hashes

Hashes for access_guard-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 5ecbe3e40d6e560b8dee66d42af27f7301d4104eaff5ff8f1e55c43961176fb9
MD5 cc8d85441d8ddb7908746b5fabac2ac2
BLAKE2b-256 7f848fe88e95312a2722e9ffe63cea23da4df937ce9f94b09fcc4d84f951343f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page