GeneralManager 0.20.0__py3-none-any.whl → 0.21.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of GeneralManager might be problematic. Click here for more details.

@@ -1,25 +1,26 @@
1
1
  """Registry of reusable permission checks and their queryset filters."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from typing import Any, Callable, TYPE_CHECKING, TypedDict, Literal
4
6
 
5
7
  if TYPE_CHECKING:
6
- from django.contrib.auth.models import AbstractUser, AnonymousUser
8
+ from django.contrib.auth.models import AbstractBaseUser, AnonymousUser
7
9
  from general_manager.permission.permission_data_manager import (
8
10
  PermissionDataManager,
9
11
  )
10
12
  from general_manager.manager.general_manager import GeneralManager
11
13
  from general_manager.manager.meta import GeneralManagerMeta
12
14
 
13
-
14
15
  type permission_filter = Callable[
15
- [AbstractUser | AnonymousUser, list[str]],
16
- dict[Literal["filter", "exclude"], dict[str, str]] | None,
16
+ [AbstractBaseUser | AnonymousUser, list[str]],
17
+ dict[Literal["filter", "exclude"], dict[str, Any]] | None,
17
18
  ]
18
19
 
19
20
  type permission_method = Callable[
20
21
  [
21
22
  PermissionDataManager | GeneralManager | GeneralManagerMeta,
22
- AbstractUser | AnonymousUser,
23
+ AbstractBaseUser | AnonymousUser,
23
24
  list[str],
24
25
  ],
25
26
  bool,
@@ -33,36 +34,205 @@ class PermissionDict(TypedDict):
33
34
  permission_filter: permission_filter
34
35
 
35
36
 
36
- permission_functions: dict[str, PermissionDict] = {
37
- "public": {
38
- "permission_method": lambda _instance, _user, _config: True,
39
- "permission_filter": lambda _user, _config: None,
40
- },
41
- "matches": {
42
- "permission_method": lambda instance, _user, config: getattr(
43
- instance, config[0]
44
- )
45
- == config[1],
46
- "permission_filter": lambda _user, config: {"filter": {config[0]: config[1]}},
47
- },
48
- "ends_with": {
49
- "permission_method": lambda instance, _user, config: getattr(
50
- instance, config[0]
51
- ).endswith(config[1]),
52
- "permission_filter": lambda _user, config: {
53
- "filter": {f"{config[0]}__endswith": config[1]}
54
- },
55
- },
56
- "isAdmin": {
57
- "permission_method": lambda _instance, user, _config: user.is_staff,
58
- "permission_filter": lambda _user, _config: None,
59
- },
60
- "isSelf": {
61
- "permission_method": lambda instance, user, _config: instance.creator == user, # type: ignore
62
- "permission_filter": lambda user, _config: {"filter": {"creator_id": user.id}}, # type: ignore
63
- },
64
- "isAuthenticated": {
65
- "permission_method": lambda _instance, user, _config: user.is_authenticated,
66
- "permission_filter": lambda _user, _config: None,
67
- },
68
- }
37
+ permission_functions: dict[str, PermissionDict] = {}
38
+
39
+ __all__ = ["permission_functions", "register_permission"]
40
+
41
+ _PERMISSION_ALREADY_REGISTERED_MESSAGE = "Permission function is already registered."
42
+
43
+
44
+ def _default_permission_filter(
45
+ _user: AbstractBaseUser | AnonymousUser, _config: list[str]
46
+ ) -> dict[Literal["filter", "exclude"], dict[str, Any]] | None:
47
+ return None
48
+
49
+
50
+ def register_permission(
51
+ name: str, *, permission_filter: permission_filter | None = None
52
+ ) -> Callable[[permission_method], permission_method]:
53
+ """
54
+ Register a permission function in the global registry.
55
+
56
+ Parameters:
57
+ name (str): Identifier used in permission expressions.
58
+ permission_filter (permission_filter | None): Optional callable that
59
+ provides queryset filters corresponding to the permission.
60
+
61
+ Returns:
62
+ Callable[[permission_method], permission_method]: Decorator that
63
+ registers the decorated function and returns it unchanged.
64
+
65
+ Raises:
66
+ ValueError: If another permission with the same name has already been
67
+ registered.
68
+ """
69
+
70
+ def decorator(func: permission_method) -> permission_method:
71
+ if name in permission_functions:
72
+ raise ValueError(_PERMISSION_ALREADY_REGISTERED_MESSAGE)
73
+ filter_callable = permission_filter or _default_permission_filter
74
+ permission_functions[name] = {
75
+ "permission_method": func,
76
+ "permission_filter": filter_callable,
77
+ }
78
+ return func
79
+
80
+ return decorator
81
+
82
+
83
+ @register_permission("public")
84
+ def _permission_public(
85
+ _instance: PermissionDataManager | GeneralManager | GeneralManagerMeta,
86
+ _user: AbstractBaseUser | AnonymousUser,
87
+ _config: list[str],
88
+ ) -> bool:
89
+ return True
90
+
91
+
92
+ def _matches_permission_filter(
93
+ _user: AbstractBaseUser | AnonymousUser, config: list[str]
94
+ ) -> dict[Literal["filter", "exclude"], dict[str, Any]] | None:
95
+ if len(config) < 2:
96
+ return None
97
+ return {"filter": {config[0]: config[1]}}
98
+
99
+
100
+ @register_permission("matches", permission_filter=_matches_permission_filter)
101
+ def _permission_matches(
102
+ instance: PermissionDataManager | GeneralManager | GeneralManagerMeta,
103
+ _user: AbstractBaseUser | AnonymousUser,
104
+ config: list[str],
105
+ ) -> bool:
106
+ return bool(
107
+ len(config) >= 2 and str(getattr(instance, config[0], None)) == config[1]
108
+ )
109
+
110
+
111
+ @register_permission("isAdmin")
112
+ def _permission_is_admin(
113
+ _instance: PermissionDataManager | GeneralManager | GeneralManagerMeta,
114
+ user: AbstractBaseUser | AnonymousUser,
115
+ _config: list[str],
116
+ ) -> bool:
117
+ return bool(getattr(user, "is_staff", False))
118
+
119
+
120
+ def _is_self_permission_filter(
121
+ user: AbstractBaseUser | AnonymousUser,
122
+ _config: list[str],
123
+ ) -> dict[Literal["filter", "exclude"], dict[str, Any]] | None:
124
+ return {"filter": {"creator_id": getattr(user, "id", None)}}
125
+
126
+
127
+ @register_permission("isSelf", permission_filter=_is_self_permission_filter)
128
+ def _permission_is_self(
129
+ instance: PermissionDataManager | GeneralManager | GeneralManagerMeta,
130
+ user: AbstractBaseUser | AnonymousUser,
131
+ _config: list[str],
132
+ ) -> bool:
133
+ return bool(instance.creator == user) # type: ignore[union-attr]
134
+
135
+
136
+ @register_permission("isAuthenticated")
137
+ def _permission_is_authenticated(
138
+ _instance: PermissionDataManager | GeneralManager | GeneralManagerMeta,
139
+ user: AbstractBaseUser | AnonymousUser,
140
+ _config: list[str],
141
+ ) -> bool:
142
+ return bool(getattr(user, "is_authenticated", False))
143
+
144
+
145
+ @register_permission("isActive")
146
+ def _permission_is_active(
147
+ _instance: PermissionDataManager | GeneralManager | GeneralManagerMeta,
148
+ user: AbstractBaseUser | AnonymousUser,
149
+ _config: list[str],
150
+ ) -> bool:
151
+ return bool(getattr(user, "is_active", False))
152
+
153
+
154
+ @register_permission("hasPermission")
155
+ def _permission_has_permission(
156
+ _instance: PermissionDataManager | GeneralManager | GeneralManagerMeta,
157
+ user: AbstractBaseUser | AnonymousUser,
158
+ config: list[str],
159
+ ) -> bool:
160
+ if not config:
161
+ return False
162
+ has_perm = getattr(user, "has_perm", None)
163
+ if not callable(has_perm):
164
+ return False
165
+ return bool(has_perm(config[0]))
166
+
167
+
168
+ @register_permission("inGroup")
169
+ def _permission_in_group(
170
+ _instance: PermissionDataManager | GeneralManager | GeneralManagerMeta,
171
+ user: AbstractBaseUser | AnonymousUser,
172
+ config: list[str],
173
+ ) -> bool:
174
+ if not config:
175
+ return False
176
+ group_manager = getattr(user, "groups", None)
177
+ if group_manager is None or not hasattr(group_manager, "filter"):
178
+ return False
179
+ filtered = group_manager.filter(name=config[0]) # type: ignore[attr-defined]
180
+ return bool(hasattr(filtered, "exists") and filtered.exists()) # type: ignore[call-arg]
181
+
182
+
183
+ def _related_user_field_permission_filter(
184
+ user: AbstractBaseUser | AnonymousUser, config: list[str]
185
+ ) -> dict[Literal["filter", "exclude"], dict[str, Any]] | None:
186
+ if not config:
187
+ return None
188
+ user_id = getattr(user, "id", None)
189
+ if user_id is None:
190
+ return None
191
+ return {"filter": {f"{config[0]}_id": user_id}}
192
+
193
+
194
+ @register_permission(
195
+ "relatedUserField",
196
+ permission_filter=_related_user_field_permission_filter,
197
+ )
198
+ def _permission_related_user_field(
199
+ instance: PermissionDataManager | GeneralManager | GeneralManagerMeta,
200
+ user: AbstractBaseUser | AnonymousUser,
201
+ config: list[str],
202
+ ) -> bool:
203
+ if not config:
204
+ return False
205
+ related_object = getattr(instance, config[0], None)
206
+ return bool(related_object == user) # type: ignore[arg-type]
207
+
208
+
209
+ def _many_to_many_contains_user_permission_filter(
210
+ user: AbstractBaseUser | AnonymousUser, config: list[str]
211
+ ) -> dict[Literal["filter", "exclude"], dict[str, Any]] | None:
212
+ if not config:
213
+ return None
214
+ user_id = getattr(user, "id", None)
215
+ if user_id is None:
216
+ return None
217
+ return {"filter": {f"{config[0]}__id": user_id}}
218
+
219
+
220
+ @register_permission(
221
+ "manyToManyContainsUser",
222
+ permission_filter=_many_to_many_contains_user_permission_filter,
223
+ )
224
+ def _permission_many_to_many_contains_user(
225
+ instance: PermissionDataManager | GeneralManager | GeneralManagerMeta,
226
+ user: AbstractBaseUser | AnonymousUser,
227
+ config: list[str],
228
+ ) -> bool:
229
+ if not config:
230
+ return False
231
+ related_manager = getattr(instance, config[0], None)
232
+ if related_manager is None or not hasattr(related_manager, "filter"):
233
+ return False
234
+ user_pk = getattr(user, "pk", None)
235
+ if user_pk is None:
236
+ return False
237
+ filtered = related_manager.filter(pk=user_pk) # type: ignore[attr-defined]
238
+ return bool(hasattr(filtered, "exists") and filtered.exists()) # type: ignore[call-arg]
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
  from typing import Callable, Optional, TypeVar, Generic, cast
5
- from django.contrib.auth.models import AbstractUser
6
5
 
7
6
  from general_manager.manager.general_manager import GeneralManager
8
7
 
@@ -4,7 +4,7 @@ from general_manager.permission.permission_checks import (
4
4
  permission_functions,
5
5
  )
6
6
  from general_manager.permission.permission_data_manager import PermissionDataManager
7
- from django.contrib.auth.models import AbstractUser, AnonymousUser
7
+ from django.contrib.auth.models import AbstractBaseUser, AnonymousUser
8
8
 
9
9
  from general_manager.manager.general_manager import GeneralManager
10
10
  from general_manager.manager.meta import GeneralManagerMeta
@@ -26,7 +26,7 @@ class PermissionNotFoundError(ValueError):
26
26
  def validate_permission_string(
27
27
  permission: str,
28
28
  data: PermissionDataManager | GeneralManager | GeneralManagerMeta,
29
- request_user: AbstractUser | AnonymousUser,
29
+ request_user: AbstractBaseUser | AnonymousUser,
30
30
  ) -> bool:
31
31
  """
32
32
  Evaluate a compound permission expression joined by '&' operators.
@@ -34,7 +34,7 @@ def validate_permission_string(
34
34
  Parameters:
35
35
  permission (str): Permission expression where sub-permissions are joined with '&'. Individual sub-permissions may include ':'-separated configuration parts (for example, "isAuthenticated&admin:level").
36
36
  data (PermissionDataManager | GeneralManager | GeneralManagerMeta): Object passed to each permission function.
37
- request_user (AbstractUser | AnonymousUser): User for whom permissions are evaluated.
37
+ request_user (AbstractBaseUser | AnonymousUser): User for whom permissions are evaluated.
38
38
 
39
39
  Returns:
40
40
  `true` if every sub-permission evaluates to True, `false` otherwise.
@@ -35,6 +35,14 @@ GENERAL_MANAGER_EXPORTS: LazyExportMap = {
35
35
  "general_manager.permission.manager_based_permission",
36
36
  "ManagerBasedPermission",
37
37
  ),
38
+ "register_permission": (
39
+ "general_manager.permission.permission_checks",
40
+ "register_permission",
41
+ ),
42
+ "permission_functions": (
43
+ "general_manager.permission.permission_checks",
44
+ "permission_functions",
45
+ ),
38
46
  "Rule": ("general_manager.rule.rule", "Rule"),
39
47
  }
