Skip to content

@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:

@strict_access_control()
class Base:
    @protected
    def _hook(self) -> str:
        return "base"

class Sub(Base):
    @protected
    def _hook(self) -> str:
        return "sub"

    def call(self) -> str:
        return self._hook()

Sub().call()                            # "sub"
Sub()._hook()                           # raises ProtectedAccessError