oxutils 0.1.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.
@@ -0,0 +1,368 @@
1
+ # Oxiliere Utils.
2
+ # Copyright (C) Oxiliere
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # Edimedia Mutoke <eddycondor07@gmail.com>, 2025.
5
+ #
6
+ msgid ""
7
+ msgstr ""
8
+ "Project-Id-Version: Oxutils 1.0\n"
9
+ "Report-Msgid-Bugs-To: \n"
10
+ "POT-Creation-Date: 2025-12-02 12:14+0200\n"
11
+ "PO-Revision-Date: 2025-12-02 12:15+0200\n"
12
+ "Last-Translator: Oxiliere Team\n"
13
+ "Language-Team: French\n"
14
+ "Language: fr\n"
15
+ "MIME-Version: 1.0\n"
16
+ "Content-Type: text/plain; charset=UTF-8\n"
17
+ "Content-Transfer-Encoding: 8bit\n"
18
+ "Plural-Forms: nplurals=2; plural=(n > 1);\n"
19
+
20
+ msgid "Oxiliere Utilities"
21
+ msgstr "Utilitaires Oxiliere"
22
+
23
+ msgid "Oxutils Export"
24
+ msgstr "Export Oxutils"
25
+
26
+ msgid "Failed"
27
+ msgstr "Échoué"
28
+
29
+ msgid "Pending"
30
+ msgstr "En attente"
31
+
32
+ msgid "Success"
33
+ msgstr "Succès"
34
+
35
+ msgid "We encountered an error, please try again later."
36
+ msgstr "Nous avons rencontré une erreur, veuillez réessayer plus tard."
37
+
38
+ msgid "The requested resource does not exist"
39
+ msgstr "La ressource demandée n'existe pas"
40
+
41
+ msgid "Validation error"
42
+ msgstr "Erreur de validation"
43
+
44
+ msgid "The operation conflicts with existing data"
45
+ msgstr "L'opération est en conflit avec les données existantes"
46
+
47
+ msgid "A resource with these details already exists"
48
+ msgstr "Une ressource avec ces détails existe déjà"
49
+
50
+ msgid "You do not have permission to perform this action"
51
+ msgstr "Vous n'avez pas la permission d'effectuer cette action"
52
+
53
+ msgid "Authentication is required"
54
+ msgstr "L'authentification est requise"
55
+
56
+ msgid "Invalid parameter provided"
57
+ msgstr "Paramètre invalide fourni"
58
+
59
+ msgid "Required parameter is missing"
60
+ msgstr "Paramètre requis manquant"
61
+
62
+ msgid "An unexpected error occurred while processing your request"
63
+ msgstr "Une erreur inattendue s'est produite lors du traitement de votre demande"
64
+
65
+ msgid "One or more referenced resources do not exist"
66
+ msgstr "Une ou plusieurs ressources référencées n'existent pas"
67
+
68
+ #, python-brace-format
69
+ msgid "Required parameter {exc} is missing"
70
+ msgstr "Le paramètre requis {exc} est manquant"
71
+
72
+ msgid ""
73
+ "An unexpected error occurred. Please try again later or contact support."
74
+ msgstr "Une erreur inattendue s'est produite. Veuillez réessayer plus tard ou contacter le support."
75
+
76
+ msgid "Credit card"
77
+ msgstr "Carte bancaire"
78
+
79
+ msgid "PayPal"
80
+ msgstr "PayPal"
81
+
82
+ msgid "Bank transfer"
83
+ msgstr "Virement bancaire"
84
+
85
+ msgid "Stripe"
86
+ msgstr "Stripe"
87
+
88
+ msgid "US Dollar"
89
+ msgstr "Dollar américain"
90
+
91
+ msgid "Congolese Franc"
92
+ msgstr "Franc congolais"
93
+
94
+ msgid "billing name"
95
+ msgstr "Nom de facturation"
96
+
97
+ msgid "Full name for billing"
98
+ msgstr "Nom complet pour la facturation"
99
+
100
+ msgid "billing email"
101
+ msgstr "email de facturation"
102
+
103
+ msgid "Email to receive invoices"
104
+ msgstr "Email pour recevoir les factures"
105
+
106
+ msgid "company name"
107
+ msgstr "Nom de l'entreprise"
108
+
109
+ msgid "Company name (optional)"
110
+ msgstr "Nom de l'entreprise (optionnel)"
111
+
112
+ msgid "VAT number"
113
+ msgstr "Numéro de TVA"
114
+
115
+ msgid "VAT or tax identification number"
116
+ msgstr "Numéro de TVA ou d'identification fiscale"
117
+
118
+ msgid "address"
119
+ msgstr "Adresse"
120
+
121
+ msgid "city"
122
+ msgstr "Ville"
123
+
124
+ msgid "postal code"
125
+ msgstr "Code postal"
126
+
127
+ msgid "country"
128
+ msgstr "Pays"
129
+
130
+ msgid "ISO 3166-1 alpha-2 country code"
131
+ msgstr "Code pays ISO 3166-1 alpha-2"
132
+
133
+ msgid "preferred currency"
134
+ msgstr "Devise préférée"
135
+
136
+ msgid "preferred payment method"
137
+ msgstr "Méthode de paiement préférée"
138
+
139
+ msgid "Stripe customer ID"
140
+ msgstr "ID client Stripe"
141
+
142
+ msgid "Stripe customer identifier"
143
+ msgstr "Identifiant client Stripe"
144
+
145
+ msgid "automatic payment"
146
+ msgstr "Paiement automatique"
147
+
148
+ msgid "Enable automatic invoice payment"
149
+ msgstr "Activer le paiement automatique des factures"
150
+
151
+ msgid "billing notes"
152
+ msgstr "Notes de facturation"
153
+
154
+ msgid "Custom notes to include on invoices"
155
+ msgstr "Notes personnalisées à inclure sur les factures"
156
+
157
+ msgid "Billing information"
158
+ msgstr "Informations de facturation"
159
+
160
+ msgid "invoice number"
161
+ msgstr "Numéro de facture"
162
+
163
+ msgid "Unique invoice number"
164
+ msgstr "Numéro unique de la facture"
165
+
166
+ msgid "status"
167
+ msgstr "Statut"
168
+
169
+ msgid "Invoice status"
170
+ msgstr "Statut de la facture"
171
+
172
+ msgid "subtotal"
173
+ msgstr "Sous-total"
174
+
175
+ msgid "Amount excluding taxes"
176
+ msgstr "Montant hors taxes"
177
+
178
+ msgid "tax rate"
179
+ msgstr "Taux de taxe"
180
+
181
+ msgid "Tax rate as percentage"
182
+ msgstr "Taux de taxe en pourcentage"
183
+
184
+ msgid "tax amount"
185
+ msgstr "Montant de la taxe"
186
+
187
+ msgid "Tax amount"
188
+ msgstr "Montant de la taxe"
189
+
190
+ msgid "total"
191
+ msgstr "Total"
192
+
193
+ msgid "Total amount including tax"
194
+ msgstr "Montant total TTC"
195
+
196
+ msgid "currency"
197
+ msgstr "Devise"
198
+
199
+ msgid "ISO currency code (CDF, USD, etc.)"
200
+ msgstr "Code devise ISO (CDF, USD, etc.)"
201
+
202
+ msgid "issue date"
203
+ msgstr "Date d'émission"
204
+
205
+ msgid "Invoice issue date"
206
+ msgstr "Date d'émission de la facture"
207
+
208
+ msgid "due date"
209
+ msgstr "Date d'échéance"
210
+
211
+ msgid "Payment due date"
212
+ msgstr "Date limite de paiement"
213
+
214
+ msgid "payment date"
215
+ msgstr "Date de paiement"
216
+
217
+ msgid "Payment date and time"
218
+ msgstr "Date et heure du paiement"
219
+
220
+ msgid "period start"
221
+ msgstr "Début de période"
222
+
223
+ msgid "Billing period start date"
224
+ msgstr "Date de début de la période facturée"
225
+
226
+ msgid "period end"
227
+ msgstr "Fin de période"
228
+
229
+ msgid "Billing period end date"
230
+ msgstr "Date de fin de la période facturée"
231
+
232
+ msgid "description"
233
+ msgstr "Description"
234
+
235
+ msgid "Detailed invoice description"
236
+ msgstr "Description détaillée de la facture"
237
+
238
+ msgid "notes"
239
+ msgstr "Notes"
240
+
241
+ msgid "Internal notes about the invoice"
242
+ msgstr "Notes internes sur la facture"
243
+
244
+ msgid "payment reference"
245
+ msgstr "Référence de paiement"
246
+
247
+ msgid "External payment system reference (Stripe, etc.)"
248
+ msgstr "Référence du système de paiement externe (Stripe, etc.)"
249
+
250
+ msgid "Invoice"
251
+ msgstr "Facture"
252
+
253
+ msgid "Invoices"
254
+ msgstr "Factures"
255
+
256
+ msgid "service name"
257
+ msgstr "Nom du service"
258
+
259
+ msgid "Name of the billed service or product"
260
+ msgstr "Nom du service ou produit facturé"
261
+
262
+ msgid "Detailed service description"
263
+ msgstr "Description détaillée du service"
264
+
265
+ msgid "quantity"
266
+ msgstr "Quantité"
267
+
268
+ msgid "Service quantity (hours, units, etc.)"
269
+ msgstr "Quantité du service (heures, unités, etc.)"
270
+
271
+ msgid "unit price"
272
+ msgstr "Prix unitaire"
273
+
274
+ msgid "Unit price excluding taxes"
275
+ msgstr "Prix unitaire hors taxes"
276
+
277
+ msgid "total price"
278
+ msgstr "Prix total"
279
+
280
+ msgid "Total price for this item (quantity × unit price)"
281
+ msgstr "Prix total pour cet élément (quantité × prix unitaire)"
282
+
283
+ msgid "metadata"
284
+ msgstr "Métadonnées"
285
+
286
+ msgid "Additional data in JSON format"
287
+ msgstr "Données supplémentaires en format JSON"
288
+
289
+ msgid "Invoice item"
290
+ msgstr "Élément de facture"
291
+
292
+ msgid "Invoice items"
293
+ msgstr "Éléments de facture"
294
+
295
+ msgid "Approved"
296
+ msgstr "Approuvé"
297
+
298
+ msgid "Rejected"
299
+ msgstr "Rejeté"
300
+
301
+ msgid "Processed"
302
+ msgstr "Traité"
303
+
304
+ msgid "Cancelled"
305
+ msgstr "Annulé"
306
+
307
+ msgid "Duplicate payment"
308
+ msgstr "Paiement en double"
309
+
310
+ msgid "Service not received"
311
+ msgstr "Service non reçu"
312
+
313
+ msgid "Billing error"
314
+ msgstr "Erreur de facturation"
315
+
316
+ msgid "Cancellation"
317
+ msgstr "Annulation"
318
+
319
+ msgid "Technical issue"
320
+ msgstr "Problème technique"
321
+
322
+ msgid "Other"
323
+ msgstr "Autre"
324
+
325
+ msgid "Refund request status"
326
+ msgstr "Statut de la demande de remboursement"
327
+
328
+ msgid "reason"
329
+ msgstr "raison"
330
+
331
+ msgid "Reason for refund request"
332
+ msgstr "Raison de la demande de remboursement"
333
+
334
+ msgid "Detailed description of the refund request"
335
+ msgstr "Description détaillée de la demande de remboursement"
336
+
337
+ msgid "requested amount"
338
+ msgstr "Montant demandé"
339
+
340
+ msgid "Requested refund amount"
341
+ msgstr "Montant du remboursement demandé"
342
+
343
+ msgid "approved amount"
344
+ msgstr "Montant approuvé"
345
+
346
+ msgid "Approved refund amount"
347
+ msgstr "Montant du remboursement approuvé"
348
+
349
+ msgid "processing date"
350
+ msgstr "date de traitement"
351
+
352
+ msgid "Refund processing date and time"
353
+ msgstr "Date et heure du traitement du remboursement"
354
+
355
+ msgid "admin notes"
356
+ msgstr "Notes administratives"
357
+
358
+ msgid "Internal administrator notes"
359
+ msgstr "Notes internes de l'administrateur"
360
+
361
+ msgid "refund reference"
362
+ msgstr "référence de remboursement"
363
+
364
+ msgid "Refund request"
365
+ msgstr "Demande de remboursement"
366
+
367
+ msgid "Refund requests"
368
+ msgstr "Demandes de remboursement"
File without changes
@@ -0,0 +1,18 @@
1
+ from django.contrib.sites.shortcuts import RequestSite
2
+ from django.dispatch import receiver
3
+ import structlog
4
+ from django_structlog import signals
5
+ from cid.locals import get_cid
6
+ from oxutils.settings import oxi_settings
7
+
8
+
9
+
10
+ @receiver(signals.bind_extra_request_metadata)
11
+ def bind_domain(request, logger, **kwargs):
12
+ current_site = RequestSite(request)
13
+ structlog.contextvars.bind_contextvars(
14
+ domain=current_site.domain,
15
+ cid=get_cid(),
16
+ user_id=str(request.user.pk),
17
+ service=oxi_settings.service_name
18
+ )
@@ -0,0 +1,63 @@
1
+ import structlog
2
+
3
+
4
+
5
+
6
+ DJANGO_STRUCTLOG_CELERY_ENABLED = True
7
+ DJANGO_STRUCTLOG_IP_LOGGING_ENABLED = True
8
+ DJANGO_STRUCTLOG_USER_ID_FIELD = 'pk'
9
+ DJANGO_STRUCTLOG_COMMAND_LOGGING_ENABLED = True
10
+
11
+
12
+ LOGGING = {
13
+ "version": 1,
14
+ "disable_existing_loggers": False,
15
+ "formatters": {
16
+ "json_formatter": {
17
+ "()": structlog.stdlib.ProcessorFormatter,
18
+ "processor": structlog.processors.JSONRenderer(),
19
+ },
20
+ "plain_console": {
21
+ "()": structlog.stdlib.ProcessorFormatter,
22
+ "processor": structlog.dev.ConsoleRenderer(),
23
+ },
24
+ },
25
+ "handlers": {
26
+ "console": {
27
+ "class": "logging.StreamHandler",
28
+ "formatter": "plain_console",
29
+ },
30
+ "json_file": {
31
+ "class": "logging.handlers.WatchedFileHandler",
32
+ "filename": "logs/json.log",
33
+ "formatter": "json_formatter",
34
+ },
35
+ },
36
+ "loggers": {
37
+ "django_structlog": {
38
+ "handlers": ["console", "json_file"],
39
+ "level": "INFO",
40
+ },
41
+ "oxiliere_log": {
42
+ "handlers": ["console", "json_file"],
43
+ "level": "INFO",
44
+ },
45
+ }
46
+ }
47
+
48
+ structlog.configure(
49
+ processors=[
50
+ structlog.contextvars.merge_contextvars,
51
+ structlog.stdlib.filter_by_level,
52
+ structlog.processors.TimeStamper(fmt="iso"),
53
+ structlog.stdlib.add_logger_name,
54
+ structlog.stdlib.add_log_level,
55
+ structlog.stdlib.PositionalArgumentsFormatter(),
56
+ structlog.processors.StackInfoRenderer(),
57
+ structlog.processors.format_exc_info,
58
+ structlog.processors.UnicodeDecoder(),
59
+ structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
60
+ ],
61
+ logger_factory=structlog.stdlib.LoggerFactory(),
62
+ cache_logger_on_first_use=True,
63
+ )
File without changes
oxutils/mixins/base.py ADDED
@@ -0,0 +1,21 @@
1
+
2
+ class DetailDictMixin:
3
+ default_detail: str
4
+ default_code: str
5
+
6
+ def __init__(self, detail=None, code=None) -> None:
7
+ """
8
+ Builds a detail dictionary for the error to give more information to API
9
+ users.
10
+ """
11
+ detail_dict = {"detail": self.default_detail, "code": self.default_code}
12
+
13
+ if isinstance(detail, dict):
14
+ detail_dict.update(detail)
15
+ elif detail is not None:
16
+ detail_dict["detail"] = detail
17
+
18
+ if code is not None:
19
+ detail_dict["code"] = code
20
+
21
+ super().__init__(detail_dict)
@@ -0,0 +1,13 @@
1
+ from typing import Optional, Dict, Any
2
+ from ninja import Schema
3
+
4
+
5
+
6
+ class ResponseSchema(Schema):
7
+ """
8
+ Standardized error response schema matching APIException format.
9
+ Use this for documenting error responses in API endpoints.
10
+ """
11
+ detail: str
12
+ code: str
13
+ errors: Optional[Dict[str, Any]] = None
@@ -0,0 +1,146 @@
1
+ from logging import Logger
2
+ from django.utils.translation import gettext_lazy as _
3
+ from auditlog.signals import accessed
4
+ from oxutils.settings import oxi_settings
5
+
6
+
7
+
8
+
9
+
10
+ class BaseService:
11
+ """
12
+ Base service class with exception handling
13
+
14
+ Services should inherit from this class and override inner_exception_handler
15
+ to handle service-specific exceptions.
16
+ """
17
+
18
+ logger: Logger | None = None
19
+
20
+
21
+ def object_accessed(self, instance_class, instance):
22
+ if oxi_settings.log_access:
23
+ accessed(instance_class, instance)
24
+
25
+ def inner_exception_handler(self, exc: Exception, logger: Logger):
26
+ """
27
+ Handle service-specific exceptions
28
+
29
+ Override this method in child classes to handle custom exceptions.
30
+ Should raise an APIException subclass if the exception is handled,
31
+ or re-raise the original exception if not handled.
32
+
33
+ Args:
34
+ exc: The exception to handle
35
+ logger: Logger instance for logging
36
+
37
+ Raises:
38
+ APIException: If the exception is handled
39
+ Exception: Re-raises the original exception if not handled
40
+ """
41
+ # Default implementation does nothing, child classes should override
42
+ raise exc
43
+
44
+ def exception_handler(self, exc: Exception, logger: Logger | None = None):
45
+ """
46
+ Handle all exceptions with a standardized approach
47
+
48
+ This method first calls inner_exception_handler for service-specific exceptions,
49
+ then handles common Django and Python exceptions.
50
+
51
+ Args:
52
+ exc: The exception to handle
53
+ logger: Optional logger instance
54
+
55
+ Raises:
56
+ APIException: Always raises an appropriate APIException subclass
57
+ """
58
+ from oxutils.exceptions import (
59
+ NotFoundException,
60
+ ValidationException,
61
+ ConflictException,
62
+ DuplicateEntryException,
63
+ PermissionDeniedException,
64
+ InvalidParameterException,
65
+ MissingParameterException,
66
+ InternalErrorException,
67
+ )
68
+ from django.core.exceptions import ValidationError, ObjectDoesNotExist
69
+ from django.db import IntegrityError
70
+ import logging
71
+
72
+ if self.logger:
73
+ logger = self.logger
74
+
75
+ if logger is None:
76
+ logger = logging.getLogger(__name__)
77
+
78
+ # Log the exception
79
+ logger.error(f"Service exception: {type(exc).__name__} - {str(exc)}")
80
+
81
+ try:
82
+ # First, try to handle service-specific exceptions
83
+ self.inner_exception_handler(exc, logger)
84
+ except Exception as inner_exc:
85
+ # If inner_exception_handler raised an APIException, re-raise it
86
+ from oxutils.exceptions import APIException
87
+ if isinstance(inner_exc, APIException):
88
+ raise inner_exc
89
+
90
+ # Otherwise, continue with standard exception handling
91
+ exc = inner_exc
92
+
93
+ # Handle Django validation errors
94
+ if isinstance(exc, ValidationError):
95
+ detail = str(exc)
96
+ if hasattr(exc, 'message_dict'):
97
+ detail = {'detail': str(exc), 'errors': exc.message_dict}
98
+ raise ValidationException(detail=detail) from exc
99
+
100
+ # Handle object not found errors
101
+ if isinstance(exc, ObjectDoesNotExist):
102
+ raise NotFoundException(detail=str(exc) or None) from exc
103
+
104
+ # Handle integrity errors (duplicate entries, foreign key violations)
105
+ if isinstance(exc, IntegrityError):
106
+ error_message = str(exc).lower()
107
+
108
+ if 'unique' in error_message or 'duplicate' in error_message:
109
+ raise DuplicateEntryException() from exc
110
+
111
+ if 'foreign key' in error_message:
112
+ raise InvalidParameterException(
113
+ detail=_('One or more referenced resources do not exist')
114
+ ) from exc
115
+
116
+ raise ConflictException() from exc
117
+
118
+ # Handle value errors (invalid parameters)
119
+ if isinstance(exc, ValueError):
120
+ raise InvalidParameterException(detail=str(exc)) from exc
121
+
122
+ # Handle permission errors
123
+ if isinstance(exc, PermissionError):
124
+ raise PermissionDeniedException(detail=str(exc) or None) from exc
125
+
126
+ # Handle type errors (usually programming errors)
127
+ if isinstance(exc, TypeError):
128
+ logger.exception("Type error in service")
129
+ raise InternalErrorException() from exc
130
+
131
+ # Handle key errors (missing required data)
132
+ if isinstance(exc, KeyError):
133
+ raise MissingParameterException(
134
+ detail=_('Required parameter {exc} is missing').format(exc=exc)
135
+ ) from exc
136
+
137
+ # Handle attribute errors
138
+ if isinstance(exc, AttributeError):
139
+ logger.exception("Attribute error in service")
140
+ raise InternalErrorException() from exc
141
+
142
+ # Default handler for unknown exceptions
143
+ logger.exception(f"Unhandled exception in service: {type(exc).__name__}")
144
+ raise InternalErrorException(
145
+ detail=_('An unexpected error occurred. Please try again later or contact support.')
146
+ ) from exc
@@ -0,0 +1,3 @@
1
+ from .base import *
2
+ from .invoice import *
3
+ from .billing import BillingMixin