cardo-python-utils 0.5.dev46__py3-none-any.whl → 0.5.dev48__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cardo-python-utils
3
- Version: 0.5.dev46
3
+ Version: 0.5.dev48
4
4
  Summary: Python library enhanced with a wide range of functions for different scenarios.
5
5
  Author-email: CardoAI <hello@cardoai.com>
6
6
  License: MIT
@@ -1,4 +1,4 @@
1
- cardo_python_utils-0.5.dev46.dist-info/licenses/LICENSE,sha256=N-YtxDy8n5A1Mo7JKKItNIlboiK_pMOZ48ojx76jo3g,1046
1
+ cardo_python_utils-0.5.dev48.dist-info/licenses/LICENSE,sha256=N-YtxDy8n5A1Mo7JKKItNIlboiK_pMOZ48ojx76jo3g,1046
2
2
  python_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  python_utils/choices.py,sha256=_sLNkSnQqhg55gGKNRsOQCJ75W6gnz8J8Q00528MEYk,2548
4
4
  python_utils/data_structures.py,sha256=ZqkZYPy20zyGYOVhwb9qst4vF_P7X2A9z5E36rMUC6I,16820
@@ -11,10 +11,10 @@ python_utils/math.py,sha256=p_v8a9nVSe9426nR8H_SM8hOQrkzESVpCnn3gntw7TA,5603
11
11
  python_utils/text.py,sha256=pw9CZeM_Lcw-6k4GyR-4D1Wix8A7F_V1u1IIZTIazW4,3792
12
12
  python_utils/time.py,sha256=7Wei3uJ02Bk-BFRf-e1axoG418XQOhrXPvTwNZgTdnw,9614
13
13
  python_utils/types_hinting.py,sha256=QVWzmXRgNxhvln14tEX_FbQYryuVYhjWJ0dVOnlF6G4,120
14
- python_utils/django/README.md,sha256=9MgmJQhl13hzp34j_CTsq3BzEWP8OPEcrYZW2ae4zF8,6062
14
+ python_utils/django/README.md,sha256=J6zy05R4DC43gYFF9_fYz7T9zxDmFn7IkYh5OByePRY,6213
15
15
  python_utils/django/__init__.py,sha256=uXyqF-_5gZAlSIKoQkUTedAeBjnUHqh6lR6SzA1DEOM,64
16
16
  python_utils/django/apps.py,sha256=vH2Ov8XgavTGKFLSjbH1kvuG7RWQCjeJepw6BSp2o3E,126
17
- python_utils/django/oidc_settings.py,sha256=EQlZeJrYy0c5X2Q3jmr4XEUm6HOUZqeKPFeM1HwKHbM,4330
17
+ python_utils/django/oidc_settings.py,sha256=JqF-qOfW23JhmmVciN1B7ZV-KI7qrdn5VigTE7E2k_0,4367
18
18
  python_utils/django/settings.py,sha256=dbu4QPvG8N_z7P8eisEgI4qNwy2T8ZPNDkx9FoK4FEs,624
19
19
  python_utils/django/tenant_context.py,sha256=9LN15__PGhajy7raK382vVx8r5k90VUwUZJS4ylW0yA,3231
20
20
  python_utils/django/admin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -43,14 +43,15 @@ python_utils/django/management/commands/tenant_aware_command.py,sha256=MBI7N0BEH
43
43
  python_utils/django/middleware/__init__.py,sha256=GyhuzaOtaXW13iB9GL0XF7OvEEzKoJzorJzYX597idA,117
44
44
  python_utils/django/middleware/tenant_aware_http_middleware.py,sha256=W8U3-nR90b8m_wz5xDJTuImUrPEXvESzYh3VITYMIJU,3825
45
45
  python_utils/django/middleware/tenant_aware_websocket_middleware.py,sha256=k6bn4pS4uZcTPafF7Yz_8VAj0vchEfJSXM4Bxb-Od-k,4100
