Coverage for src/pypermission/models.py: 100%
69 statements
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-14 14:14 +0000
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-14 14:14 +0000
1from typing import Never
3from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
4from sqlalchemy.sql.schema import ForeignKey
5from sqlalchemy.sql.sqltypes import String
7from pypermission.exc import PyPermissionError
10class BaseORM(DeclarativeBase): ...
13################################################################################
14#### Types
15################################################################################
18class Permission:
19 """
20 Represents a Resource paired with an Action.
22 Attributes
23 ----------
24 resource_type : str
25 The ResourceType (e.g., "document", "user").
26 resource_id : str
27 The ResourceID. The star '*' acts as a wildcard matching all ResourceIDs of the same ResourceType. The empty string can be used for Actions on Resources that do not have an ResourceID.
28 action : str
29 The Action allowed on the Resource (e.g., "read", "write", "delete").
30 """
32 resource_type: str
33 resource_id: str
34 action: str
36 def __init__(self, *, resource_type: str, resource_id: str, action: str) -> None:
37 """
38 Initialize the Permission.
40 Parameters
41 ----------
42 resource_type : str
43 The type of the resource (e.g., "document", "user").
44 resource_id : str
45 The ID of the resource instance. The start '*' acts as a wildcard matching all IDs of the resource. The empty string can be used for actions on resources that do not have an ID.
46 action : str
47 The action allowed on the resource (e.g., "read", "write", "delete").
48 """
49 if resource_type == "":
50 raise PyPermissionError("Resource type cannot be empty!")
51 if action == "":
52 raise PyPermissionError("Action cannot be empty!")
54 self.resource_type = resource_type
55 self.resource_id = resource_id
56 self.action = action
58 def __str__(self) -> str:
59 if not self.resource_id:
60 return f"{self.resource_type}:{self.action}"
61 return f"{self.resource_type}[{self.resource_id}]:{self.action}"
63 def __eq__(self, other: object) -> bool:
64 if not isinstance(other, Permission):
65 return False
67 return (
68 self.resource_type == other.resource_type
69 and self.resource_id == other.resource_id
70 and self.action == other.action
71 )
73 def __ne__(self, other: object) -> bool:
74 return not self.__eq__(other)
77class Policy:
78 """
79 Represents a Role paired with a Permission.
81 Attributes
82 ----------
83 role : str
84 The target RoleID.
85 permission : Permission
86 The target Permission.
87 """
89 role: str
90 permission: Permission
92 def __init__(self, *, role: str, permission: Permission) -> None:
93 """
94 Initialize the Policy.
96 Parameters
97 ----------
98 role : str
99 The target RoleID.
100 permission : Permission
101 The target Permission.
102 """
103 if role == "":
104 raise PyPermissionError("Role name cannot be empty!")
105 self.role = role
106 self.permission = permission
108 def __str__(self) -> str:
109 return f"{self.role}:{self.permission}"
111 def __eq__(self, other: object) -> bool:
112 if not isinstance(other, Policy):
113 return False
115 return self.role == other.role and self.permission == other.permission
117 def __ne__(self, other: object) -> bool:
118 return not self.__eq__(other)
121class FrozenClass(type):
122 def __setattr__(cls, key: str, value: Never) -> None:
123 if key in cls.__dict__:
124 raise AttributeError("Frozen attributes cannot be modified!")
125 super().__setattr__(key, value)
128################################################################################
129#### RoleORM
130################################################################################
133class RoleORM(BaseORM):
134 __tablename__ = "pp_role_table"
135 id: Mapped[str] = mapped_column(String, primary_key=True)
138################################################################################
139#### HierarchyORM
140################################################################################
143class HierarchyORM(BaseORM):
144 __tablename__ = "pp_hierarchy_table"
145 parent_role_id: Mapped[str] = mapped_column(
146 String, ForeignKey("pp_role_table.id", ondelete="CASCADE"), primary_key=True
147 )
148 child_role_id: Mapped[str] = mapped_column(
149 String, ForeignKey("pp_role_table.id", ondelete="CASCADE"), primary_key=True
150 )
153################################################################################
154#### SubjectORM
155################################################################################
158class SubjectORM(BaseORM):
159 __tablename__ = "pp_subject_table"
160 id: Mapped[str] = mapped_column(String, primary_key=True)
163################################################################################
164#### MemberORM
165################################################################################
168class MemberORM(BaseORM):
169 __tablename__ = "pp_member_table"
170 role_id: Mapped[str] = mapped_column(
171 String, ForeignKey("pp_role_table.id", ondelete="CASCADE"), primary_key=True
172 )
173 subject_id: Mapped[str] = mapped_column(
174 String, ForeignKey("pp_subject_table.id", ondelete="CASCADE"), primary_key=True
175 )
178################################################################################
179#### PolicyORM
180################################################################################
183class PolicyORM(BaseORM):
184 __tablename__ = "pp_policy_table"
185 role_id: Mapped[str] = mapped_column(
186 String, ForeignKey("pp_role_table.id", ondelete="CASCADE"), primary_key=True
187 )
188 resource_type: Mapped[str] = mapped_column(String, primary_key=True)
189 resource_id: Mapped[str] = mapped_column(String, primary_key=True)
190 action: Mapped[str] = mapped_column(String, primary_key=True)