factpulse 1.0.9__py3-none-any.whl → 3.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of factpulse might be problematic. Click here for more details.
- factpulse/__init__.py +275 -203
- factpulse/api/__init__.py +5 -3
- factpulse/api/afnorpdppa_api.py +559 -9
- factpulse/api/afnorpdppa_directory_service_api.py +4313 -66
- factpulse/api/afnorpdppa_flow_service_api.py +23 -23
- factpulse/api/chorus_pro_api.py +362 -404
- factpulse/api/{signature_lectronique_api.py → document_conversion_api.py} +519 -371
- factpulse/api/health_api.py +526 -0
- factpulse/api/invoice_processing_api.py +3437 -0
- factpulse/api/pdfxml_verification_api.py +1719 -0
- factpulse/api/{sant_api.py → user_api.py} +18 -17
- factpulse/api_client.py +6 -6
- factpulse/configuration.py +6 -4
- factpulse/exceptions.py +8 -5
- factpulse/models/__init__.py +133 -99
- factpulse/models/acknowledgment_status.py +38 -0
- factpulse/models/additional_document.py +115 -0
- factpulse/models/afnor_credentials.py +106 -0
- factpulse/models/afnor_destination.py +127 -0
- factpulse/models/afnor_health_check_response.py +91 -0
- factpulse/models/afnor_result.py +105 -0
- factpulse/models/allowance_charge.py +147 -0
- factpulse/models/allowance_reason_code.py +42 -0
- factpulse/models/allowance_total_amount.py +145 -0
- factpulse/models/amount.py +139 -0
- factpulse/models/amount_due.py +139 -0
- factpulse/models/api_error.py +104 -0
- factpulse/models/async_task_status.py +97 -0
- factpulse/models/base_amount.py +145 -0
- factpulse/models/bounding_box_schema.py +100 -0
- factpulse/models/celery_status.py +40 -0
- factpulse/models/certificate_info_response.py +24 -24
- factpulse/models/charge_total_amount.py +145 -0
- factpulse/models/chorus_pro_destination.py +108 -0
- factpulse/models/chorus_pro_result.py +101 -0
- factpulse/models/contact.py +113 -0
- factpulse/models/convert_error_response.py +105 -0
- factpulse/models/convert_pending_input_response.py +114 -0
- factpulse/models/convert_resume_request.py +87 -0
- factpulse/models/convert_success_response.py +126 -0
- factpulse/models/convert_validation_failed_response.py +120 -0
- factpulse/models/delivery_party.py +121 -0
- factpulse/models/destination.py +27 -27
- factpulse/models/document_type_info.py +91 -0
- factpulse/models/electronic_address.py +90 -0
- factpulse/models/enriched_invoice_info.py +133 -0
- factpulse/models/error_level.py +37 -0
- factpulse/models/error_source.py +43 -0
- factpulse/models/extraction_info.py +93 -0
- factpulse/models/factur_x_invoice.py +320 -0
- factpulse/models/factur_x_profile.py +39 -0
- factpulse/models/factur_xpdf_info.py +91 -0
- factpulse/models/facture_electronique_rest_api_schemas_chorus_pro_chorus_pro_credentials.py +95 -0
- factpulse/models/facture_electronique_rest_api_schemas_processing_chorus_pro_credentials.py +115 -0
- factpulse/models/field_status.py +40 -0
- factpulse/models/file_info.py +94 -0
- factpulse/models/files_info.py +106 -0
- factpulse/models/flow_direction.py +37 -0
- factpulse/models/flow_profile.py +38 -0
- factpulse/models/flow_summary.py +131 -0
- factpulse/models/flow_syntax.py +40 -0
- factpulse/models/flow_type.py +40 -0
- factpulse/models/generate_certificate_request.py +26 -26
- factpulse/models/generate_certificate_response.py +15 -15
- factpulse/models/get_chorus_pro_id_request.py +100 -0
- factpulse/models/get_chorus_pro_id_response.py +98 -0
- factpulse/models/get_invoice_request.py +98 -0
- factpulse/models/get_invoice_response.py +142 -0
- factpulse/models/get_structure_request.py +100 -0
- factpulse/models/get_structure_response.py +142 -0
- factpulse/models/global_allowance_amount.py +139 -0
- factpulse/models/gross_unit_price.py +145 -0
- factpulse/models/http_validation_error.py +2 -2
- factpulse/models/incoming_invoice.py +196 -0
- factpulse/models/incoming_supplier.py +144 -0
- factpulse/models/invoice_format.py +38 -0
- factpulse/models/invoice_line.py +354 -0
- factpulse/models/invoice_line_allowance_amount.py +145 -0
- factpulse/models/invoice_note.py +94 -0
- factpulse/models/invoice_references.py +194 -0
- factpulse/models/invoice_status.py +96 -0
- factpulse/models/invoice_totals.py +177 -0
- factpulse/models/invoice_totals_prepayment.py +145 -0
- factpulse/models/invoice_type_code.py +51 -0
- factpulse/models/invoicing_framework.py +110 -0
- factpulse/models/invoicing_framework_code.py +39 -0
- factpulse/models/line_net_amount.py +145 -0
- factpulse/models/line_total_amount.py +145 -0
- factpulse/models/mandatory_note_schema.py +124 -0
- factpulse/models/manual_rate.py +139 -0
- factpulse/models/manual_vat_rate.py +139 -0
- factpulse/models/missing_field.py +107 -0
- factpulse/models/operation_nature.py +49 -0
- factpulse/models/output_format.py +37 -0
- factpulse/models/page_dimensions_schema.py +89 -0
- factpulse/models/payee.py +168 -0
- factpulse/models/payment_card.py +99 -0
- factpulse/models/payment_means.py +41 -0
- factpulse/models/pdf_validation_result_api.py +169 -0
- factpulse/models/pdp_credentials.py +20 -13
- factpulse/models/percentage.py +145 -0
- factpulse/models/postal_address.py +134 -0
- factpulse/models/price_allowance_amount.py +145 -0
- factpulse/models/price_basis_quantity.py +145 -0
- factpulse/models/processing_options.py +94 -0
- factpulse/models/product_characteristic.py +89 -0
- factpulse/models/product_classification.py +101 -0
- factpulse/models/quantity.py +139 -0
- factpulse/models/recipient.py +167 -0
- factpulse/models/rounding_amount.py +145 -0
- factpulse/models/scheme_id.py +14 -8
- factpulse/models/search_flow_request.py +143 -0
- factpulse/models/search_flow_response.py +101 -0
- factpulse/models/search_services_response.py +101 -0
- factpulse/models/search_structure_request.py +119 -0
- factpulse/models/search_structure_response.py +101 -0
- factpulse/models/signature_info.py +6 -6
- factpulse/models/signature_info_api.py +122 -0
- factpulse/models/signature_parameters.py +133 -0
- factpulse/models/simplified_invoice_data.py +124 -0
- factpulse/models/structure_info.py +14 -14
- factpulse/models/structure_parameters.py +91 -0
- factpulse/models/structure_service.py +93 -0
- factpulse/models/submission_mode.py +38 -0
- factpulse/models/submit_complete_invoice_request.py +116 -0
- factpulse/models/submit_complete_invoice_response.py +145 -0
- factpulse/models/submit_flow_request.py +123 -0
- factpulse/models/submit_flow_response.py +109 -0
- factpulse/models/submit_gross_amount.py +139 -0
- factpulse/models/submit_invoice_request.py +176 -0
- factpulse/models/submit_invoice_response.py +103 -0
- factpulse/models/submit_net_amount.py +139 -0
- factpulse/models/submit_vat_amount.py +139 -0
- factpulse/models/supplementary_attachment.py +95 -0
- factpulse/models/supplier.py +225 -0
- factpulse/models/task_response.py +87 -0
- factpulse/models/tax_representative.py +95 -0
- factpulse/models/taxable_amount.py +139 -0
- factpulse/models/total_gross_amount.py +139 -0
- factpulse/models/total_net_amount.py +139 -0
- factpulse/models/total_vat_amount.py +139 -0
- factpulse/models/unit_net_price.py +139 -0
- factpulse/models/unit_of_measure.py +41 -0
- factpulse/models/validation_error.py +2 -2
- factpulse/models/validation_error_detail.py +107 -0
- factpulse/models/validation_error_loc_inner.py +2 -2
- factpulse/models/validation_error_response.py +87 -0
- factpulse/models/validation_info.py +105 -0
- factpulse/models/validation_success_response.py +87 -0
- factpulse/models/vat_accounting_code.py +39 -0
- factpulse/models/vat_amount.py +139 -0
- factpulse/models/vat_category.py +44 -0
- factpulse/models/vat_line.py +140 -0
- factpulse/models/vat_point_date_code.py +38 -0
- factpulse/models/vat_rate.py +145 -0
- factpulse/models/verification_success_response.py +135 -0
- factpulse/models/verified_field_schema.py +129 -0
- factpulse/rest.py +9 -4
- factpulse-3.0.7.dist-info/METADATA +292 -0
- factpulse-3.0.7.dist-info/RECORD +168 -0
- factpulse-3.0.7.dist-info/top_level.txt +2 -0
- factpulse_helpers/__init__.py +96 -0
- factpulse_helpers/client.py +2111 -0
- factpulse_helpers/exceptions.py +253 -0
- factpulse/api/processing_endpoints_unifis_api.py +0 -592
- factpulse/api/traitement_facture_api.py +0 -3439
- factpulse/api/utilisateur_api.py +0 -282
- factpulse/models/adresse_electronique.py +0 -90
- factpulse/models/adresse_postale.py +0 -120
- factpulse/models/body_ajouter_fichier_api_v1_chorus_pro_transverses_ajouter_fichier_post.py +0 -104
- factpulse/models/body_completer_facture_api_v1_chorus_pro_factures_completer_post.py +0 -104
- factpulse/models/body_lister_services_structure_api_v1_chorus_pro_structures_id_structure_cpp_services_get.py +0 -102
- factpulse/models/body_rechercher_factures_destinataire_api_v1_chorus_pro_factures_rechercher_destinataire_post.py +0 -104
- factpulse/models/body_rechercher_factures_fournisseur_api_v1_chorus_pro_factures_rechercher_fournisseur_post.py +0 -104
- factpulse/models/body_recycler_facture_api_v1_chorus_pro_factures_recycler_post.py +0 -104
- factpulse/models/body_telecharger_groupe_factures_api_v1_chorus_pro_factures_telecharger_groupe_post.py +0 -104
- factpulse/models/body_traiter_facture_recue_api_v1_chorus_pro_factures_traiter_facture_recue_post.py +0 -104
- factpulse/models/body_valideur_consulter_facture_api_v1_chorus_pro_factures_valideur_consulter_post.py +0 -104
- factpulse/models/body_valideur_rechercher_factures_api_v1_chorus_pro_factures_valideur_rechercher_post.py +0 -104
- factpulse/models/body_valideur_traiter_facture_api_v1_chorus_pro_factures_valideur_traiter_post.py +0 -104
- factpulse/models/cadre_de_facturation.py +0 -102
- factpulse/models/categorie_tva.py +0 -44
- factpulse/models/chorus_pro_credentials.py +0 -95
- factpulse/models/code_cadre_facturation.py +0 -39
- factpulse/models/code_raison_reduction.py +0 -42
- factpulse/models/consulter_facture_request.py +0 -98
- factpulse/models/consulter_facture_response.py +0 -142
- factpulse/models/consulter_structure_request.py +0 -100
- factpulse/models/consulter_structure_response.py +0 -142
- factpulse/models/credentials_afnor.py +0 -106
- factpulse/models/credentials_chorus_pro.py +0 -115
- factpulse/models/destinataire.py +0 -116
- factpulse/models/destination_afnor.py +0 -127
- factpulse/models/destination_chorus_pro.py +0 -108
- factpulse/models/direction_flux.py +0 -37
- factpulse/models/donnees_facture_simplifiees.py +0 -124
- factpulse/models/facture_enrichie_info_input.py +0 -123
- factpulse/models/facture_enrichie_info_output.py +0 -133
- factpulse/models/facture_factur_x.py +0 -173
- factpulse/models/flux_resume.py +0 -131
- factpulse/models/format_sortie.py +0 -37
- factpulse/models/fournisseur.py +0 -146
- factpulse/models/information_signature_api.py +0 -122
- factpulse/models/ligne_de_poste.py +0 -188
- factpulse/models/ligne_de_poste_montant_remise_ht.py +0 -145
- factpulse/models/ligne_de_poste_montant_total_ligne_ht.py +0 -145
- factpulse/models/ligne_de_poste_taux_tva_manuel.py +0 -145
- factpulse/models/ligne_de_tva.py +0 -118
- factpulse/models/mode_depot.py +0 -38
- factpulse/models/mode_paiement.py +0 -41
- factpulse/models/montant_ht_total.py +0 -139
- factpulse/models/montant_total.py +0 -138
- factpulse/models/montant_total_acompte.py +0 -145
- factpulse/models/montant_total_montant_remise_globale_ttc.py +0 -145
- factpulse/models/montant_ttc_total.py +0 -139
- factpulse/models/montant_tva.py +0 -139
- factpulse/models/montantapayer.py +0 -139
- factpulse/models/montantbaseht.py +0 -139
- factpulse/models/montanthttotal.py +0 -139
- factpulse/models/montantttctotal.py +0 -139
- factpulse/models/montanttva.py +0 -139
- factpulse/models/montanttva1.py +0 -139
- factpulse/models/montantunitaireht.py +0 -139
- factpulse/models/obtenir_id_chorus_pro_request.py +0 -100
- factpulse/models/obtenir_id_chorus_pro_response.py +0 -98
- factpulse/models/options_processing.py +0 -94
- factpulse/models/parametres_signature.py +0 -133
- factpulse/models/parametres_structure.py +0 -91
- factpulse/models/pdf_factur_x_info.py +0 -91
- factpulse/models/piece_jointe_complementaire.py +0 -95
- factpulse/models/profil_api.py +0 -39
- factpulse/models/profil_flux.py +0 -38
- factpulse/models/quantite.py +0 -139
- factpulse/models/quota_info.py +0 -95
- factpulse/models/rechercher_services_response.py +0 -101
- factpulse/models/rechercher_structure_request.py +0 -119
- factpulse/models/rechercher_structure_response.py +0 -101
- factpulse/models/references.py +0 -124
- factpulse/models/reponse_healthcheck_afnor.py +0 -91
- factpulse/models/reponse_recherche_flux.py +0 -101
- factpulse/models/reponse_soumission_flux.py +0 -109
- factpulse/models/reponse_tache.py +0 -87
- factpulse/models/reponse_validation_erreur.py +0 -87
- factpulse/models/reponse_validation_succes.py +0 -87
- factpulse/models/requete_recherche_flux.py +0 -143
- factpulse/models/requete_soumission_flux.py +0 -123
- factpulse/models/resultat_afnor.py +0 -105
- factpulse/models/resultat_chorus_pro.py +0 -101
- factpulse/models/resultat_validation_pdfapi.py +0 -169
- factpulse/models/service_structure.py +0 -93
- factpulse/models/soumettre_facture_complete_request.py +0 -116
- factpulse/models/soumettre_facture_complete_response.py +0 -145
- factpulse/models/soumettre_facture_request.py +0 -164
- factpulse/models/soumettre_facture_response.py +0 -103
- factpulse/models/statut_acquittement.py +0 -38
- factpulse/models/statut_facture.py +0 -96
- factpulse/models/statut_tache.py +0 -99
- factpulse/models/syntaxe_flux.py +0 -40
- factpulse/models/tauxmanuel.py +0 -139
- factpulse/models/type_facture.py +0 -37
- factpulse/models/type_flux.py +0 -40
- factpulse/models/type_tva.py +0 -39
- factpulse/models/unite.py +0 -41
- factpulse/models/utilisateur.py +0 -128
- factpulse-1.0.9.dist-info/METADATA +0 -182
- factpulse-1.0.9.dist-info/RECORD +0 -131
- factpulse-1.0.9.dist-info/top_level.txt +0 -1
- {factpulse-1.0.9.dist-info → factpulse-3.0.7.dist-info}/WHEEL +0 -0
- {factpulse-1.0.9.dist-info → factpulse-3.0.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""Custom exceptions for the FactPulse client.
|
|
2
|
+
|
|
3
|
+
This module defines an exception hierarchy aligned with the FactPulse API error format
|
|
4
|
+
(APIError, ValidationErrorDetail) compliant with AFNOR XP Z12-013 standard.
|
|
5
|
+
|
|
6
|
+
Exception hierarchy:
|
|
7
|
+
- FactPulseError (base)
|
|
8
|
+
├── FactPulseAuthError (401)
|
|
9
|
+
├── FactPulseValidationError (400, 422) - with structured details
|
|
10
|
+
├── FactPulsePollingTimeout (polling timeout)
|
|
11
|
+
├── FactPulseNotFoundError (404)
|
|
12
|
+
├── FactPulseServiceUnavailableError (503)
|
|
13
|
+
└── FactPulseAPIError (generic with error_code)
|
|
14
|
+
"""
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import Any, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FactPulseError(Exception):
|
|
20
|
+
"""Base class for all FactPulse errors."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FactPulseAuthError(FactPulseError):
|
|
25
|
+
"""FactPulse authentication error (401).
|
|
26
|
+
|
|
27
|
+
Raised when:
|
|
28
|
+
- Invalid email/password
|
|
29
|
+
- Expired or invalid JWT token
|
|
30
|
+
- client_uid not found
|
|
31
|
+
"""
|
|
32
|
+
def __init__(self, message: str = "Authentication required"):
|
|
33
|
+
self.message = message
|
|
34
|
+
super().__init__(message)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class FactPulsePollingTimeout(FactPulseError):
|
|
38
|
+
"""Timeout while polling an asynchronous task."""
|
|
39
|
+
def __init__(self, task_id: str, timeout: int):
|
|
40
|
+
self.task_id = task_id
|
|
41
|
+
self.timeout = timeout
|
|
42
|
+
super().__init__(f"Timeout ({timeout}ms) reached for task {task_id}")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class ValidationErrorDetail:
|
|
47
|
+
"""Validation error detail in AFNOR format.
|
|
48
|
+
|
|
49
|
+
Aligned with the AcknowledgementDetail schema from AFNOR XP Z12-013 standard.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
level: Severity level ('Error' or 'Warning')
|
|
53
|
+
item: Identifier of the affected element (BR-FR rule, field, XPath)
|
|
54
|
+
reason: Error description
|
|
55
|
+
source: Error source (schematron, pydantic, pdfa, afnor, chorus_pro)
|
|
56
|
+
code: Unique error code (e.g., SCHEMATRON_BR_FR_01)
|
|
57
|
+
"""
|
|
58
|
+
level: str = ""
|
|
59
|
+
item: str = ""
|
|
60
|
+
reason: str = ""
|
|
61
|
+
source: Optional[str] = None
|
|
62
|
+
code: Optional[str] = None
|
|
63
|
+
|
|
64
|
+
def __str__(self) -> str:
|
|
65
|
+
item = self.item or "unknown"
|
|
66
|
+
reason = self.reason or "Unknown error"
|
|
67
|
+
source_str = f" [{self.source}]" if self.source else ""
|
|
68
|
+
return f"[{item}]{source_str} {reason}"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class FactPulseValidationError(FactPulseError):
|
|
72
|
+
"""Validation error with structured details (400, 422).
|
|
73
|
+
|
|
74
|
+
Contains a list of ValidationErrorDetail for diagnostics.
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
errors: List of detailed errors
|
|
78
|
+
error_code: API error code (e.g., VALIDATION_FAILED, SCHEMATRON_VALIDATION_FAILED)
|
|
79
|
+
"""
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
message: str,
|
|
83
|
+
errors: Optional[List[ValidationErrorDetail]] = None,
|
|
84
|
+
error_code: str = "VALIDATION_FAILED",
|
|
85
|
+
):
|
|
86
|
+
self.errors = errors or []
|
|
87
|
+
self.error_code = error_code
|
|
88
|
+
if self.errors:
|
|
89
|
+
details = "\n".join(f" - {e}" for e in self.errors)
|
|
90
|
+
message = f"{message}\n\nDetails:\n{details}"
|
|
91
|
+
super().__init__(message)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class FactPulseNotFoundError(FactPulseError):
|
|
95
|
+
"""Resource not found (404).
|
|
96
|
+
|
|
97
|
+
Attributes:
|
|
98
|
+
resource: Resource type (invoice, structure, flow, client)
|
|
99
|
+
identifier: Resource identifier
|
|
100
|
+
"""
|
|
101
|
+
def __init__(self, resource: str, identifier: str = ""):
|
|
102
|
+
self.resource = resource
|
|
103
|
+
self.identifier = identifier
|
|
104
|
+
message = f"{resource.capitalize()} not found"
|
|
105
|
+
if identifier:
|
|
106
|
+
message = f"{resource.capitalize()} '{identifier}' not found"
|
|
107
|
+
super().__init__(message)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class FactPulseServiceUnavailableError(FactPulseError):
|
|
111
|
+
"""External service unavailable (503).
|
|
112
|
+
|
|
113
|
+
Attributes:
|
|
114
|
+
service_name: Service name (AFNOR PDP, Chorus Pro, Django)
|
|
115
|
+
original_error: Original exception (optional)
|
|
116
|
+
"""
|
|
117
|
+
def __init__(self, service_name: str, original_error: Optional[Exception] = None):
|
|
118
|
+
self.service_name = service_name
|
|
119
|
+
self.original_error = original_error
|
|
120
|
+
message = f"Service {service_name} is unavailable"
|
|
121
|
+
if original_error:
|
|
122
|
+
message = f"{message}: {str(original_error)}"
|
|
123
|
+
super().__init__(message)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class FactPulseAPIError(FactPulseError):
|
|
127
|
+
"""Generic API error with structured error code.
|
|
128
|
+
|
|
129
|
+
Used for errors not covered by specific exceptions.
|
|
130
|
+
|
|
131
|
+
Attributes:
|
|
132
|
+
status_code: HTTP response status code
|
|
133
|
+
error_code: API error code (e.g., INTERNAL_ERROR)
|
|
134
|
+
error_message: API error message
|
|
135
|
+
details: Optional details (ValidationErrorDetail)
|
|
136
|
+
"""
|
|
137
|
+
def __init__(
|
|
138
|
+
self,
|
|
139
|
+
status_code: int,
|
|
140
|
+
error_code: str,
|
|
141
|
+
error_message: str,
|
|
142
|
+
details: Optional[List[ValidationErrorDetail]] = None,
|
|
143
|
+
):
|
|
144
|
+
self.status_code = status_code
|
|
145
|
+
self.error_code = error_code
|
|
146
|
+
self.error_message = error_message
|
|
147
|
+
self.details = details or []
|
|
148
|
+
super().__init__(f"[{error_code}] {error_message}")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def parse_api_error(response_json: Dict[str, Any], status_code: int = 400) -> FactPulseError:
|
|
152
|
+
"""Parse an API error response and return the appropriate exception.
|
|
153
|
+
|
|
154
|
+
This function parses the unified FactPulse API error format
|
|
155
|
+
(APIError with errorCode, errorMessage, details) and returns
|
|
156
|
+
the appropriate Python exception.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
response_json: Error response JSON (dict)
|
|
160
|
+
status_code: HTTP response status code
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Appropriate exception based on status_code and error_code
|
|
164
|
+
|
|
165
|
+
Example:
|
|
166
|
+
>>> response = requests.post(url, json=data)
|
|
167
|
+
>>> if response.status_code >= 400:
|
|
168
|
+
... error = parse_api_error(response.json(), response.status_code)
|
|
169
|
+
... raise error
|
|
170
|
+
"""
|
|
171
|
+
# Extract API error fields
|
|
172
|
+
# Support both formats: camelCase (API) and snake_case
|
|
173
|
+
error_code = response_json.get("errorCode") or response_json.get("error_code") or "UNKNOWN_ERROR"
|
|
174
|
+
error_message = response_json.get("errorMessage") or response_json.get("error_message") or "Unknown error"
|
|
175
|
+
details_raw = response_json.get("details") or []
|
|
176
|
+
|
|
177
|
+
# Sometimes the error is wrapped in a "detail" field
|
|
178
|
+
if "detail" in response_json and isinstance(response_json["detail"], dict):
|
|
179
|
+
detail = response_json["detail"]
|
|
180
|
+
error_code = detail.get("error") or detail.get("errorCode") or error_code
|
|
181
|
+
error_message = detail.get("message") or detail.get("errorMessage") or error_message
|
|
182
|
+
details_raw = detail.get("details") or details_raw
|
|
183
|
+
|
|
184
|
+
# Parse details into ValidationErrorDetail
|
|
185
|
+
details = []
|
|
186
|
+
for d in details_raw:
|
|
187
|
+
if isinstance(d, dict):
|
|
188
|
+
details.append(ValidationErrorDetail(
|
|
189
|
+
level=d.get("level", "Error"),
|
|
190
|
+
item=d.get("item", ""),
|
|
191
|
+
reason=d.get("reason", ""),
|
|
192
|
+
source=d.get("source"),
|
|
193
|
+
code=d.get("code"),
|
|
194
|
+
))
|
|
195
|
+
|
|
196
|
+
# Return appropriate exception based on status_code
|
|
197
|
+
if status_code == 401:
|
|
198
|
+
return FactPulseAuthError(error_message)
|
|
199
|
+
elif status_code == 404:
|
|
200
|
+
# Try to extract resource from message
|
|
201
|
+
resource = "resource"
|
|
202
|
+
if "client" in error_message.lower():
|
|
203
|
+
resource = "client"
|
|
204
|
+
elif "flux" in error_message.lower() or "flow" in error_message.lower():
|
|
205
|
+
resource = "flow"
|
|
206
|
+
elif "facture" in error_message.lower() or "invoice" in error_message.lower():
|
|
207
|
+
resource = "invoice"
|
|
208
|
+
elif "structure" in error_message.lower():
|
|
209
|
+
resource = "structure"
|
|
210
|
+
return FactPulseNotFoundError(resource)
|
|
211
|
+
elif status_code == 503:
|
|
212
|
+
service_name = "API"
|
|
213
|
+
if "afnor" in error_message.lower() or "pdp" in error_message.lower():
|
|
214
|
+
service_name = "AFNOR PDP"
|
|
215
|
+
elif "chorus" in error_message.lower():
|
|
216
|
+
service_name = "Chorus Pro"
|
|
217
|
+
return FactPulseServiceUnavailableError(service_name)
|
|
218
|
+
elif status_code in (400, 422) and details:
|
|
219
|
+
return FactPulseValidationError(error_message, details, error_code)
|
|
220
|
+
else:
|
|
221
|
+
return FactPulseAPIError(status_code, error_code, error_message, details)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def api_exception_to_validation_error(api_exception) -> FactPulseValidationError:
|
|
225
|
+
"""Convert an SDK-generated ApiException to FactPulseValidationError.
|
|
226
|
+
|
|
227
|
+
The openapi-generator SDK generates ApiException objects that are not
|
|
228
|
+
very user-friendly. This function converts them to FactPulse exceptions
|
|
229
|
+
with intelligent error parsing.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
api_exception: ApiException from the generated SDK
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
FactPulseValidationError with structured details
|
|
236
|
+
"""
|
|
237
|
+
import json
|
|
238
|
+
|
|
239
|
+
status_code = getattr(api_exception, "status", 400)
|
|
240
|
+
body = getattr(api_exception, "body", "{}")
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
response_json = json.loads(body) if isinstance(body, str) else body
|
|
244
|
+
except (json.JSONDecodeError, TypeError):
|
|
245
|
+
response_json = {"errorMessage": str(api_exception)}
|
|
246
|
+
|
|
247
|
+
error = parse_api_error(response_json, status_code)
|
|
248
|
+
|
|
249
|
+
# Convert to FactPulseValidationError if not already
|
|
250
|
+
if isinstance(error, FactPulseValidationError):
|
|
251
|
+
return error
|
|
252
|
+
else:
|
|
253
|
+
return FactPulseValidationError(str(error))
|