46
- python_utils/django/migrations/0001_initial.py,sha256=fLP4gJgVZRx5heFqNTVTf0h2dHVeIIyOxQn37kNpfUY,4340
47
- python_utils/django/migrations/0001_initial_squashed_0005_alter_userrole_id.py,sha256=JZsoon-pZLwIzAy-3SnGzkVxa9eqPkP9QBSQsqPiJVo,6133
46
+ python_utils/django/migrations/0001_initial.py,sha256=j-y64JqkDv2A1o2YzJB9NE41KFOOiVmQn0-fv9A98WU,4841
47
+ python_utils/django/migrations/0001_initial_squashed_0005_alter_userrole_id.py,sha256=PveIcroiawK2Mr4Vh0lsj5bjJfyaA98wdSfn_g53WZI,5803
48
48
  python_utils/django/migrations/0002_auto_20220120_1617.py,sha256=TOTmFU8wejKOX-bjq4D1s3B-QXTEub_FOX4STWAcJ4o,378
49
49
  python_utils/django/migrations/0003_auto_20220513_1025.py,sha256=j4usfLINqTdSHSvWBnEUd4cxvHXqL8rRIw_FFna1Hn4,390
50
50
  python_utils/django/migrations/0004_auto_20220817_1526.py,sha256=nS_nnQJabst0Eigt4Ry05RrG_lSM8WdWhRZ0iIX4tZY,579
51
51
  python_utils/django/migrations/0005_alter_userrole_id.py,sha256=fWbqJw8ryCBtnJvqM-O_RDpQi6GFJHnwfyZOs0EKH3k,446
52
52
  python_utils/django/migrations/0006_userrole_organization_and_more.py,sha256=EJE6wqmUS4qbcCwhjU11ZJXcRDDNZ3rjPqE8XbqZt8E,1270
53
53
  python_utils/django/migrations/0007_user_demo.py,sha256=sqMrC1cxh0sDVixUeC9CQsWi9EZVbWrCat1AUfCQ6nA,396
54
+ python_utils/django/migrations/0008_delete_userrole.py,sha256=cJG8QsMe_iFSJ-a_uv-B_GrDReAmRjFTWWK7OlxvnjQ,640
54
55
  python_utils/django/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
56
  python_utils/django/models/__init__.py,sha256=l9ueTdsKVif6Ufh5a4ikcgFWncsnPbMtfbjgg5G660o,43
56
57
  python_utils/django/models/user.py,sha256=mPZBm9uIif1aCWkoYSqwaP7MQBhOmBLL55CfXasL74Q,801
@@ -61,7 +62,7 @@ python_utils/django/storage/__init__.py,sha256=mNn2YmD7pkXhBLHMM1444BLsCMq78YdYx
61
62
  python_utils/django/storage/tenant_aware_storage.py,sha256=5dDes6xLv7_R8hIBbFIzRvPL7HL9K_RM-G6LI8qUSxM,2550
62
63
  python_utils/django/tests/__init__.py,sha256=Nkt0a7LEHyjLvuEBZ7113VjjAWJlyZlMy-H-JZ5tNcs,252
63
64
  python_utils/django/tests/conftest.py,sha256=KozXmXUWVcDLbkVAb7Aq4sDydGLh2YZkbRa4tkA8Z6U,3167
64
- cardo_python_utils-0.5.dev46.dist-info/METADATA,sha256=1XWHcADAgQe3WKw1I9NJGI4WjFou8wGSk5jUOTC-H7c,3007
65
- cardo_python_utils-0.5.dev46.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
66
- cardo_python_utils-0.5.dev46.dist-info/top_level.txt,sha256=zAx6OfEsjJs8BEW3okSiG_j9gpkI69xWShzum6oBgKI,13
67
- cardo_python_utils-0.5.dev46.dist-info/RECORD,,
65
+ cardo_python_utils-0.5.dev48.dist-info/METADATA,sha256=EimWDluEjRNS_-U--n2S4C4jBu3Vx2nWS6PPnWeo3Ls,3007
66
+ cardo_python_utils-0.5.dev48.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
67
+ cardo_python_utils-0.5.dev48.dist-info/top_level.txt,sha256=zAx6OfEsjJs8BEW3okSiG_j9gpkI69xWShzum6oBgKI,13
68
+ cardo_python_utils-0.5.dev48.dist-info/RECORD,,
@@ -124,6 +124,11 @@ OIDC_AUTHENTICATE_CLASS = "python_utils.django.admin.views.TenantAwareOIDCAuthen
124
124
  LOGIN_REDIRECT_URL = "/admin"
