@protected¶
A method marked @protected is callable from the defining class and any
subclass. External callers are rejected with ProtectedAccessError.
from strictaccess import strict_access_control, protected, ProtectedAccessError
@strict_access_control()
class Service:
@protected
def _build_request(self) -> dict:
return {"id": 1}
def execute(self) -> dict:
return self._build_request() # OK: same class
class FastService(Service):
def fast_execute(self) -> dict:
return self._build_request() # OK: subclass
FastService().fast_execute() # {"id": 1}
Service()._build_request() # raises ProtectedAccessError
The underscore convention is automatic protected¶
Any attribute or method whose name starts with _ (one underscore, not
two) is treated as protected even without the decorator:
@strict_access_control()
class Config:
def __init__(self) -> None:
self._host = "localhost"
Config()._host # raises ProtectedAccessError
So you only need @protected on methods you want to mark explicitly (for
clarity in a code review) or on methods that do not start with an
underscore but should still be protected.
Use it for¶
- Hook methods that subclasses are expected to override.
- Helpers that subclasses may reasonably want to call.
- Internal API that should not leak to consumers but is useful in the class hierarchy.
Combining with @classmethod / @staticmethod¶
@strict_access_control()
class Builder:
@protected
@classmethod
def _make(cls): ...
def create(self):
return type(self)._make() # OK
Override in a subclass¶
Overriding a protected method preserves the contract: