oxutils 0.1.2__py3-none-any.whl → 0.1.4__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.
oxutils/__init__.py CHANGED
@@ -10,7 +10,7 @@ This package provides:
10
10
  - Custom exceptions
11
11
  """
12
12
 
13
- __version__ = "0.1.1"
13
+ __version__ = "0.1.4"
14
14
 
15
15
  from oxutils.settings import oxi_settings
16
16
  from oxutils.conf import UTILS_APPS, AUDIT_MIDDLEWARE
oxutils/audit/settings.py CHANGED
@@ -1,19 +1,4 @@
1
1
  # Oxiliere Audit settings
2
2
 
3
- AUDITLOG_DISABLE_REMOTE_ADDR = False
4
- AUDITLOG_MASK_TRACKING_FIELDS = (
5
- "password",
6
- "api_key",
7
- "secret_token",
8
- "token",
9
- )
10
-
11
- AUDITLOG_EXCLUDE_TRACKING_FIELDS = (
12
- "created_at",
13
- "updated_at",
14
- )
15
-
16
- CID_GENERATE = False
17
-
18
- AUDITLOG_CID_GETTER = "cid.locals.get_cid"
3
+ AUDITLOG_CID_GETTER = "oxutils.audit.utils.get_request_id"
19
4
  AUDITLOG_LOGENTRY_MODEL = "auditlog.LogEntry"
oxutils/audit/utils.py ADDED
@@ -0,0 +1,22 @@
1
+ """
2
+ Utility functions for audit logging.
3
+ """
4
+ import structlog
5
+
6
+
7
+ def get_request_id():
8
+ """
9
+ Get the request_id from django-structlog context.
10
+
11
+ This function retrieves the request_id that was set by
12
+ django-structlog's RequestMiddleware and returns it for use
13
+ in auditlog's correlation ID field.
14
+
15
+ Returns:
16
+ str: The request_id from the current request context, or None if not available.
17
+ """
18
+ try:
19
+ context = structlog.contextvars.get_contextvars()
20
+ return context.get('request_id')
21
+ except Exception:
22
+ return None
oxutils/conf.py CHANGED
@@ -1,13 +1,11 @@
1
1
  UTILS_APPS = (
2
2
  'django_structlog',
3
3
  'auditlog',
4
- 'cid.apps.CidAppConfig',
5
4
  'django_celery_results',
6
5
  'oxutils.audit',
7
6
  )
8
7
 
9
8
  AUDIT_MIDDLEWARE = (
10
- 'cid.middleware.CidMiddleware',
11
- 'auditlog.middleware.AuditlogMiddleware',
12
9
  'django_structlog.middlewares.RequestMiddleware',
10
+ 'auditlog.middleware.AuditlogMiddleware',
13
11
  )
File without changes
@@ -0,0 +1,11 @@
1
+ """
2
+ Context processor for site name
3
+ """
4
+
5
+ def site_name(request):
6
+ from oxutils.settings import oxi_settings
7
+
8
+ return {
9
+ 'site_name': oxi_settings.site_name,
10
+ 'site_domain': oxi_settings.site_domain,
11
+ }
File without changes
@@ -0,0 +1,57 @@
1
+ from django.contrib import admin
2
+ from .models import CurrencyState, Currency
3
+
4
+
5
+ class CurrencyInline(admin.TabularInline):
6
+ model = Currency
7
+ extra = 0
8
+ readonly_fields = ('id', 'code', 'rate')
9
+ can_delete = False
10
+
11
+ def has_add_permission(self, request, obj=None):
12
+ return False
13
+
14
+
15
+ @admin.register(CurrencyState)
16
+ class CurrencyStateAdmin(admin.ModelAdmin):
17
+ list_display = ('id', 'source', 'currency_count', 'created_at', 'updated_at')
18
+ list_filter = ('source', 'created_at')
19
+ readonly_fields = ('id', 'created_at', 'updated_at')
20
+ search_fields = ('id', 'source')
21
+ ordering = ('-created_at',)
22
+ inlines = [CurrencyInline]
23
+
24
+ def currency_count(self, obj):
25
+ return obj.currencies.count()
26
+ currency_count.short_description = 'Currencies'
27
+
28
+ def has_add_permission(self, request):
29
+ return False
30
+
31
+ def has_delete_permission(self, request, obj=None):
32
+ return request.user.is_superuser
33
+
34
+
35
+ @admin.register(Currency)
36
+ class CurrencyAdmin(admin.ModelAdmin):
37
+ list_display = ('id', 'code', 'rate', 'state_source', 'state_created_at')
38
+ list_filter = ('code', 'state__source', 'state__created_at')
39
+ readonly_fields = ('id', 'code', 'rate', 'state')
40
+ search_fields = ('code', 'state__id')
41
+ ordering = ('code',)
42
+
43
+ def state_source(self, obj):
44
+ return obj.state.source
45
+ state_source.short_description = 'Source'
46
+ state_source.admin_order_field = 'state__source'
47
+
48
+ def state_created_at(self, obj):
49
+ return obj.state.created_at
50
+ state_created_at.short_description = 'State Created'
51
+ state_created_at.admin_order_field = 'state__created_at'
52
+
53
+ def has_add_permission(self, request):
54
+ return False
55
+
56
+ def has_delete_permission(self, request, obj=None):
57
+ return request.user.is_superuser
@@ -0,0 +1,7 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class CurrencyConfig(AppConfig):
5
+ default_auto_field = 'django.db.models.BigAutoField'
6
+ name = 'oxutils.currency'
7
+ verbose_name = "Oxutils Currency"
@@ -0,0 +1,78 @@
1
+ from django.http import HttpRequest
2
+ from django.core.exceptions import ObjectDoesNotExist
3
+ from ninja_extra import (
4
+ ControllerBase,
5
+ api_controller,
6
+ http_get,
7
+ )
8
+ from ninja_extra.pagination import (
9
+ paginate, PageNumberPaginationExtra, PaginatedResponseSchema
10
+ )
11
+ from ninja.errors import HttpError
12
+ from uuid import UUID
13
+ import structlog
14
+ from currency.models import CurrencyState
15
+ from currency.schemas import (
16
+ CurrencyStateSchema,
17
+ CurrencyStateDetailSchema,
18
+ )
19
+
20
+ logger = structlog.get_logger(__name__)
21
+
22
+
23
+ @api_controller('/currency', tags=['Currency'], auth=None)
24
+ class CurrencyController(ControllerBase):
25
+
26
+ @http_get('/states', response=PaginatedResponseSchema[CurrencyStateSchema])
27
+ @paginate(PageNumberPaginationExtra, page_size=20)
28
+ def list_states(self, request: HttpRequest):
29
+ return CurrencyState.objects.all().order_by('-created_at')
30
+
31
+ @http_get('/states/latest', response=CurrencyStateDetailSchema)
32
+ def get_latest_state(self, request: HttpRequest):
33
+ try:
34
+ state = CurrencyState.objects.latest()
35
+ except ObjectDoesNotExist:
36
+ logger.error("currency_state_not_found", message="No currency state found in database")
37
+ raise HttpError(404, "No currency state found in database")
38
+
39
+ return {
40
+ 'id': state.id,
41
+ 'source': state.source,
42
+ 'created_at': state.created_at,
43
+ 'updated_at': state.updated_at,
44
+ 'currencies': {c.code: float(c.rate) for c in state.currencies.all()}
45
+ }
46
+
47
+ @http_get('/states/{state_id}', response=CurrencyStateDetailSchema)
48
+ def get_state(self, request: HttpRequest, state_id: UUID):
49
+ state = CurrencyState.objects.prefetch_related('currencies').get(id=state_id)
50
+ return {
51
+ 'id': state.id,
52
+ 'source': state.source,
53
+ 'created_at': state.created_at,
54
+ 'updated_at': state.updated_at,
55
+ 'currencies': {c.code: float(c.rate) for c in state.currencies.all()}
56
+ }
57
+
58
+ @http_get('/rates', response=dict[str, float])
59
+ def get_current_rates(self, request: HttpRequest):
60
+ try:
61
+ state = CurrencyState.objects.latest()
62
+ except ObjectDoesNotExist:
63
+ logger.error("currency_state_not_found", message="No currency state found in database")
64
+ raise HttpError(404, "No currency rates available")
65
+
66
+ currencies = state.currencies.all()
67
+ return {c.code: float(c.rate) for c in currencies}
68
+
69
+ @http_get('/rates/{code}', response=dict[str, float])
70
+ def get_rate_by_code(self, request: HttpRequest, code: str):
71
+ try:
72
+ state = CurrencyState.objects.latest()
73
+ currency = state.currencies.get(code=code.upper())
74
+ except ObjectDoesNotExist:
75
+ logger.error("currency_rate_not_found", code=code.upper())
76
+ raise HttpError(404, f"Currency rate for {code.upper()} not found")
77
+
78
+ return {currency.code: float(currency.rate)}
@@ -0,0 +1,7 @@
1
+ from django.db.models import TextChoices
2
+
3
+
4
+ class CurrencySource(TextChoices):
5
+ BCC = "bcc", "BCC"
6
+ OXR = "oxr", "Open Exchange Rates"
7
+
@@ -0,0 +1,41 @@
1
+ # Generated by Django 5.2.9 on 2025-12-19 14:34
2
+
3
+ import django.db.models.deletion
4
+ import uuid
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ initial = True
11
+
12
+ dependencies = [
13
+ ]
14
+
15
+ operations = [
16
+ migrations.CreateModel(
17
+ name='CurrencyState',
18
+ fields=[
19
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Unique identifier for this record', primary_key=True, serialize=False)),
20
+ ('created_at', models.DateTimeField(auto_now_add=True, help_text='Date and time when this record was created')),
21
+ ('updated_at', models.DateTimeField(auto_now=True, help_text='Date and time when this record was last updated')),
22
+ ('source', models.CharField(choices=[('bcc', 'BCC'), ('oxr', 'Open Exchange Rates')], max_length=10)),
23
+ ],
24
+ options={
25
+ 'abstract': False,
26
+ },
27
+ ),
28
+ migrations.CreateModel(
29
+ name='Currency',
30
+ fields=[
31
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Unique identifier for this record', primary_key=True, serialize=False)),
32
+ ('code', models.CharField(max_length=10)),
33
+ ('rate', models.DecimalField(decimal_places=4, max_digits=10)),
34
+ ('state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='currencies', to='currency.currencystate')),
35
+ ],
36
+ options={
37
+ 'ordering': ['code'],
38
+ 'indexes': [models.Index(fields=['code', 'state'], name='currency_cu_code_c68344_idx')],
39
+ },
40
+ ),
41
+ ]
File without changes
@@ -0,0 +1,100 @@
1
+ from typing import Optional
2
+ from django.db import models
3
+ from django.db import transaction
4
+ import structlog
5
+ from oxutils.models import (
6
+ UUIDPrimaryKeyMixin,
7
+ TimestampMixin,
8
+ )
9
+ from .enums import CurrencySource
10
+ from .utils import load_rates
11
+
12
+
13
+
14
+ logger = structlog.get_logger(__name__)
15
+
16
+
17
+ AVAILABLES_CURRENCIES = [
18
+ "AOA",
19
+ "AUD",
20
+ "BIF",
21
+ "CAD",
22
+ "CHF",
23
+ "CNY",
24
+ "EUR",
25
+ "GBP",
26
+ "JPY",
27
+ "RWF",
28
+ "TZS",
29
+ "UGX",
30
+ "USD",
31
+ "XAF",
32
+ "XDR",
33
+ "ZAR",
34
+ "ZMW"
35
+ ]
36
+
37
+
38
+ class CurrencyStateManager(models.Manager):
39
+ def latest(self):
40
+ return self.get_queryset().prefetch_related("currencies").latest("created_at")
41
+
42
+ class CurrencyState(UUIDPrimaryKeyMixin, TimestampMixin):
43
+ source = models.CharField(max_length=10, choices=CurrencySource.choices)
44
+ objects = CurrencyStateManager()
45
+
46
+ @classmethod
47
+ def sync(cls) -> Optional['CurrencyState']:
48
+ rates, source = load_rates()
49
+ currencies = []
50
+
51
+ if not rates:
52
+ logger.error("currency_state_sync_failed", source=source)
53
+ raise ValueError("No rates found")
54
+
55
+ with transaction.atomic():
56
+ state = cls.objects.create(source=source)
57
+
58
+ for rate in rates:
59
+ currency = Currency(
60
+ code=rate.currency,
61
+ rate=rate.amount,
62
+ state=state
63
+ )
64
+ currencies.append(currency)
65
+
66
+ Currency.objects.bulk_create(currencies)
67
+
68
+ logger.info("currency_state_synced", state=state.id, source=source)
69
+
70
+ return state
71
+
72
+
73
+ class Currency(UUIDPrimaryKeyMixin):
74
+ code = models.CharField(max_length=10)
75
+ rate = models.DecimalField(max_digits=10, decimal_places=4)
76
+ state = models.ForeignKey(
77
+ CurrencyState,
78
+ on_delete=models.CASCADE,
79
+ related_name="currencies"
80
+ )
81
+
82
+ class Meta:
83
+ ordering = ['code']
84
+ indexes = [
85
+ models.Index(fields=['code', 'state']),
86
+ ]
87
+
88
+ def __str__(self):
89
+ return f"{self.code} - {self.rate}"
90
+
91
+ def clean(self):
92
+ if self.code not in AVAILABLES_CURRENCIES:
93
+ raise ValueError(f"Invalid currency code: {self.code}")
94
+
95
+ if self.rate <= 0:
96
+ raise ValueError(f"Invalid currency rate: {self.rate}")
97
+
98
+ def save(self, *args, **kwargs):
99
+ self.clean()
100
+ super().save(*args, **kwargs)
@@ -0,0 +1,38 @@
1
+ from ninja import Schema
2
+ from datetime import datetime
3
+ from uuid import UUID
4
+ from decimal import Decimal
5
+
6
+
7
+ class CurrencySchema(Schema):
8
+ code: str
9
+ rate: Decimal
10
+
11
+ class Config:
12
+ from_attributes = True
13
+
14
+
15
+ class CurrencyStateSchema(Schema):
16
+ id: UUID
17
+ source: str
18
+ created_at: datetime
19
+ updated_at: datetime
20
+
21
+ class Config:
22
+ from_attributes = True
23
+
24
+
25
+ class CurrencyStateDetailSchema(Schema):
26
+ id: UUID
27
+ source: str
28
+ created_at: datetime
29
+ updated_at: datetime
30
+ currencies: dict[str, float]
31
+
32
+ class Config:
33
+ from_attributes = True
34
+
35
+
36
+ class CurrencyRateSchema(Schema):
37
+ code: str
38
+ rate: Decimal
@@ -0,0 +1,3 @@
1
+ from django.test import TestCase
2
+
3
+ # Create your tests here.
@@ -0,0 +1,31 @@
1
+ import time
2
+ from django.conf import settings
3
+ from bcc_rates import BCCBankSource, OXRBankSource, SourceValue
4
+ from oxutils.currency.enums import CurrencySource
5
+
6
+
7
+
8
+ def load_rates() -> tuple[list[SourceValue], CurrencySource]:
9
+ max_retries = 3
10
+ retry_count = 0
11
+
12
+ while retry_count < max_retries:
13
+ try:
14
+ bcc_source = BCCBankSource()
15
+ rates = bcc_source.sync(cache=True)
16
+ return rates, CurrencySource.BCC
17
+ except Exception as e:
18
+ retry_count += 1
19
+ if retry_count < max_retries:
20
+ time.sleep(1)
21
+ else:
22
+ if not getattr(settings, 'OXI_BCC_FALLBACK_ON_OXR', False):
23
+ raise Exception(f"Failed to load rates from BCC: {str(e)}")
24
+ break
25
+
26
+ try:
27
+ oxr_source = OXRBankSource()
28
+ rates = oxr_source.sync(cache=True)
29
+ return rates, CurrencySource.OXR
30
+ except Exception as e:
31
+ raise Exception(f"Failed to load rates from both BCC and OXR: {str(e)}")
oxutils/functions.py CHANGED
@@ -6,12 +6,15 @@ from ninja_extra.exceptions import ValidationError
6
6
 
7
7
 
8
8
  def get_absolute_url(url: str, request=None):
9
+ if url.startswith('http'):
10
+ return url
11
+
9
12
  if request:
10
13
  # Build absolute URL using request
11
14
  return request.build_absolute_uri(url)
12
15
  else:
13
- # Fallback: build URL using MEDIA_URL and domain
14
- base_url = getattr(settings, 'SITE_URL', 'http://localhost:8000')
16
+ # Fallback: build URL using SITE_DOMAIN and domain
17
+ base_url = getattr(settings, 'SITE_DOMAIN', 'http://localhost:8000')
15
18
  return urljoin(base_url, url)
16
19
 
17
20
 
@@ -2,7 +2,6 @@ from django.contrib.sites.shortcuts import RequestSite
2
2
  from django.dispatch import receiver
3
3
  import structlog
4
4
  from django_structlog import signals
5
- from cid.locals import get_cid
6
5
  from oxutils.settings import oxi_settings
7
6
 
8
7
 
@@ -12,7 +11,6 @@ def bind_domain(request, logger, **kwargs):
12
11
  current_site = RequestSite(request)
13
12
  structlog.contextvars.bind_contextvars(
14
13
  domain=current_site.domain,
15
- cid=get_cid(),
16
14
  user_id=str(request.user.pk),
17
15
  service=oxi_settings.service_name
18
16
  )
oxutils/settings.py CHANGED
@@ -21,6 +21,8 @@ class OxUtilsSettings(BaseSettings):
21
21
 
22
22
  # Service
23
23
  service_name: Optional[str] = 'Oxutils'
24
+ site_name: Optional[str] = 'Oxiliere'
25
+ site_domain: Optional[str] = 'oxiliere.com'
24
26
 
25
27
  # Auth JWT Settings (JWT_SIGNING_KEY)
26
28
  jwt_signing_key: Optional[str] = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oxutils
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Production-ready utilities for Django applications in the Oxiliere ecosystem
5
5
  Keywords: django,utilities,jwt,s3,audit,logging,celery,structlog
6
6
  Author: Edimedia Mutoke
@@ -13,16 +13,15 @@ Classifier: Intended Audience :: Developers
13
13
  Classifier: License :: OSI Approved :: Apache Software License
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
18
17
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
18
  Classifier: Topic :: Internet :: WWW/HTTP
19
+ Requires-Dist: bcc-rates>=1.1.0
20
20
  Requires-Dist: boto3>=1.41.5
21
21
  Requires-Dist: celery>=5.5.3
22
22
  Requires-Dist: cryptography>=46.0.3
23
23
  Requires-Dist: django-auditlog>=3.3.0
24
24
  Requires-Dist: django-celery-results>=2.6.0
25
- Requires-Dist: django-cid>=3.0
26
25
  Requires-Dist: django-extensions>=4.1
27
26
  Requires-Dist: django-ninja>=1.5.0
28
27
  Requires-Dist: django-ninja-extra>=0.30.6
@@ -32,7 +31,7 @@ Requires-Dist: jwcrypto>=1.5.6
32
31
  Requires-Dist: pydantic-settings>=2.12.0
33
32
  Requires-Dist: pyjwt>=2.10.1
34
33
  Requires-Dist: requests>=2.32.5
35
- Requires-Python: >=3.11
34
+ Requires-Python: >=3.12
36
35
  Project-URL: Changelog, https://github.com/oxiliere/oxutils/blob/main/CHANGELOG.md
37
36
  Project-URL: Documentation, https://github.com/oxiliere/oxutils/tree/main/docs
38
37
  Project-URL: Homepage, https://github.com/oxiliere/oxutils
@@ -45,9 +44,9 @@ Description-Content-Type: text/markdown
45
44
  **Production-ready utilities for Django applications in the Oxiliere ecosystem.**
46
45
 
47
46
  [![PyPI version](https://img.shields.io/pypi/v/oxutils.svg)](https://pypi.org/project/oxutils/)
48
- [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/)
47
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/)
49
48
  [![Django 5.0+](https://img.shields.io/badge/django-5.0+-green.svg)](https://www.djangoproject.com/)
50
- [![Tests](https://img.shields.io/badge/tests-126%20passed-success.svg)](tests/)
49
+ [![Tests](https://img.shields.io/badge/tests-145%20passed-success.svg)](tests/)
51
50
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
52
51
  [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
53
52
 
@@ -55,11 +54,12 @@ Description-Content-Type: text/markdown
55
54
 
56
55
  - 🔐 **JWT Authentication** - RS256 with JWKS caching
57
56
  - 📦 **S3 Storage** - Static, media, private, and log backends
58
- - 📝 **Structured Logging** - JSON logs with correlation IDs
57
+ - 📝 **Structured Logging** - JSON logs with automatic request tracking
59
58
  - 🔍 **Audit System** - Change tracking with S3 export
60
59
  - ⚙️ **Celery Integration** - Pre-configured task processing
61
60
  - 🛠️ **Django Mixins** - UUID, timestamps, user tracking
62
61
  - ⚡ **Custom Exceptions** - Standardized API errors
62
+ - 🎨 **Context Processors** - Site name and domain for templates
63
63
 
64
64
  ---
65
65
 
@@ -82,12 +82,12 @@ uv add oxutils
82
82
  from oxutils.conf import UTILS_APPS, AUDIT_MIDDLEWARE
83
83
 
84
84
  INSTALLED_APPS = [
85
- *UTILS_APPS, # structlog, auditlog, cid, celery_results
85
+ *UTILS_APPS, # structlog, auditlog, celery_results
86
86
  # your apps...
87
87
  ]
88
88
 
89
89
  MIDDLEWARE = [
90
- *AUDIT_MIDDLEWARE, # CID, Auditlog, RequestMiddleware
90
+ *AUDIT_MIDDLEWARE, # RequestMiddleware, Auditlog
91
91
  # your middleware...
92
92
  ]
93
93
  ```
@@ -126,6 +126,17 @@ class Product(BaseModelMixin): # UUID + timestamps + is_active
126
126
  # Custom Exceptions
127
127
  from oxutils.exceptions import NotFoundException
128
128
  raise NotFoundException(detail="User not found")
129
+
130
+ # Context Processors
131
+ # settings.py
132
+ TEMPLATES = [{
133
+ 'OPTIONS': {
134
+ 'context_processors': [
135
+ 'oxutils.context.site_name_processor.site_name',
136
+ ],
137
+ },
138
+ }]
139
+ # Now {{ site_name }} and {{ site_domain }} are available in templates
129
140
  ```
130
141
 
131
142
  ## Documentation
@@ -140,7 +151,7 @@ raise NotFoundException(detail="User not found")
140
151
 
141
152
  ## Requirements
142
153
 
143
- - Python 3.11+
154
+ - Python 3.12+
144
155
  - Django 5.0+
145
156
  - PostgreSQL (recommended)
146
157
 
@@ -150,7 +161,7 @@ raise NotFoundException(detail="User not found")
150
161
  git clone https://github.com/oxiliere/oxutils.git
151
162
  cd oxutils
152
163
  uv sync
153
- uv run pytest # 126 tests
164
+ uv run pytest # 145 tests
154
165
  ```
155
166
 
156
167
  ### Creating Migrations
@@ -1,4 +1,4 @@
1
- oxutils/__init__.py,sha256=3P1RgwJOYUc9uXjd_PhSprxHiOLf_qZrSxX2nuxBRWU,536
1
+ oxutils/__init__.py,sha256=ulW43bC3kZ6K4NPfptTEwJDs1Ho6wUf_NCZWaTJgClw,536
2
2
  oxutils/apps.py,sha256=8pO8eXUZeKYn8fPo0rkoytmHACwDNuTNhdRcpkPTxGM,347
3
3
  oxutils/audit/__init__.py,sha256=uonc00G73Xm7RwRHVWD-wBn8lJYNCq3iBgnRGMWAEWs,583
4
4
  oxutils/audit/apps.py,sha256=xvnmB5Z6nLV7ejzhSeQbesTkwRoFygoPFob8H5QTHgU,304
@@ -7,24 +7,37 @@ oxutils/audit/masks.py,sha256=BRCz2m8dbaLgqn5umxpWCwn9mT6Z_ww_WIedl36AmPM,2345
7
7
  oxutils/audit/migrations/0001_initial.py,sha256=xDOxV6NqkU8yuEDPvqG2AKANKrOJsOIAEJn5IbuLLFU,2151
8
8
  oxutils/audit/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  oxutils/audit/models.py,sha256=NWzZjwgRGB212PeSb0E_aSxSComrTSR0knt2aBCiWhg,2100
10
- oxutils/audit/settings.py,sha256=E4AoTpbvL1svDogux-OTjAdccD5LzyJ1G-lBeCZTbDU,353
10
+ oxutils/audit/settings.py,sha256=NNCFaN6tilDj-0WsVRK9HJNcr2nJOBYJT9E8cY5n9iI,133
11
+ oxutils/audit/utils.py,sha256=VJESUbMb5bC155XNlkIvr8DMc_o6ZTAIn5r8NDr7k_E,581
11
12
  oxutils/celery/__init__.py,sha256=29jo4DfdvOoThX-jfL0ZiDjsy3-Z_fNhwHVJaLO5rsk,29
12
13
  oxutils/celery/base.py,sha256=qLlBU2XvT2zj8qszy8togqH7hM_wUYyWWA3JAQPPJx0,3378
13
14
  oxutils/celery/settings.py,sha256=njhHBErpcFczV2e23NCPX_Jxs015jr4dIig4Is_wbgE,33
14
- oxutils/conf.py,sha256=JT0Zj4Wmn9HCEDpk7eOIY47YLBGP793tstsm34cLb1A,296
15
+ oxutils/conf.py,sha256=TR0RIVaLMHvG0gm3NgbKsoU25eJFBjItAhknFJdiOiQ,231
16
+ oxutils/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ oxutils/context/site_name_processor.py,sha256=1gc0Td_3HVlUn9ThhQBCQ8kfnRnI88bEflK9vEzTvEc,225
18
+ oxutils/currency/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ oxutils/currency/admin.py,sha256=cCd4tXIZTctr3Ksa1jTEpaECw-on7aRHibAfYxEjGek,1831
20
+ oxutils/currency/apps.py,sha256=w35QaiH3BIjCYv2IU6AadENAyBOw7Hmejy9kT5e_law,194
21
+ oxutils/currency/controllers.py,sha256=6LEDOLghPxqeqcaqehJShTTVb2w6H0BiV7ZF5ZSnO1o,3025
22
+ oxutils/currency/enums.py,sha256=AHm8zpYCyGv0WMnh8XHLa3cAJnQx3aNOCjGug2Q94VQ,141
23
+ oxutils/currency/migrations/0001_initial.py,sha256=GHypakExy4jW5iQ6hB9x2UUjBoPgaz0OaHNTcBU64Pw,1707
24
+ oxutils/currency/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ oxutils/currency/models.py,sha256=-NrVq-degRc4sAO2BZ_LqSuIAP-IzCwwZJVxfYlNL8o,2448
26
+ oxutils/currency/schemas.py,sha256=4DyEjpL_HaMNDhKyu48fZtcXfRdemVP4rdyHUFit8uI,658
27
+ oxutils/currency/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2nk6VY-g,60
28
+ oxutils/currency/utils.py,sha256=wwXlYxG8tUJ6M_n-tnil3fMmRyz7dCfNvSuEOi6Cq1E,1025
15
29
  oxutils/enums/__init__.py,sha256=gFhZG8ER6ArGZO5agWhdfs7NiD2h9FzrzfQRHq18dD0,40
16
30
  oxutils/enums/audit.py,sha256=ju2Z9CrtdyPziRQ7oOe4Ygw85t9sW3jynO_1DkgZoAM,126
17
31
  oxutils/enums/invoices.py,sha256=E33QGQeutZUqvlovJY0VGDxWUb0i_kdfhEiir1ARKuQ,201
18
32
  oxutils/exceptions.py,sha256=CCjENOD0of6_noif2ajrpfbBLoG16DWa46iB9_uEe3M,3592
19
- oxutils/functions.py,sha256=p4SycMykom6H2AoryX-X4W_Acphf2_tx-LY2WxFD0Xc,3190
33
+ oxutils/functions.py,sha256=4stHj94VebWX0s1XeWshubMD2v8w8QztTWppbkTE_Gg,3246
20
34
  oxutils/jwt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
35
  oxutils/jwt/auth.py,sha256=rO-xWNfug9Ok6zA7EIPvVkpD8TBUdq05CdrnMrL-t9Q,1597
22
36
  oxutils/jwt/client.py,sha256=bskLpmSBrehi_snbo3Qbq1m99Kbfg2GP7jqfcXKHvys,3341
23
37
  oxutils/jwt/constants.py,sha256=MUahZjm8plTYpHjLOMQCuH0H18lkIwS45EtRm617wq8,26
24
- oxutils/locale/fr/LC_MESSAGES/django.mo,sha256=Qk5R90E-JqJX2JxSnveKXxecKVHR-a76uFlwkHTF7f0,8114
25
38
  oxutils/locale/fr/LC_MESSAGES/django.po,sha256=APXt_8R99seCWjJyS5ELOawvRLvUqqBT32O252BaG5s,7971
26
39
  oxutils/logger/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
- oxutils/logger/receivers.py,sha256=U8JVjHRK1zUaCRnPn6p1qsm6FFXgTkXp67PpJ1LnjgU,542
40
+ oxutils/logger/receivers.py,sha256=EBpkwMCHYacOJvuOPzUtM_8ttWWetz17kIwoudGiV34,488
28
41
  oxutils/logger/settings.py,sha256=aiKiJqNNkw1g5vQgjk3Zfh6UgY7jx-lbmcFgATiXrGI,1805
29
42
  oxutils/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
43
  oxutils/mixins/base.py,sha256=0cGY4mGKhL-hJTEBsbETYiaKMVuUgio5DISCv5iYtGI,589
@@ -38,8 +51,8 @@ oxutils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
51
  oxutils/s3/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
52
  oxutils/s3/settings.py,sha256=NIlVhaOzWdsepOgCpxdTTJRHfM0tM5EcAoy4kaFC1R8,1190
40
53
  oxutils/s3/storages.py,sha256=gjQg05edVn6NuyfJZ-NwUB2lRWwg8GqgzHB8I1D5vbI,5402
41
- oxutils/settings.py,sha256=PLcBElUpsZe7imvcRXA_-J1r9Z8Mvmu03Tk2xrdTlKc,9652
54
+ oxutils/settings.py,sha256=mp_ZSf8bcCUe2Zc9YdOe_EtltBgIY2n_8lUERQtsIFk,9742
42
55
  oxutils/types.py,sha256=DIz8YK8xMpLc7FYbf88yEElyLsYN_-rbvaZXvENQkOQ,234
43
- oxutils-0.1.2.dist-info/WHEEL,sha256=4n27za1eEkOnA7dNjN6C5-O2rUiw6iapszm14Uj-Qmk,79
44
- oxutils-0.1.2.dist-info/METADATA,sha256=Ud9P1yRUNUumfv-LRED-rRCHg65KKy_QAnhlZWDet-E,5934
45
- oxutils-0.1.2.dist-info/RECORD,,
56
+ oxutils-0.1.4.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
57
+ oxutils-0.1.4.dist-info/METADATA,sha256=mXLenN2oqYVUWo1nQL19Z0P9YXqJ9j6yKKDOMLMTXCc,6205
58
+ oxutils-0.1.4.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.8.13
2
+ Generator: uv 0.8.24
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
Binary file