125
125
  SESSION_COOKIE_AGE = 60 * 30 # 30 minutes
126
126
  SESSION_SAVE_EVERY_REQUEST = True # Extend session on each request
127
+
128
+ # If using django-easy-audit
129
+
130
+ from python_utils.django.db.alias import DynamicDatabaseAlias
131
+ DJANGO_EASY_AUDIT_DATABASE_ALIAS = DynamicDatabaseAlias()
127
132
  ```
128
133
 
129
134
  ## urls.py file
@@ -11,63 +11,105 @@ class Migration(migrations.Migration):
11
11
  initial = True
12
12
 
13
13
  dependencies = [
14
- ('auth', '0012_alter_user_first_name_max_length'),
14
+ ("auth", "0012_alter_user_first_name_max_length"),
15
15
  ]
16
16
 
17
17
  operations = [
18
18
  migrations.CreateModel(
19
- name='User',
19
+ name="User",
20
20
  fields=[
21
- ('password', models.CharField(max_length=128, verbose_name='password')),
22
- ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
23
- ('is_superuser', models.BooleanField(default=False,
24
- help_text='Designates that this user has all permissions without explicitly assigning them.',
25
- verbose_name='superuser status')),
26
- ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'},
27
- help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.',
28
- max_length=150, unique=True,
29
- validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
30
- verbose_name='username')),
31
- ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
32
- ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
33
- ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
34
- ('is_staff', models.BooleanField(default=False,
35
- help_text='Designates whether the user can log into this admin site.',
36
- verbose_name='staff status')),
37
- ('is_active', models.BooleanField(default=True,
38
- help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.',
39
- verbose_name='active')),
40
- ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
41
- ('idp_user_id', models.BigIntegerField(primary_key=True, serialize=False)),
42
- ('groups', models.ManyToManyField(blank=True,
43
- help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.',
44
- related_name='user_set', related_query_name='user', to='auth.Group',
45
- verbose_name='groups')),
46
- ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.',
47
- related_name='user_set', related_query_name='user',
48
- to='auth.Permission', verbose_name='user permissions')),
21
+ ("password", models.CharField(max_length=128, verbose_name="password")),
22
+ ("last_login", models.DateTimeField(blank=True, null=True, verbose_name="last login")),
23
+ (
24
+ "is_superuser",
25
+ models.BooleanField(
26
+ default=False,
27
+ help_text="Designates that this user has all permissions without explicitly assigning them.",
28
+ verbose_name="superuser status",
29
+ ),
30
+ ),
31
+ (
32
+ "username",
33
+ models.CharField(
34
+ error_messages={"unique": "A user with that username already exists."},
35
+ help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
36
+ max_length=150,
37
+ unique=True,
38
+ validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
39
+ verbose_name="username",
40
+ ),
41
+ ),
42
+ ("first_name", models.CharField(blank=True, max_length=150, verbose_name="first name")),
43
+ ("last_name", models.CharField(blank=True, max_length=150, verbose_name="last name")),
44
+ ("email", models.EmailField(blank=True, max_length=254, verbose_name="email address")),
45
+ (
46
+ "is_staff",
47
+ models.BooleanField(
48
+ default=False,
49
+ help_text="Designates whether the user can log into this admin site.",
50
+ verbose_name="staff status",
51
+ ),
52
+ ),
53
+ (
54
+ "is_active",
55
+ models.BooleanField(
56
+ default=True,
57
+ help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
58
+ verbose_name="active",
59
+ ),
60
+ ),
61
+ ("date_joined", models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined")),
62
+ ("idp_user_id", models.BigIntegerField(primary_key=True, serialize=False)),
63
+ (
64
+ "groups",
65
+ models.ManyToManyField(
66
+ blank=True,
67
+ help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
68
+ related_name="idp_user_set",
69
+ related_query_name="user",
70
+ to="auth.group",
71
+ verbose_name="groups",
72
+ ),
73
+ ),
74
+ (
75
+ "user_permissions",
76
+ models.ManyToManyField(
77
+ blank=True,
78
+ help_text="Specific permissions for this user.",
79
+ related_name="idp_user_set",
80
+ related_query_name="user",
81
+ to="auth.permission",
82
+ verbose_name="user permissions",
83
+ ),
84
+ ),
49
85
  ],
50
86
  options={
51
- 'verbose_name': 'user',
52
- 'verbose_name_plural': 'users',
53
- 'abstract': False,
87
+ "verbose_name": "user",
88
+ "verbose_name_plural": "users",
89
+ "abstract": False,
54
90
  },
55
91
  managers=[
56
- ('objects', django.contrib.auth.models.UserManager()),
92
+ ("objects", django.contrib.auth.models.UserManager()),
57
93
  ],
58
94
  ),
59
95
  migrations.CreateModel(
60
- name='UserRole',
96
+ name="UserRole",
61
97
  fields=[
62
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
63
- ('role', models.CharField(max_length=125)),
64
- ('app_config', models.JSONField(null=True)),
65
- ('permission_restrictions', models.JSONField(default=dict)),
66
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_roles',
67
- to=settings.AUTH_USER_MODEL)),
98
+ ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
99
+ ("role", models.CharField(max_length=125)),
100
+ ("app_config", models.JSONField(null=True)),
101
+ ("permission_restrictions", models.JSONField(default=dict)),
102
+ (
103
+ "user",
104
+ models.ForeignKey(
105
+ on_delete=django.db.models.deletion.CASCADE,
106
+ related_name="user_roles",
107
+ to=settings.AUTH_USER_MODEL,
108
+ ),
109
+ ),
68
110
  ],
69
111
  options={
70
- 'unique_together': {('user', 'role')},
112
+ "unique_together": {("user", "role")},
71
113
  },
72
114
  ),
73
115
  ]
@@ -28,9 +28,7 @@ class Migration(migrations.Migration):
28
28
  ("password", models.CharField(max_length=128, verbose_name="password")),
29
29
  (
30
30
  "last_login",
31
- models.DateTimeField(
32
- blank=True, null=True, verbose_name="last login"
33
- ),
31
+ models.DateTimeField(blank=True, null=True, verbose_name="last login"),
34
32
  ),
35
33
  (
36
34
  "is_superuser",
@@ -43,35 +41,25 @@ class Migration(migrations.Migration):
43
41
  (
44
42
  "username",
45
43
  models.CharField(
46
- error_messages={
47
- "unique": "A user with that username already exists."
48
- },
44
+ error_messages={"unique": "A user with that username already exists."},
49
45
  help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
50
46
  max_length=150,
51
47
  unique=True,
52
- validators=[
53
- django.contrib.auth.validators.UnicodeUsernameValidator()
54
- ],
48
+ validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
55
49
  verbose_name="username",
56
50
  ),
57
51
  ),
58
52
  (
59
53
  "first_name",
60
- models.CharField(
61
- blank=True, max_length=150, verbose_name="first name"
62
- ),
54
+ models.CharField(blank=True, max_length=150, verbose_name="first name"),
63
55
  ),
64
56
  (
65
57
  "last_name",
66
- models.CharField(
67
- blank=True, max_length=150, verbose_name="last name"
68
- ),
58
+ models.CharField(blank=True, max_length=150, verbose_name="last name"),
69
59
  ),
70
60
  (
71
61
  "email",
72
- models.EmailField(
73
- blank=True, max_length=254, verbose_name="email address"
74
- ),
62
+ models.EmailField(blank=True, max_length=254, verbose_name="email address"),
75
63
  ),
76
64
  (
77
65
  "is_staff",
@@ -91,9 +79,7 @@ class Migration(migrations.Migration):
91
79
  ),
92
80
  (
93
81
  "date_joined",
94
- models.DateTimeField(
95
- default=django.utils.timezone.now, verbose_name="date joined"
96
- ),
82
+ models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined"),
97
83
  ),
98
84
  (
99
85
  "id",
@@ -109,7 +95,7 @@ class Migration(migrations.Migration):
109
95
  models.ManyToManyField(
110
96
  blank=True,
111
97
  help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
112
- related_name="user_set",
98
+ related_name="idp_user_set",
113
99
  related_query_name="user",
114
100
  to="auth.group",
115
101
  verbose_name="groups",
@@ -120,7 +106,7 @@ class Migration(migrations.Migration):
120
106
  models.ManyToManyField(
121
107
  blank=True,
122
108
  help_text="Specific permissions for this user.",
123
- related_name="user_set",
109
+ related_name="idp_user_set",
124
110
  related_query_name="user",
125
111
  to="auth.permission",
126
112
  verbose_name="user permissions",
@@ -0,0 +1,25 @@
1
+ # Generated by Django 5.0.8 on 2026-02-13 13:32
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("idp_user", "0007_user_demo"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.SeparateDatabaseAndState(
13
+ state_operations=[
14
+ migrations.DeleteModel(
15
+ name="UserRole",
16
+ ),
17
+ ],
18
+ database_operations=[
19
+ migrations.RunSQL(
20
+ sql="DROP TABLE IF EXISTS idp_user_userrole;",
21
+ reverse_sql=migrations.RunSQL.noop,
22
+ ),
23
+ ],
24
+ ),
25
+ ]
@@ -76,16 +76,16 @@ def get_confidential_client_service_account_token() -> str:
76
76
  """
77
77
  Reads the Keycloak confidential client service account token for the current tenant from a file.
78
78
  """
79
- tenant = TenantContext.get()
80
- token_file_path = KEYCLOAK_CONFIDENTIAL_CLIENT_SERVICE_ACCOUNT_TOKEN_FILE_PATHS.get(tenant)
79
+ realm = get_realm_for_tenant(TenantContext.get())
80
+ token_file_path = KEYCLOAK_CONFIDENTIAL_CLIENT_SERVICE_ACCOUNT_TOKEN_FILE_PATHS.get(realm)
81
81
  if not token_file_path or not os.path.isfile(token_file_path):
82
- raise FileNotFoundError(f"Keycloak service account token file for tenant {tenant} not found: {token_file_path}")
82
+ raise FileNotFoundError(f"Keycloak service account token file for tenant {realm} not found: {token_file_path}")
83
83
 
84
84
  with open(token_file_path, "r") as f:
85
85
  token = f.read().strip()
86
86
 
87
87
  if not token:
88
- raise ValueError(f"Keycloak service account token for tenant {tenant} is empty.")
88
+ raise ValueError(f"Keycloak service account token for tenant {realm} is empty.")
89
89
 
90
90
  return token
91
91
 
@@ -94,10 +94,10 @@ def get_confidential_client_secret() -> str:
94
94
  """
95
95
  Retrieves the Keycloak confidential client secret for the current tenant.
96
96
  """
97
- tenant = TenantContext.get()
98
- client_secret = KEYCLOAK_CONFIDENTIAL_CLIENT_SECRETS.get(tenant)
97
+ realm = get_realm_for_tenant(TenantContext.get())
98
+ client_secret = KEYCLOAK_CONFIDENTIAL_CLIENT_SECRETS.get(realm)
99
99
  if not client_secret:
100
- raise ValueError(f"Keycloak confidential client secret for tenant {tenant} not found.")
100
+ raise ValueError(f"Keycloak confidential client secret for tenant {realm} not found.")
101
101
 
102
102
  return client_secret
103
103