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

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.exc import PyPermissionError 

8 

9 

10class BaseORM(DeclarativeBase): ... 

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 def __init__(self, *, resource_type: str, resource_id: str, action: str) -> None: 

37 """ 

38 Initialize the Permission. 

39 

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

53 

54 self.resource_type = resource_type 

55 self.resource_id = resource_id 

56 self.action = action 

57 

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

62 

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

64 if not isinstance(other, Permission): 

65 return False 

66 

67 return ( 

68 self.resource_type == other.resource_type 

69 and self.resource_id == other.resource_id 

70 and self.action == other.action 

71 ) 

72 

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

74 return not self.__eq__(other) 

75 

76 

77class Policy: 

78 """ 

79 Represents a Role paired with a Permission. 

80 

81 Attributes 

82 ---------- 

83 role : str 

84 The target RoleID. 

85 permission : Permission 

86 The target Permission. 

87 """ 

88 

89 role: str 

90 permission: Permission 

91 

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

93 """ 

94 Initialize the Policy. 

95 

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 

107 

108 def __str__(self) -> str: 

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

110 

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

112 if not isinstance(other, Policy): 

113 return False 

114 

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

116 

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

118 return not self.__eq__(other) 

119 

120 

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) 

126 

127 

128################################################################################ 

129#### RoleORM 

130################################################################################ 

131 

132 

133class RoleORM(BaseORM): 

134 __tablename__ = "pp_role_table" 

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

136 

137 

138################################################################################ 

139#### HierarchyORM 

140################################################################################ 

141 

142 

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 ) 

151 

152 

153################################################################################ 

154#### SubjectORM 

155################################################################################ 

156 

157 

158class SubjectORM(BaseORM): 

159 __tablename__ = "pp_subject_table" 

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

161 

162 

163################################################################################ 

164#### MemberORM 

165################################################################################ 

166 

167 

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 ) 

176 

177 

178################################################################################ 

179#### PolicyORM 

180################################################################################ 

181 

182 

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)