Coverage for src/pypermission/models.py: 99%

60 statements  

« prev     ^ index     » next       coverage.py v7.11.3, created at 2025-12-01 18:06 +0000

1from typing import Never 

2 

3from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column 

4from sqlalchemy.sql.schema import ForeignKey 

5from sqlalchemy.sql.sqltypes import String 

6 

7from pypermission.util.input_validation import validate_rbac_parameters 

8 

9 

10class PyPermissionORM(DeclarativeBase): ... 10 ↛ 18line 10 didn't jump to line 18 because

11 

12 

13################################################################################ 

14#### Types 

15################################################################################ 

16 

17 

18class Permission: 

19 """ 

20 Represents a **Resource** paired with an **Action**. 

21 

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 """ 

31 

32 resource_type: str 

33 resource_id: str 

34 action: str 

35 

36 @validate_rbac_parameters 

37 def __init__(self, *, resource_type: str, resource_id: str, action: str) -> None: 

38 """ 

39 Initialize the **Permission**. 

40 

41 Parameters 

42 ---------- 

43 resource_type : str 

44 The type of the resource (e.g., "document", "user"). 

45 resource_id : str 

46 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. 

47 action : str 

48 The action allowed on the resource (e.g., "read", "write", "delete"). 

49 """ 

50 

51 self.resource_type = resource_type 

52 self.resource_id = resource_id 

53 self.action = action 

54 

55 def __str__(self) -> str: 

56 if not self.resource_id: 

57 return f"{self.resource_type}:{self.action}" 

58 return f"{self.resource_type}[{self.resource_id}]:{self.action}" 

59 

60 def __eq__(self, other: object) -> bool: 

61 if not isinstance(other, Permission): 

62 return False 

63 

64 return ( 

65 self.resource_type == other.resource_type 

66 and self.resource_id == other.resource_id 

67 and self.action == other.action 

68 ) 

69 

70 def __ne__(self, other: object) -> bool: 

71 return not self.__eq__(other) 

72 

73 

74class Policy: 

75 """ 

76 Represents a **Role** paired with a **Permission**. 

77 

78 Attributes 

79 ---------- 

80 role : str 

81 The target **RoleID**. 

82 permission : Permission 

83 The target **Permission**. 

84 """ 

85 

86 role: str 

87 permission: Permission 

88 

89 @validate_rbac_parameters 

90 def __init__(self, *, role: str, permission: Permission) -> None: 

91 """ 

92 Initialize the Policy. 

93 

94 Parameters 

95 ---------- 

96 role : str 

97 The target **RoleID**. 

98 permission : Permission 

99 The target **Permission**. 

100 """ 

101 self.role = role 

102 self.permission = permission 

103 

104 def __str__(self) -> str: 

105 return f"{self.role}:{self.permission}" 

106 

107 def __eq__(self, other: object) -> bool: 

108 if not isinstance(other, Policy): 

109 return False 

110 

111 return self.role == other.role and self.permission == other.permission 

112 

113 def __ne__(self, other: object) -> bool: 

114 return not self.__eq__(other) 

115 

116 

117class FrozenClass(type): 

118 def __setattr__(cls, key: str, value: Never) -> None: 

119 if key in cls.__dict__: 

120 raise AttributeError("Frozen attributes cannot be modified!") 

121 super().__setattr__(key, value) 

122 

123 

124################################################################################ 

125#### RoleORM 

126################################################################################ 

127 

128 

129class RoleORM(PyPermissionORM): 

130 __tablename__ = "pp_role_table" 

131 id: Mapped[str] = mapped_column(String, primary_key=True) 

132 

133 

134################################################################################ 

135#### HierarchyORM 

136################################################################################ 

137 

138 

139class HierarchyORM(PyPermissionORM): 

140 __tablename__ = "pp_hierarchy_table" 

141 parent_role_id: Mapped[str] = mapped_column( 

142 String, ForeignKey("pp_role_table.id", ondelete="CASCADE"), primary_key=True 

143 ) 

144 child_role_id: Mapped[str] = mapped_column( 

145 String, ForeignKey("pp_role_table.id", ondelete="CASCADE"), primary_key=True 

146 ) 

147 

148 

149################################################################################ 

150#### SubjectORM 

151################################################################################ 

152 

153 

154class SubjectORM(PyPermissionORM): 

155 __tablename__ = "pp_subject_table" 

156 id: Mapped[str] = mapped_column(String, primary_key=True) 

157 

158 

159################################################################################ 

160#### MemberORM 

161################################################################################ 

162 

163 

164class MemberORM(PyPermissionORM): 

165 __tablename__ = "pp_member_table" 

166 role_id: Mapped[str] = mapped_column( 

167 String, ForeignKey("pp_role_table.id", ondelete="CASCADE"), primary_key=True 

168 ) 

169 subject_id: Mapped[str] = mapped_column( 

170 String, ForeignKey("pp_subject_table.id", ondelete="CASCADE"), primary_key=True 

171 ) 

172 

173 

174################################################################################ 

175#### PolicyORM 

176################################################################################ 

177 

178 

179class PolicyORM(PyPermissionORM): 

180 __tablename__ = "pp_policy_table" 

181 role_id: Mapped[str] = mapped_column( 

182 String, ForeignKey("pp_role_table.id", ondelete="CASCADE"), primary_key=True 

183 ) 

184 resource_type: Mapped[str] = mapped_column(String, primary_key=True) 

185 resource_id: Mapped[str] = mapped_column(String, primary_key=True) 

186 action: Mapped[str] = mapped_column(String, primary_key=True)