Compatibilidad con Python 3.13
- Actualiza dependencias: cryptography>=41.0.0, pyOpenSSL>=23.2.0, xmlsec>=1.3.12, xmlsig>=0.1.9, zeep>=4.2.1, lxml>=5.2.2 - Anade monkey-patch para xades.load_pkcs12 con pyOpenSSL >= 24 (PKCS12 eliminado de OpenSSL.crypto) - Reemplaza datetime.utcnow() por datetime.now(timezone.utc) - Reemplaza APIs deprecadas de lxml en facho.py y codelist - Migra verificacion PKCS12 de pyOpenSSL a cryptography API en fe.py - Moderniza typing: Union/Optional/List a sintaxis | y list[] - Actualiza requirements_dev.txt con versiones recientes - Actualiza tox.ini y Makefile.dev para Python 3.13
This commit is contained in:
@@ -140,12 +140,12 @@ class LXMLBuilder:
|
||||
attrs['pretty_print'] = attrs.pop('pretty_print', False)
|
||||
attrs['encoding'] = attrs.pop('encoding', 'UTF-8')
|
||||
|
||||
for el in elem.getiterator():
|
||||
for el in elem.iter():
|
||||
keys = filter(lambda key: key.startswith('facho_'), el.keys())
|
||||
self.remove_attributes(el, keys, exclude=['facho_optional'])
|
||||
|
||||
is_optional = el.get('facho_optional', 'False') == 'True'
|
||||
if is_optional and el.getchildren() == [] and el.keys() == [
|
||||
if is_optional and list(el) == [] and el.keys() == [
|
||||
'facho_optional']:
|
||||
el.getparent().remove(el)
|
||||
|
||||
@@ -291,7 +291,7 @@ class FachoXML:
|
||||
# se fuerza la adicion como un nuevo elemento
|
||||
if append:
|
||||
last_slibing = None
|
||||
for child in parent.getchildren():
|
||||
for child in list(parent):
|
||||
if child.tag == node_tag:
|
||||
last_slibing = child
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import xmlsec
|
||||
import urllib.request
|
||||
from datetime import datetime
|
||||
from dataclasses import dataclass, asdict, field
|
||||
from typing import List
|
||||
|
||||
import http.client
|
||||
import hashlib
|
||||
import secrets
|
||||
@@ -47,7 +47,7 @@ class GetNumberingRangeResponse:
|
||||
ValidateDateTo: str
|
||||
TechnicalKey: str
|
||||
|
||||
NumberRangeResponse: List[NumberRangeResponse]
|
||||
NumberRangeResponse: list[NumberRangeResponse]
|
||||
|
||||
|
||||
@classmethod
|
||||
@@ -91,7 +91,7 @@ class SendBillAsync(SOAPService):
|
||||
@dataclass
|
||||
class SendTestSetAsyncResponse:
|
||||
ZipKey: str
|
||||
ErrorMessageList: List[str]
|
||||
ErrorMessageList: list[str]
|
||||
|
||||
@classmethod
|
||||
def fromdict(cls, data):
|
||||
@@ -134,7 +134,7 @@ class GetStatusResponse:
|
||||
IsValid: bool
|
||||
StatusDescription: str
|
||||
StatusCode: int
|
||||
ErrorMessage: List[str]
|
||||
ErrorMessage: list[str]
|
||||
|
||||
@classmethod
|
||||
def fromdict(cls, data):
|
||||
|
||||
@@ -9,7 +9,7 @@ module.
|
||||
|
||||
"""
|
||||
import pytz
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from lxml import etree
|
||||
from lxml.etree import QName
|
||||
|
||||
@@ -222,7 +222,7 @@ def sign_envelope(
|
||||
return _sign_envelope_with_key(envelope, key, signature_method, digest_method)
|
||||
|
||||
def get_timestamp(timestamp = None, delta=None):
|
||||
timestamp = timestamp or datetime.utcnow()
|
||||
timestamp = timestamp or datetime.now(timezone.utc)
|
||||
if delta:
|
||||
timestamp += delta
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@ class CodeList:
|
||||
row = {}
|
||||
|
||||
#construir registro...
|
||||
for value in xmlrow.getchildren():
|
||||
row[value.attrib['ColumnRef']] = value.getchildren()[0].text
|
||||
for value in list(xmlrow):
|
||||
row[value.attrib['ColumnRef']] = list(value)[0].text
|
||||
|
||||
return row
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import uuid
|
||||
import xmlsig
|
||||
import xades
|
||||
from datetime import datetime
|
||||
import OpenSSL
|
||||
import zipfile
|
||||
# import warnings
|
||||
import hashlib
|
||||
@@ -18,6 +17,21 @@ from dateutil import tz
|
||||
|
||||
from cryptography.hazmat.primitives.serialization import pkcs12
|
||||
|
||||
# Monkey-patch xades/xmlsig para compatibilidad con pyOpenSSL >= 24
|
||||
# (PKCS12 fue eliminado de OpenSSL.crypto, pero ambas librerías lo referencian)
|
||||
import OpenSSL
|
||||
import xades.xades_context
|
||||
|
||||
if not hasattr(OpenSSL.crypto, 'PKCS12'):
|
||||
def _patched_load_pkcs12(self, key):
|
||||
if isinstance(key, tuple):
|
||||
self.x509 = key[1]
|
||||
self.public_key = key[1].public_key()
|
||||
self.private_key = key[0]
|
||||
else:
|
||||
raise NotImplementedError("unsupported key type")
|
||||
xades.xades_context.XAdESContext.load_pkcs12 = _patched_load_pkcs12
|
||||
|
||||
AMBIENTE_PRUEBAS = codelist.TipoAmbiente.by_name('Pruebas')['code']
|
||||
AMBIENTE_PRODUCCION = codelist.TipoAmbiente.by_name('Producción')['code']
|
||||
|
||||
@@ -444,9 +458,6 @@ class DianXMLExtensionSigner:
|
||||
self._pkcs12_data,
|
||||
self._passphrase))
|
||||
|
||||
# ctx.load_pkcs12(OpenSSL.crypto.load_pkcs12(
|
||||
# self._pkcs12_data,
|
||||
# self._passphrase))
|
||||
if self._localpolicy:
|
||||
with mock_xades_policy():
|
||||
ctx.sign(signature)
|
||||
@@ -590,8 +601,8 @@ class DianXMLExtensionSignerVerifier:
|
||||
if isinstance(self._pkcs12_path_or_bytes, str):
|
||||
pkcs12_data = open(self._pkcs12_path_or_bytes, 'rb').read()
|
||||
ctx = xades.XAdESContext()
|
||||
ctx.load_pkcs12(OpenSSL.crypto.load_pkcs12(pkcs12_data,
|
||||
self._passphrase))
|
||||
ctx.load_pkcs12(pkcs12.load_key_and_certificates(
|
||||
pkcs12_data, self._passphrase))
|
||||
try:
|
||||
if self._localpolicy:
|
||||
with mock_xades_policy():
|
||||
|
||||
@@ -11,7 +11,6 @@ from datetime import datetime, date
|
||||
# from collections import defaultdict
|
||||
import decimal
|
||||
from decimal import Decimal
|
||||
import typing
|
||||
from ..data.dian import codelist
|
||||
|
||||
DECIMAL_PRECISION = 6
|
||||
@@ -60,7 +59,7 @@ class AmountCollection(Collection):
|
||||
|
||||
class Amount:
|
||||
def __init__(
|
||||
self, amount: typing.Union[int, float, str, "Amount"],
|
||||
self, amount: int | float | str | "Amount",
|
||||
currency: Currency = Currency('COP')):
|
||||
|
||||
# DIAN 1.7.-2020: 1.2.3.1
|
||||
@@ -294,7 +293,7 @@ class TaxScheme:
|
||||
class Party:
|
||||
name: str
|
||||
ident: str
|
||||
responsability_code: typing.List[Responsability]
|
||||
responsability_code: list[Responsability]
|
||||
responsability_regime_code: str
|
||||
organization_code: str
|
||||
tax_scheme: TaxScheme = field(default_factory=lambda: TaxScheme('01'))
|
||||
@@ -327,7 +326,7 @@ class TaxScheme:
|
||||
@dataclass
|
||||
class TaxSubTotal:
|
||||
percent: float
|
||||
scheme: typing.Optional[TaxScheme] = None
|
||||
scheme: TaxScheme | None = None
|
||||
tax_amount: Amount = field(default_factory=lambda: Amount(0.0))
|
||||
|
||||
def calculate(self, invline):
|
||||
@@ -359,7 +358,7 @@ class TaxTotalOmit(TaxTotal):
|
||||
@dataclass
|
||||
class WithholdingTaxSubTotal:
|
||||
percent: float
|
||||
scheme: typing.Optional[TaxScheme] = None
|
||||
scheme: TaxScheme | None = None
|
||||
tax_amount: Amount = field(default_factory=lambda: Amount(0.0))
|
||||
|
||||
def calculate(self, invline):
|
||||
@@ -491,7 +490,7 @@ class AllowanceCharge:
|
||||
reason: AllowanceChargeReason = None
|
||||
|
||||
# Valor Base para calcular el descuento o el cargo
|
||||
base_amount: typing.Optional[Amount] = field(
|
||||
base_amount: Amount | None = field(
|
||||
default_factory=lambda: Amount(0.0))
|
||||
|
||||
# Porcentaje: Porcentaje que aplicar.
|
||||
@@ -536,9 +535,9 @@ class InvoiceLine:
|
||||
# ya que al reportar los totales es sobre
|
||||
# la factura y el percent es unico por type_code
|
||||
# de subtotal
|
||||
tax: typing.Optional[TaxTotal]
|
||||
withholding: typing.Optional[WithholdingTaxTotal]
|
||||
allowance_charge: typing.List[AllowanceCharge] = dataclasses.field(
|
||||
tax: TaxTotal | None
|
||||
withholding: WithholdingTaxTotal | None
|
||||
allowance_charge: list[AllowanceCharge] = dataclasses.field(
|
||||
default_factory=list)
|
||||
|
||||
def add_allowance_charge(self, charge):
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
import typing
|
||||
|
||||
|
||||
from .. import fe
|
||||
from .. import form
|
||||
@@ -86,13 +86,13 @@ class NumeroSecuencia:
|
||||
|
||||
@dataclass
|
||||
class Periodo:
|
||||
fecha_ingreso: typing.Union[str, Fecha]
|
||||
fecha_liquidacion_inicio: typing.Union[str, Fecha]
|
||||
fecha_liquidacion_fin: typing.Union[str, Fecha]
|
||||
fecha_generacion: typing.Union[str, Fecha]
|
||||
fecha_ingreso: str | Fecha
|
||||
fecha_liquidacion_inicio: str | Fecha
|
||||
fecha_liquidacion_fin: str | Fecha
|
||||
fecha_generacion: str | Fecha
|
||||
|
||||
tiempo_laborado: int = 1
|
||||
fecha_retiro: typing.Union[str, Fecha] = None
|
||||
fecha_retiro: str | Fecha | None = None
|
||||
|
||||
def __post_init__(self):
|
||||
self.fecha_ingreso = Fecha.cast(self.fecha_ingreso)
|
||||
@@ -259,7 +259,7 @@ class InformacionGeneral:
|
||||
def __str__(self):
|
||||
self.valor
|
||||
|
||||
fecha_generacion: typing.Union[str, Fecha]
|
||||
fecha_generacion: str | Fecha
|
||||
hora_generacion: str
|
||||
periodo_nomina: PeriodoNomina
|
||||
tipo_moneda: TipoMoneda
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
|
||||
from ..amount import Amount
|
||||
from .devengado import Devengado
|
||||
@@ -30,7 +30,7 @@ class DevengadoHoraExtra:
|
||||
|
||||
@dataclass
|
||||
class DevengadoHorasExtrasDiarias(Devengado):
|
||||
horas_extras: List[DevengadoHoraExtra]
|
||||
horas_extras: list[DevengadoHoraExtra]
|
||||
|
||||
def apply(self, fragment):
|
||||
hora_extra_xml = fragment.fragment('./HEDs')
|
||||
@@ -39,7 +39,7 @@ class DevengadoHorasExtrasDiarias(Devengado):
|
||||
|
||||
@dataclass
|
||||
class DevengadoHorasExtrasNocturnas(Devengado):
|
||||
horas_extras: List[DevengadoHoraExtra]
|
||||
horas_extras: list[DevengadoHoraExtra]
|
||||
|
||||
def apply(self, fragment):
|
||||
hora_extra_xml = fragment.fragment('./HENs')
|
||||
@@ -49,7 +49,7 @@ class DevengadoHorasExtrasNocturnas(Devengado):
|
||||
|
||||
@dataclass
|
||||
class DevengadoHorasRecargoNocturno(Devengado):
|
||||
horas_extras: List[DevengadoHoraExtra]
|
||||
horas_extras: list[DevengadoHoraExtra]
|
||||
|
||||
def apply(self, fragment):
|
||||
hora_extra_xml = fragment.fragment('./HRNs')
|
||||
@@ -58,7 +58,7 @@ class DevengadoHorasRecargoNocturno(Devengado):
|
||||
|
||||
@dataclass
|
||||
class DevengadoHorasExtrasDiariasDominicalesYFestivos(Devengado):
|
||||
horas_extras: List[DevengadoHoraExtra]
|
||||
horas_extras: list[DevengadoHoraExtra]
|
||||
|
||||
def apply(self, fragment):
|
||||
hora_extra_xml = fragment.fragment('./HEDDFs')
|
||||
@@ -67,7 +67,7 @@ class DevengadoHorasExtrasDiariasDominicalesYFestivos(Devengado):
|
||||
|
||||
@dataclass
|
||||
class DevengadoHorasRecargoDiariasDominicalesYFestivos(Devengado):
|
||||
horas_extras: List[DevengadoHoraExtra]
|
||||
horas_extras: list[DevengadoHoraExtra]
|
||||
|
||||
def apply(self, fragment):
|
||||
hora_extra_xml = fragment.fragment('./HRDDFs')
|
||||
@@ -76,7 +76,7 @@ class DevengadoHorasRecargoDiariasDominicalesYFestivos(Devengado):
|
||||
|
||||
@dataclass
|
||||
class DevengadoHorasExtrasNocturnasDominicalesYFestivos(Devengado):
|
||||
horas_extras: List[DevengadoHoraExtra]
|
||||
horas_extras: list[DevengadoHoraExtra]
|
||||
|
||||
def apply(self, fragment):
|
||||
hora_extra_xml = fragment.fragment('./HENDFs')
|
||||
@@ -85,7 +85,7 @@ class DevengadoHorasExtrasNocturnasDominicalesYFestivos(Devengado):
|
||||
|
||||
@dataclass
|
||||
class DevengadoHorasRecargoNocturnoDominicalesYFestivos(Devengado):
|
||||
horas_extras: List[DevengadoHoraExtra]
|
||||
horas_extras: list[DevengadoHoraExtra]
|
||||
|
||||
def apply(self, fragment):
|
||||
hora_extra_xml = fragment.fragment('./HRNDFs')
|
||||
|
||||
Reference in New Issue
Block a user