40
48
 
@@ -124,6 +132,35 @@ PERMISSION_EXPORTS: LazyExportMap = {
124
132
  "general_manager.permission.mutation_permission",
125
133
  "MutationPermission",
126
134
  ),
135
+ "register_permission": (
136
+ "general_manager.permission.permission_checks",
137
+ "register_permission",
138
+ ),
139
+ "permission_functions": (
140
+ "general_manager.permission.permission_checks",
141
+ "permission_functions",
142
+ ),
143
+ "configure_audit_logger": (
144
+ "general_manager.permission.audit",
145
+ "configure_audit_logger",
146
+ ),
147
+ "configure_audit_logger_from_settings": (
148
+ "general_manager.permission.audit",
149
+ "configure_audit_logger_from_settings",
150
+ ),
151
+ "FileAuditLogger": (
152
+ "general_manager.permission.audit",
153
+ "FileAuditLogger",
154
+ ),
155
+ "DatabaseAuditLogger": (
156
+ "general_manager.permission.audit",
157
+ "DatabaseAuditLogger",
158
+ ),
159
+ "PermissionAuditEvent": (
160
+ "general_manager.permission.audit",
161
+ "PermissionAuditEvent",
162
+ ),
163
+ "AuditLogger": ("general_manager.permission.audit", "AuditLogger"),
127
164
  }
