djresttoolkit 0.7.0__tar.gz → 0.9.0__tar.gz
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.
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/PKG-INFO +142 -8
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/README.md +139 -7
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/pyproject.toml +3 -1
- djresttoolkit-0.9.0/src/djresttoolkit/envconfig/__init__.py +3 -0
- djresttoolkit-0.9.0/src/djresttoolkit/envconfig/_env_settings.py +84 -0
- djresttoolkit-0.9.0/src/djresttoolkit/serializers/__init__.py +0 -0
- djresttoolkit-0.9.0/src/djresttoolkit/serializers/_serializer_create_mixin.py +87 -0
- djresttoolkit-0.9.0/src/djresttoolkit/serializers/mixins/__init__.py +6 -0
- djresttoolkit-0.9.0/src/djresttoolkit/serializers/mixins/_absolute_url_file_mixin.py +94 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/.gitignore +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/LICENSE +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/demo/staticfiles/admin/img/LICENSE +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/__init__.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/admin.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/apps.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/dbseed/__init__.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/dbseed/models/__init__.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/dbseed/models/_choice_field.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/dbseed/models/_gen.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/dbseed/models/_seed_model.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/mail/__init__.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/mail/_email_sender.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/mail/_models.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/mail/_types.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/management/__init__.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/management/commands/__init__.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/management/commands/dbflush.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/management/commands/dbseed.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/middlewares/__init__.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/middlewares/_response_time_middleware.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/migrations/__init__.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/models/__init__.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/py.typed +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/renderers/__init__.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/renderers/_throttle_info_json_renderer.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/throttling/__init__.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/throttling/_throttle_inspector.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/views/__init__.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/views/_exceptions/__init__.py +0 -0
- {djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/views/_exceptions/_exception_handler.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: djresttoolkit
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.9.0
|
4
4
|
Summary: A collection of Django and DRF utilities to simplify API development.
|
5
5
|
Project-URL: Homepage, https://github.com/shaileshpandit141/djresttoolkit
|
6
6
|
Project-URL: Documentation, https://shaileshpandit141.github.io/djresttoolkit
|
@@ -47,7 +47,9 @@ Classifier: Topic :: Utilities
|
|
47
47
|
Classifier: Typing :: Typed
|
48
48
|
Requires-Python: >=3.13
|
49
49
|
Requires-Dist: faker>=37.5.3
|
50
|
+
Requires-Dist: pydantic-settings>=2.10.1
|
50
51
|
Requires-Dist: pydantic>=2.11.7
|
52
|
+
Requires-Dist: pyyaml>=6.0.2
|
51
53
|
Provides-Extra: dev
|
52
54
|
Requires-Dist: mypy; extra == 'dev'
|
53
55
|
Requires-Dist: pytest; extra == 'dev'
|
@@ -201,7 +203,79 @@ or
|
|
201
203
|
Flushed 120 records from all models and reset IDs.
|
202
204
|
```
|
203
205
|
|
204
|
-
### 3.
|
206
|
+
### 3. EnvBaseSettings
|
207
|
+
|
208
|
+
```python
|
209
|
+
from djresttoolkit.envconfig import EnvBaseSettings
|
210
|
+
```
|
211
|
+
|
212
|
+
#### `EnvBaseSettings`
|
213
|
+
|
214
|
+
A **base settings class** for managing application configuration using:
|
215
|
+
|
216
|
+
- YAML files (default `.environ.yaml`)
|
217
|
+
- Environment variables (default `.env`)
|
218
|
+
|
219
|
+
Supports **nested configuration** using double underscores (`__`) in environment variable names.
|
220
|
+
|
221
|
+
#### Class Attributes
|
222
|
+
|
223
|
+
- Attributes
|
224
|
+
- `env_file`
|
225
|
+
- Type: `str`
|
226
|
+
- Default: `.env`
|
227
|
+
- Description: Environment variable file path.
|
228
|
+
- `yaml_file`
|
229
|
+
- Type: `str`
|
230
|
+
- Default: `.environ.yaml`
|
231
|
+
- Description: YAML configuration file path.
|
232
|
+
- `model_config`
|
233
|
+
- Type: `SettingsConfigDict`
|
234
|
+
- Description: Pydantic settings configuration (file encoding, nested delimiter).
|
235
|
+
|
236
|
+
#### Methods
|
237
|
+
|
238
|
+
#### `load(cls, *, env_file: str | None = None, ymal_file: str | None = None, warning: bool = True) -> EnvBaseSettings`
|
239
|
+
|
240
|
+
Loads configuration from **YAML first**, then overrides with **environment variables**.
|
241
|
+
|
242
|
+
#### Parameters
|
243
|
+
|
244
|
+
- `env_file` — Optional custom `.env` file path.
|
245
|
+
- `ymal_file` — Optional custom YAML file path.
|
246
|
+
- `warning` — Emit a warning if YAML file is missing (default `True`).
|
247
|
+
|
248
|
+
#### Returns
|
249
|
+
|
250
|
+
- Instance of `EnvBaseSettings` (or subclass) with loaded configuration.
|
251
|
+
|
252
|
+
#### Raises
|
253
|
+
|
254
|
+
- `UserWarning` if YAML file not found and `warning=True`.
|
255
|
+
|
256
|
+
### Usage Example
|
257
|
+
|
258
|
+
```python
|
259
|
+
from djresttoolkit.envconfig import EnvBaseSettings
|
260
|
+
|
261
|
+
class EnvSettings(EnvBaseSettings):
|
262
|
+
debug: bool = False
|
263
|
+
database_url: str
|
264
|
+
|
265
|
+
# Load settings
|
266
|
+
settings = EnvSettings.load(warning=False)
|
267
|
+
|
268
|
+
print(settings.debug)
|
269
|
+
print(settings.database_url)
|
270
|
+
```
|
271
|
+
|
272
|
+
#### Features
|
273
|
+
|
274
|
+
- Prioritizes `.env` variables over YAML.
|
275
|
+
- Supports nested keys: `DATABASE__HOST` → `settings.database.host`.
|
276
|
+
- Designed to be subclassed for project-specific settings.
|
277
|
+
|
278
|
+
### 4. EmailSender
|
205
279
|
|
206
280
|
```python
|
207
281
|
from djresttoolkit.mail import EmailSender, EmailContent, EmailTemplate
|
@@ -217,7 +291,7 @@ Send templated emails.
|
|
217
291
|
EmailSender(email_content: EmailContent | EmailContentDict)
|
218
292
|
```
|
219
293
|
|
220
|
-
#### Methods
|
294
|
+
#### EmailSender Methods
|
221
295
|
|
222
296
|
```python
|
223
297
|
send(to: list[str], exceptions: bool = False) -> bool
|
@@ -250,7 +324,7 @@ EmailSender(content).send(to=["user@example.com"])
|
|
250
324
|
|
251
325
|
- `text`, `html` — template file paths
|
252
326
|
|
253
|
-
###
|
327
|
+
### 5. Custom DRF Exception Handler
|
254
328
|
|
255
329
|
```python
|
256
330
|
from djresttoolkit.views import exception_handler
|
@@ -264,12 +338,12 @@ A DRF exception handler that:
|
|
264
338
|
- Adds throttling support (defaults to `AnonRateThrottle`).
|
265
339
|
- Returns **429 Too Many Requests** with `retry_after` if throttle limit is exceeded.
|
266
340
|
|
267
|
-
#### Parameters
|
341
|
+
#### Exception Handler Parameters
|
268
342
|
|
269
343
|
- `exc`: Exception object.
|
270
344
|
- `context`: DRF context dictionary containing `"request"` and `"view"`.
|
271
345
|
|
272
|
-
#### Returns
|
346
|
+
#### Returns Type of Exception Handler
|
273
347
|
|
274
348
|
- `Response` — DRF Response object (with throttling info if applicable), or `None`.
|
275
349
|
|
@@ -290,7 +364,7 @@ REST_FRAMEWORK = {
|
|
290
364
|
- Tracks requests in cache and calculates `retry_after`.
|
291
365
|
- Cleans expired timestamps automatically.
|
292
366
|
|
293
|
-
###
|
367
|
+
### 6. Response Time Middleware
|
294
368
|
|
295
369
|
```python
|
296
370
|
from djresttoolkit.middlewares import ResponseTimeMiddleware
|
@@ -337,7 +411,7 @@ X-Response-Time: 0.01234 seconds
|
|
337
411
|
INFO: Request processed in 0.01234 seconds
|
338
412
|
```
|
339
413
|
|
340
|
-
###
|
414
|
+
### 7. Throttle Utilities
|
341
415
|
|
342
416
|
#### `ThrottleInfoJSONRenderer`
|
343
417
|
|
@@ -393,6 +467,66 @@ ThrottleInspector(
|
|
393
467
|
- `attach_headers(response: Response, throttle_info: dict | None)`
|
394
468
|
Attaches throttle data to HTTP headers.
|
395
469
|
|
470
|
+
### 8. AbsoluteUrlFileMixin — API Reference
|
471
|
+
|
472
|
+
```python
|
473
|
+
from djresttoolkit.serializers.mixins import AbsoluteUrlFileMixin
|
474
|
+
```
|
475
|
+
|
476
|
+
### `AbsoluteUrlFileMixin`
|
477
|
+
|
478
|
+
A **serializer mixin** that converts **FileField** and **ImageField** URLs to **absolute URLs**, ensuring compatibility with cloud storage backends.
|
479
|
+
|
480
|
+
---
|
481
|
+
|
482
|
+
### Attributes
|
483
|
+
|
484
|
+
- `file_fields`
|
485
|
+
- type: `list[str] | None`
|
486
|
+
- default: `None`
|
487
|
+
- description: Manual list of file field names for non-model serializers.
|
488
|
+
|
489
|
+
### Absolute Url File Mixin Methods
|
490
|
+
|
491
|
+
#### `to_representation(self, instance: Any) -> dict[str, Any]`
|
492
|
+
|
493
|
+
- Overrides default serializer `to_representation`.
|
494
|
+
- Enhances all file-related fields in the serialized output to **absolute URLs**.
|
495
|
+
|
496
|
+
#### `enhance_file_fields(self, instance: Any, representation: dict[str, Any], request: Any) -> dict[str, Any]`
|
497
|
+
|
498
|
+
- Core logic to process each file field.
|
499
|
+
- Converts relative URLs to absolute URLs using `request.build_absolute_uri()`.
|
500
|
+
- Supports model serializers or manual `file_fields`.
|
501
|
+
- Logs warnings if request context is missing or file is not found.
|
502
|
+
|
503
|
+
#### Exceptions
|
504
|
+
|
505
|
+
- `MissingRequestContext`: Raised if the request object is missing in serializer context and `DEBUG=True`.
|
506
|
+
|
507
|
+
### Absolute Url File Mixin Example
|
508
|
+
|
509
|
+
```python
|
510
|
+
from rest_framework import serializers
|
511
|
+
from djresttoolkit.serializers.mixins import AbsoluteUrlFileMixin
|
512
|
+
from myapp.models import Document
|
513
|
+
|
514
|
+
class DocumentSerializer(AbsoluteUrlFileMixin, serializers.ModelSerializer):
|
515
|
+
class Meta:
|
516
|
+
model = Document
|
517
|
+
fields = ["id", "title", "file"]
|
518
|
+
|
519
|
+
# Output will convert `file` field to an absolute URL
|
520
|
+
serializer = DocumentSerializer(instance, context={"request": request})
|
521
|
+
data = serializer.data
|
522
|
+
```
|
523
|
+
|
524
|
+
#### Notes
|
525
|
+
|
526
|
+
- Works with both Django model serializers and custom serializers.
|
527
|
+
- Relative file paths are automatically converted to absolute URLs.
|
528
|
+
- Can manually specify fields via `file_fields` for non-model serializers.
|
529
|
+
|
396
530
|
## 🛠️ Planned Features
|
397
531
|
|
398
532
|
- Add more utils
|
@@ -145,7 +145,79 @@ or
|
|
145
145
|
Flushed 120 records from all models and reset IDs.
|
146
146
|
```
|
147
147
|
|
148
|
-
### 3.
|
148
|
+
### 3. EnvBaseSettings
|
149
|
+
|
150
|
+
```python
|
151
|
+
from djresttoolkit.envconfig import EnvBaseSettings
|
152
|
+
```
|
153
|
+
|
154
|
+
#### `EnvBaseSettings`
|
155
|
+
|
156
|
+
A **base settings class** for managing application configuration using:
|
157
|
+
|
158
|
+
- YAML files (default `.environ.yaml`)
|
159
|
+
- Environment variables (default `.env`)
|
160
|
+
|
161
|
+
Supports **nested configuration** using double underscores (`__`) in environment variable names.
|
162
|
+
|
163
|
+
#### Class Attributes
|
164
|
+
|
165
|
+
- Attributes
|
166
|
+
- `env_file`
|
167
|
+
- Type: `str`
|
168
|
+
- Default: `.env`
|
169
|
+
- Description: Environment variable file path.
|
170
|
+
- `yaml_file`
|
171
|
+
- Type: `str`
|
172
|
+
- Default: `.environ.yaml`
|
173
|
+
- Description: YAML configuration file path.
|
174
|
+
- `model_config`
|
175
|
+
- Type: `SettingsConfigDict`
|
176
|
+
- Description: Pydantic settings configuration (file encoding, nested delimiter).
|
177
|
+
|
178
|
+
#### Methods
|
179
|
+
|
180
|
+
#### `load(cls, *, env_file: str | None = None, ymal_file: str | None = None, warning: bool = True) -> EnvBaseSettings`
|
181
|
+
|
182
|
+
Loads configuration from **YAML first**, then overrides with **environment variables**.
|
183
|
+
|
184
|
+
#### Parameters
|
185
|
+
|
186
|
+
- `env_file` — Optional custom `.env` file path.
|
187
|
+
- `ymal_file` — Optional custom YAML file path.
|
188
|
+
- `warning` — Emit a warning if YAML file is missing (default `True`).
|
189
|
+
|
190
|
+
#### Returns
|
191
|
+
|
192
|
+
- Instance of `EnvBaseSettings` (or subclass) with loaded configuration.
|
193
|
+
|
194
|
+
#### Raises
|
195
|
+
|
196
|
+
- `UserWarning` if YAML file not found and `warning=True`.
|
197
|
+
|
198
|
+
### Usage Example
|
199
|
+
|
200
|
+
```python
|
201
|
+
from djresttoolkit.envconfig import EnvBaseSettings
|
202
|
+
|
203
|
+
class EnvSettings(EnvBaseSettings):
|
204
|
+
debug: bool = False
|
205
|
+
database_url: str
|
206
|
+
|
207
|
+
# Load settings
|
208
|
+
settings = EnvSettings.load(warning=False)
|
209
|
+
|
210
|
+
print(settings.debug)
|
211
|
+
print(settings.database_url)
|
212
|
+
```
|
213
|
+
|
214
|
+
#### Features
|
215
|
+
|
216
|
+
- Prioritizes `.env` variables over YAML.
|
217
|
+
- Supports nested keys: `DATABASE__HOST` → `settings.database.host`.
|
218
|
+
- Designed to be subclassed for project-specific settings.
|
219
|
+
|
220
|
+
### 4. EmailSender
|
149
221
|
|
150
222
|
```python
|
151
223
|
from djresttoolkit.mail import EmailSender, EmailContent, EmailTemplate
|
@@ -161,7 +233,7 @@ Send templated emails.
|
|
161
233
|
EmailSender(email_content: EmailContent | EmailContentDict)
|
162
234
|
```
|
163
235
|
|
164
|
-
#### Methods
|
236
|
+
#### EmailSender Methods
|
165
237
|
|
166
238
|
```python
|
167
239
|
send(to: list[str], exceptions: bool = False) -> bool
|
@@ -194,7 +266,7 @@ EmailSender(content).send(to=["user@example.com"])
|
|
194
266
|
|
195
267
|
- `text`, `html` — template file paths
|
196
268
|
|
197
|
-
###
|
269
|
+
### 5. Custom DRF Exception Handler
|
198
270
|
|
199
271
|
```python
|
200
272
|
from djresttoolkit.views import exception_handler
|
@@ -208,12 +280,12 @@ A DRF exception handler that:
|
|
208
280
|
- Adds throttling support (defaults to `AnonRateThrottle`).
|
209
281
|
- Returns **429 Too Many Requests** with `retry_after` if throttle limit is exceeded.
|
210
282
|
|
211
|
-
#### Parameters
|
283
|
+
#### Exception Handler Parameters
|
212
284
|
|
213
285
|
- `exc`: Exception object.
|
214
286
|
- `context`: DRF context dictionary containing `"request"` and `"view"`.
|
215
287
|
|
216
|
-
#### Returns
|
288
|
+
#### Returns Type of Exception Handler
|
217
289
|
|
218
290
|
- `Response` — DRF Response object (with throttling info if applicable), or `None`.
|
219
291
|
|
@@ -234,7 +306,7 @@ REST_FRAMEWORK = {
|
|
234
306
|
- Tracks requests in cache and calculates `retry_after`.
|
235
307
|
- Cleans expired timestamps automatically.
|
236
308
|
|
237
|
-
###
|
309
|
+
### 6. Response Time Middleware
|
238
310
|
|
239
311
|
```python
|
240
312
|
from djresttoolkit.middlewares import ResponseTimeMiddleware
|
@@ -281,7 +353,7 @@ X-Response-Time: 0.01234 seconds
|
|
281
353
|
INFO: Request processed in 0.01234 seconds
|
282
354
|
```
|
283
355
|
|
284
|
-
###
|
356
|
+
### 7. Throttle Utilities
|
285
357
|
|
286
358
|
#### `ThrottleInfoJSONRenderer`
|
287
359
|
|
@@ -337,6 +409,66 @@ ThrottleInspector(
|
|
337
409
|
- `attach_headers(response: Response, throttle_info: dict | None)`
|
338
410
|
Attaches throttle data to HTTP headers.
|
339
411
|
|
412
|
+
### 8. AbsoluteUrlFileMixin — API Reference
|
413
|
+
|
414
|
+
```python
|
415
|
+
from djresttoolkit.serializers.mixins import AbsoluteUrlFileMixin
|
416
|
+
```
|
417
|
+
|
418
|
+
### `AbsoluteUrlFileMixin`
|
419
|
+
|
420
|
+
A **serializer mixin** that converts **FileField** and **ImageField** URLs to **absolute URLs**, ensuring compatibility with cloud storage backends.
|
421
|
+
|
422
|
+
---
|
423
|
+
|
424
|
+
### Attributes
|
425
|
+
|
426
|
+
- `file_fields`
|
427
|
+
- type: `list[str] | None`
|
428
|
+
- default: `None`
|
429
|
+
- description: Manual list of file field names for non-model serializers.
|
430
|
+
|
431
|
+
### Absolute Url File Mixin Methods
|
432
|
+
|
433
|
+
#### `to_representation(self, instance: Any) -> dict[str, Any]`
|
434
|
+
|
435
|
+
- Overrides default serializer `to_representation`.
|
436
|
+
- Enhances all file-related fields in the serialized output to **absolute URLs**.
|
437
|
+
|
438
|
+
#### `enhance_file_fields(self, instance: Any, representation: dict[str, Any], request: Any) -> dict[str, Any]`
|
439
|
+
|
440
|
+
- Core logic to process each file field.
|
441
|
+
- Converts relative URLs to absolute URLs using `request.build_absolute_uri()`.
|
442
|
+
- Supports model serializers or manual `file_fields`.
|
443
|
+
- Logs warnings if request context is missing or file is not found.
|
444
|
+
|
445
|
+
#### Exceptions
|
446
|
+
|
447
|
+
- `MissingRequestContext`: Raised if the request object is missing in serializer context and `DEBUG=True`.
|
448
|
+
|
449
|
+
### Absolute Url File Mixin Example
|
450
|
+
|
451
|
+
```python
|
452
|
+
from rest_framework import serializers
|
453
|
+
from djresttoolkit.serializers.mixins import AbsoluteUrlFileMixin
|
454
|
+
from myapp.models import Document
|
455
|
+
|
456
|
+
class DocumentSerializer(AbsoluteUrlFileMixin, serializers.ModelSerializer):
|
457
|
+
class Meta:
|
458
|
+
model = Document
|
459
|
+
fields = ["id", "title", "file"]
|
460
|
+
|
461
|
+
# Output will convert `file` field to an absolute URL
|
462
|
+
serializer = DocumentSerializer(instance, context={"request": request})
|
463
|
+
data = serializer.data
|
464
|
+
```
|
465
|
+
|
466
|
+
#### Notes
|
467
|
+
|
468
|
+
- Works with both Django model serializers and custom serializers.
|
469
|
+
- Relative file paths are automatically converted to absolute URLs.
|
470
|
+
- Can manually specify fields via `file_fields` for non-model serializers.
|
471
|
+
|
340
472
|
## 🛠️ Planned Features
|
341
473
|
|
342
474
|
- Add more utils
|
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "djresttoolkit"
|
7
|
-
version = "0.
|
7
|
+
version = "0.9.0"
|
8
8
|
description = "A collection of Django and DRF utilities to simplify API development."
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
10
10
|
license = { file = "LICENSE" }
|
@@ -48,6 +48,8 @@ requires-python = ">=3.13"
|
|
48
48
|
dependencies = [
|
49
49
|
"faker>=37.5.3",
|
50
50
|
"pydantic>=2.11.7",
|
51
|
+
"pydantic-settings>=2.10.1",
|
52
|
+
"pyyaml>=6.0.2",
|
51
53
|
]
|
52
54
|
|
53
55
|
# CLI scripts entry point configuration
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import warnings
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import Any, ClassVar
|
4
|
+
|
5
|
+
import yaml
|
6
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
7
|
+
|
8
|
+
|
9
|
+
class EnvBaseSettings[T: "EnvBaseSettings"](BaseSettings):
|
10
|
+
""" "
|
11
|
+
EnvBaseSettings is a base settings class for managing application configuration
|
12
|
+
using both YAML files and environment variables.
|
13
|
+
This class is designed to load configuration values from a YAML file first,
|
14
|
+
and then override those values with environment variables if present. It supports
|
15
|
+
nested configuration using a double underscore (`__`) as the delimiter in
|
16
|
+
environment variable names, allowing for hierarchical settings.
|
17
|
+
|
18
|
+
Class Attributes:
|
19
|
+
env_file (str): The default filename for the environment variables file (default: ".env").
|
20
|
+
yaml_file (str): The default filename for the YAML configuration file (default: ".environ.yaml").
|
21
|
+
model_config (SettingsConfigDict): Configuration for environment variable parsing, including file encoding and nested delimiter.
|
22
|
+
|
23
|
+
Methods:
|
24
|
+
load(cls, *, env_file: str | None = None, ymal_file: str | None = None, warning: bool = True) -> "EnvBaseSettings":
|
25
|
+
Loads configuration from a YAML file (if it exists), then overrides with environment variables.
|
26
|
+
- env_file: Optional custom path to the .env file.
|
27
|
+
- ymal_file: Optional custom path to the YAML file.
|
28
|
+
- warning: If True, emits a warning if the YAML file is not found.
|
29
|
+
Returns an instance of EnvBaseSettings with the loaded configuration.
|
30
|
+
|
31
|
+
Usage:
|
32
|
+
- Define your settings as subclasses of EnvBaseSettings.
|
33
|
+
- Call `YourSettingsClass.load()` to load configuration from files and environment variables.
|
34
|
+
- Supports nested configuration via double underscore in environment variable names (e.g., `DATABASE__HOST`).
|
35
|
+
|
36
|
+
Raises:
|
37
|
+
- UserWarning: If the YAML file is not found and `warning` is True.
|
38
|
+
|
39
|
+
Example:
|
40
|
+
```python
|
41
|
+
from djresttoolkit.envconfig import EnvBaseSettings
|
42
|
+
|
43
|
+
class EnvSettings(EnvBaseSettings):
|
44
|
+
debug: bool = False
|
45
|
+
database_url: str
|
46
|
+
|
47
|
+
settings = EnvSettings.load(warning=False)
|
48
|
+
```
|
49
|
+
|
50
|
+
"""
|
51
|
+
|
52
|
+
env_file: ClassVar[str] = ".env"
|
53
|
+
yaml_file: ClassVar[str] = ".environ.yaml"
|
54
|
+
|
55
|
+
model_config = SettingsConfigDict(
|
56
|
+
env_file=env_file,
|
57
|
+
env_file_encoding="utf-8",
|
58
|
+
env_nested_delimiter="__",
|
59
|
+
)
|
60
|
+
|
61
|
+
@classmethod
|
62
|
+
def load(
|
63
|
+
cls: type[T],
|
64
|
+
*,
|
65
|
+
env_file: str | None = None,
|
66
|
+
ymal_file: str | None = None,
|
67
|
+
warning: bool = True,
|
68
|
+
) -> T:
|
69
|
+
"""Load from YAML first, then override with .env."""
|
70
|
+
if env_file:
|
71
|
+
cls.env_file = env_file
|
72
|
+
if ymal_file:
|
73
|
+
cls.yaml_file = ymal_file
|
74
|
+
|
75
|
+
config_file = Path(cls.yaml_file)
|
76
|
+
yaml_data: dict[str, Any] = {}
|
77
|
+
if config_file.exists():
|
78
|
+
with config_file.open("r") as f:
|
79
|
+
yaml_data = yaml.safe_load(f) or {}
|
80
|
+
elif warning:
|
81
|
+
msg: str = f"Config file {config_file} not found, using only env vars."
|
82
|
+
warnings.warn(msg, UserWarning, stacklevel=1)
|
83
|
+
|
84
|
+
return cls(**yaml_data)
|
File without changes
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Any, cast
|
3
|
+
|
4
|
+
from django.core.exceptions import FieldDoesNotExist
|
5
|
+
from django.db.models import Model
|
6
|
+
from rest_framework.serializers import Field as SerializerField
|
7
|
+
from django.db.models import Field as ModelField
|
8
|
+
|
9
|
+
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
|
13
|
+
class RecordsCreationMixin:
|
14
|
+
"""
|
15
|
+
A mixin for DRF serializers that supports:
|
16
|
+
- Single instance creation with extra context fields
|
17
|
+
- Bulk creation from a list of validated_data dicts
|
18
|
+
- Updating field error messages with model-specific messages
|
19
|
+
|
20
|
+
Notes:
|
21
|
+
- bulk_create() does not trigger model signals or .save()
|
22
|
+
- Meta.model must be defined
|
23
|
+
"""
|
24
|
+
|
25
|
+
def create(
|
26
|
+
self, validated_data: dict[str, Any] | list[dict[str, Any]]
|
27
|
+
) -> Model | list[Model]:
|
28
|
+
logger.debug("Starting creation", extra={"validated_data": validated_data})
|
29
|
+
|
30
|
+
model: type[Model] | None = getattr(getattr(self, "Meta", None), "model", None)
|
31
|
+
if model is None:
|
32
|
+
logger.error("Meta.model not defined.")
|
33
|
+
raise AttributeError(f"{self.__class__.__name__} missing Meta.model.")
|
34
|
+
|
35
|
+
# Bulk creation
|
36
|
+
if isinstance(validated_data, list):
|
37
|
+
instances = [model(**item) for item in validated_data]
|
38
|
+
if instances:
|
39
|
+
logger.info(
|
40
|
+
"Bulk creating instances",
|
41
|
+
extra={"count": len(instances), "model": model.__name__},
|
42
|
+
)
|
43
|
+
return model.objects.bulk_create(instances)
|
44
|
+
logger.info("No instances to create.")
|
45
|
+
return []
|
46
|
+
|
47
|
+
# Single instance creation
|
48
|
+
if not hasattr(super(), "create"):
|
49
|
+
raise NotImplementedError(
|
50
|
+
f"{self.__class__.__name__} must be used with a DRF serializer "
|
51
|
+
"that implements create()."
|
52
|
+
)
|
53
|
+
|
54
|
+
logger.info("Creating a single instance", extra={"model": model.__name__})
|
55
|
+
return super().create({**validated_data}) # type: ignore[misc]
|
56
|
+
|
57
|
+
def get_fields(self) -> dict[str, SerializerField[Any, Any, Any, Any]]:
|
58
|
+
# DRF serializer fields
|
59
|
+
fields = cast(
|
60
|
+
dict[str, SerializerField[Any, Any, Any, Any]],
|
61
|
+
super().get_fields(), # type: ignore
|
62
|
+
)
|
63
|
+
|
64
|
+
meta = getattr(self, "Meta", None)
|
65
|
+
model: type[Model] | None = getattr(meta, "model", None)
|
66
|
+
|
67
|
+
if model is None:
|
68
|
+
raise ValueError(f"{self.__class__.__name__}.Meta.model must be defined.")
|
69
|
+
|
70
|
+
logger.debug("Setting up serializer fields", extra={"model": model.__name__})
|
71
|
+
|
72
|
+
for field_name, serializer_field in fields.items():
|
73
|
+
try:
|
74
|
+
# Django model field
|
75
|
+
model_field = cast(
|
76
|
+
ModelField[Any, Any],
|
77
|
+
model._meta.get_field(field_name), # type: ignore
|
78
|
+
)
|
79
|
+
if hasattr(model_field, "error_messages"):
|
80
|
+
serializer_field.error_messages.update(model_field.error_messages)
|
81
|
+
except FieldDoesNotExist:
|
82
|
+
logger.warning(
|
83
|
+
"Skipping serializer field not present on model",
|
84
|
+
extra={"field_name": field_name, "model": model.__name__},
|
85
|
+
)
|
86
|
+
|
87
|
+
return fields
|
@@ -0,0 +1,94 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Any, cast
|
3
|
+
from urllib.parse import urlparse
|
4
|
+
|
5
|
+
from django.conf import settings
|
6
|
+
from django.db import models
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
|
11
|
+
class MissingRequestContext(Exception):
|
12
|
+
"""Custom exception for missing request in serializer context."""
|
13
|
+
|
14
|
+
...
|
15
|
+
|
16
|
+
|
17
|
+
class AbsoluteUrlFileMixin:
|
18
|
+
"""
|
19
|
+
A mixin that updates FileField and ImageField URLs in serializer output
|
20
|
+
to be absolute URLs, compatible with cloud storage backends.
|
21
|
+
"""
|
22
|
+
|
23
|
+
# manually specify file fields for non-model serializers
|
24
|
+
file_fields: list[str] | None = None
|
25
|
+
|
26
|
+
def to_representation(self, instance: Any) -> dict[str, Any]:
|
27
|
+
"""Extend serializer representation to enhance file field URLs."""
|
28
|
+
representation = cast(dict[str, Any], super().to_representation(instance)) # type: ignore[misc]
|
29
|
+
request = self.context.get("request") # type: ignore
|
30
|
+
return self.enhance_file_fields(instance, representation, request)
|
31
|
+
|
32
|
+
def enhance_file_fields(
|
33
|
+
self,
|
34
|
+
instance: Any,
|
35
|
+
representation: dict[str, Any],
|
36
|
+
request: Any,
|
37
|
+
) -> dict[str, Any]:
|
38
|
+
if request is None:
|
39
|
+
logger.warning("Request not found in serializer context.")
|
40
|
+
if settings.DEBUG:
|
41
|
+
raise MissingRequestContext("Request not found in serializer context.")
|
42
|
+
return representation
|
43
|
+
|
44
|
+
# Collect only file-related model fields if available
|
45
|
+
model_fields = (
|
46
|
+
{
|
47
|
+
field.name: field
|
48
|
+
for field in instance._meta.get_fields()
|
49
|
+
if isinstance(field, (models.FileField, models.ImageField))
|
50
|
+
}
|
51
|
+
if hasattr(instance, "_meta")
|
52
|
+
else {}
|
53
|
+
)
|
54
|
+
|
55
|
+
manual_fields = getattr(self, "file_fields", []) or []
|
56
|
+
|
57
|
+
for field_name, field_value in representation.items():
|
58
|
+
model_field = model_fields.get(field_name)
|
59
|
+
|
60
|
+
is_file_field = model_field or (field_name in manual_fields)
|
61
|
+
if not is_file_field:
|
62
|
+
continue
|
63
|
+
|
64
|
+
try:
|
65
|
+
# Get file URL from instance or raw serializer value
|
66
|
+
if model_field:
|
67
|
+
file_instance = getattr(instance, field_name, None)
|
68
|
+
file_url = (
|
69
|
+
getattr(file_instance, "url", None) if file_instance else None
|
70
|
+
)
|
71
|
+
else:
|
72
|
+
file_url = field_value
|
73
|
+
|
74
|
+
if not file_url:
|
75
|
+
logger.info("No file found for field: %s", field_name)
|
76
|
+
representation[field_name] = None
|
77
|
+
continue
|
78
|
+
|
79
|
+
# Only build absolute URL if it's relative
|
80
|
+
parsed_url = urlparse(str(file_url))
|
81
|
+
if not parsed_url.netloc: # relative path
|
82
|
+
file_url = request.build_absolute_uri(file_url)
|
83
|
+
|
84
|
+
representation[field_name] = file_url
|
85
|
+
logger.debug("Enhanced URL for %s: %s", field_name, file_url)
|
86
|
+
|
87
|
+
except Exception as error:
|
88
|
+
logger.error(
|
89
|
+
"Unexpected error processing file field %s: %s",
|
90
|
+
field_name,
|
91
|
+
error,
|
92
|
+
)
|
93
|
+
|
94
|
+
return representation
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/dbseed/models/_choice_field.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/management/commands/__init__.py
RENAMED
File without changes
|
{djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/management/commands/dbflush.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{djresttoolkit-0.7.0 → djresttoolkit-0.9.0}/src/djresttoolkit/throttling/_throttle_inspector.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|