128
165
 
129
166
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.20.0
3
+ Version: 0.21.0
4
4
  Summary: Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching.
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License: MIT License
@@ -1,18 +1,18 @@
1
1
  general_manager/__init__.py,sha256=Xy4_fFjChIYtlBPDv7K3JEHOulq9_IFXkCc4CElS32Q,1035
2
- general_manager/apps.py,sha256=bz_A2IfgVba2b8lZC5aeQqe40IH7GaavpTEg828Ltm4,27343
2
+ general_manager/apps.py,sha256=1wzMof8IKn_Zw4QyD3WwD7zVedAvsZKhCwsa0ajx6U8,27480
3
3
  general_manager/logging.py,sha256=bL2dEdNnxo74qRmhOCG06lbPtVCOozfF3GS133T9HH8,4250
4
- general_manager/public_api_registry.py,sha256=mDpKIE3fmFAMTbQnQdLAZGc_CKNNA6p22Jrql9R6Y4w,7817
4
+ general_manager/public_api_registry.py,sha256=zAYG6UPfbv5NDSM59kvLxUZBJce5NF8lzEB4op8LnPM,8974
5
5
  general_manager/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  general_manager/_types/__init__.py,sha256=RmS0Ok-1-CwWOZL_socLWksEk9MfsshL9eQx879I4mU,60
7
7
  general_manager/_types/api.py,sha256=i5fi7O3C3frI74oqPT-tAoztDplgNDOShyvPNxhw7CY,496
8
8
  general_manager/_types/bucket.py,sha256=DZ3GC-1iR4RiNBKydsMhp_R4GTaYMfsonwe7MCADVP4,435
9
9
  general_manager/_types/cache.py,sha256=jMbIEXDq1pUvVxTN8hvD8rSH7QRjE3ewrMx2gEhYP3w,660
10
10
  general_manager/_types/factory.py,sha256=cPG_td05KCWv2j6_h_vYCKETPol3k_nXkEMWCcUdNHM,1726
11
- general_manager/_types/general_manager.py,sha256=Izs47-pOnYJqPmMF7m5DRodSISEztXKSxtBGIHgN8sA,1031
11
+ general_manager/_types/general_manager.py,sha256=m-VA1968DYEFiunWyA72fUI1EicMLKarYJ5bkSyKJ7I,1241
12
12
  general_manager/_types/interface.py,sha256=JlPn3G3ompn1PRfFQfsrIJv2Pf0I3n1RL2ki5_fDX54,605
13
13
  general_manager/_types/manager.py,sha256=rXM6NrQaPdfE9CQoRV_jhbI3SK23rvdpvyBpybVgWi8,506
14
14
  general_manager/_types/measurement.py,sha256=Unkpf3H7TGaUKfkjmFDR7QWBU4lPp5I2A4GlK8pps-Y,443
15
- general_manager/_types/permission.py,sha256=HJBEOIDYbkYSZkEbT4_6qRDyUk2Ek153K_RchmeYd5I,416
15
+ general_manager/_types/permission.py,sha256=olbtZNPkYoI1mbtzs6FDuVlKyGiP6VfGwufe8G56CQQ,1196
16
16
  general_manager/_types/rule.py,sha256=aNbx3jlVinAYQGJlmFwW8rOwTjf19F6khq5xbGTdD3U,238
17
17
  general_manager/_types/utils.py,sha256=8VyD6EKkcRIzdVs6FANP5Ri71eT4BT4c3dTUeyjDr40,1043
18
18
  general_manager/api/__init__.py,sha256=ChV1UPCUjW1Lci0BajmXVtE-3C-WLQTvjO058BMu1Bs,1020
@@ -41,7 +41,7 @@ general_manager/interface/calculation_interface.py,sha256=dsu2pjFgaOv-Kif5YbrsbE
41
41
  general_manager/interface/database_based_interface.py,sha256=1L0Q22nEWiO_i--3AtuseAytiXMEkIUF4HL5rTB0s0M,25623
42
42
  general_manager/interface/database_interface.py,sha256=ALFQKl7jXNQMHegmUkNKyDGJruMmAmprDiiyYsfZZ_A,11825
43
43
  general_manager/interface/models.py,sha256=MWCgr-Qzauu7jq-15_iEdI0C9B99MsRONV9jUcjkkMU,3699
44
- general_manager/interface/read_only_interface.py,sha256=pOi2KNE_eyAMDZMsphaMfsXWa8sy4A-_xVzAV7FZVDM,15177
44
+ general_manager/interface/read_only_interface.py,sha256=w1XrTeOyf3T7lDfC7ponAMhfNw5LTM46lTOEsocE244,16000
45
45
  general_manager/manager/__init__.py,sha256=Dbka86jtcS2NIxKXA0mohqnLZwLb6NDRX-HeD1_R6UQ,933
46
46
  general_manager/manager/general_manager.py,sha256=GperYpkoTg_CZlwYBsqGhnaMFENR6jyxanKYdHsvZjg,12083
47
47
  general_manager/manager/group_manager.py,sha256=EhiW2cEnr7J4Ft-FBfuW4hCT5mYg_RCD_-mYF4an9dw,7320
@@ -51,13 +51,13 @@ general_manager/measurement/__init__.py,sha256=38-6kTERbw7EeOQAw3eZ9DDEu6hwwqkGJ
51
51
  general_manager/measurement/measurement.py,sha256=ArEnntLu3ffHID71uFbXwiB5e5tpsBT2_TbBPJfCWJI,26070
52
52
  general_manager/measurement/measurement_field.py,sha256=zQbyLZT7E9Lx4f3u_wQ-rEoLPqH0FWhfWyOd1BaW--M,13950
53
53
  general_manager/permission/__init__.py,sha256=s4Ka11uYBrymM5O-n5kIxOjwUprZ394jTuzGf5prO_8,981
54
- general_manager/permission/base_permission.py,sha256=IKi5n8t1sKUP_F2eZnzTC8lg7HI8OVo5p1jxCBpyCgw,11961
55
- general_manager/permission/file_based_permission.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
- general_manager/permission/manager_based_permission.py,sha256=OdDGKwutkHTT13bhSeGGycd3xr7wGhuRXsYym3Z9j2A,10613
57
- general_manager/permission/mutation_permission.py,sha256=5JV6pluIFPGzJPQ_pr-44zwFg_TgzNwDTHcdpwHettU,4606
58
- general_manager/permission/permission_checks.py,sha256=4OVQz0QVnmHN-KPge4pz2Zm_oMxo2PUytW7JoDoHjL0,2297
59
- general_manager/permission/permission_data_manager.py,sha256=vNWbPrcw1NUZ-oTRjc3PME479ubbOqxlKx020GY5wzQ,3905
60
- general_manager/permission/utils.py,sha256=yojrcF1kD6KjKugrGyiaf8XOyS_tUA4yB5_RideKJeg,2905
54
+ general_manager/permission/audit.py,sha256=mOT4Dj5LnAMbHWXKf6JCM01L-jd7GsMSmAIBTR1hTS0,12802
55
+ general_manager/permission/base_permission.py,sha256=69mgDS7iuxEA1V8lDanDBAH20BY3yc4vp8E0sjrqzmE,16045
56
+ general_manager/permission/manager_based_permission.py,sha256=SfqEvC3RbXjWxxm6JOgEJI9li_6uiB4-avihhgfjynw,11902
57
+ general_manager/permission/mutation_permission.py,sha256=QG-cH1T8anIS4DYRdUHwHOilpop9UzkwMUTiak-sG8s,6871
58
+ general_manager/permission/permission_checks.py,sha256=S0agZzG-tdZWLKWAF_1jdrvsLVbhVk1k1MrBdqNYc8o,7710
59
+ general_manager/permission/permission_data_manager.py,sha256=aqmj5rpslOgCT0GdXnsBtjpFoRerFOa3xG84Jus17b8,3853
60
+ general_manager/permission/utils.py,sha256=Q3E9aq3jUOhBXOdLwWSQsXgNMuV6Bp7R84xM_JLB6Y0,2917
61
61
  general_manager/rule/__init__.py,sha256=dFQLJNr0qpkpuzuBbMY-74jz_Y033YyB9X_OfeJYhSs,1026
62
62
  general_manager/rule/handler.py,sha256=qSBXmXvl7fRgjxUo4wyUOzdZm14a8Umd2Jec4zPXJQc,16309
63
63
  general_manager/rule/rule.py,sha256=41VXFTSWkBEOj8045vZTezm38VEGimjrk22J4YRl144,22691
@@ -71,8 +71,8 @@ general_manager/utils/none_to_zero.py,sha256=Z2KVXBKCz02V2ZBs-7tIs_32n7YDxiXyood
71
71
  general_manager/utils/path_mapping.py,sha256=f3sd3OFh9aJgyF-ayqr22mHnCo-uoIXqio5DRQmziGM,9931
72
72
  general_manager/utils/public_api.py,sha256=bas4SKchJUg2dI2SLRQwkF_CoXJKKwkzQx_JOjMJ2VY,3272
73
73
  general_manager/utils/testing.py,sha256=_V4Y5SXvu-ocoq-AntPFPd140igclUJUT2xVXxhRrSs,14954
74
- generalmanager-0.20.0.dist-info/licenses/LICENSE,sha256=OcRwUgM4iiESN1oNELgyuwuah39XX-EPZiPDDHndNVI,1070
75
- generalmanager-0.20.0.dist-info/METADATA,sha256=dpVHVlqT7Mp593hrJnPwNztTzT2E_xf10n-91hrMARE,8266
76
- generalmanager-0.20.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
77
- generalmanager-0.20.0.dist-info/top_level.txt,sha256=sTDtExP9ga-YP3h3h42yivUY-A2Q23C2nw6LNKOho4I,16
78
- generalmanager-0.20.0.dist-info/RECORD,,
74
+ generalmanager-0.21.0.dist-info/licenses/LICENSE,sha256=OcRwUgM4iiESN1oNELgyuwuah39XX-EPZiPDDHndNVI,1070
75
+ generalmanager-0.21.0.dist-info/METADATA,sha256=ubnYLTNd5AzlLYju1ykPewMtGIMF-e7lVKl4Hqo2Os8,8266
76
+ generalmanager-0.21.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
77
+ generalmanager-0.21.0.dist-info/top_level.txt,sha256=sTDtExP9ga-YP3h3h42yivUY-A2Q23C2nw6LNKOho4I,16
78
+ generalmanager-0.21.0.dist-info/RECORD,,
File without changes