diff --git a/.github/workflows/auto_merge.yml b/.github/workflows/auto_merge.yml index dfcabc3..471e93b 100644 --- a/.github/workflows/auto_merge.yml +++ b/.github/workflows/auto_merge.yml @@ -7,7 +7,7 @@ jobs: auto-merge: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ahmadnassri/action-dependabot-auto-merge@v2 with: github-token: ${{ secrets.DEPENDABOT_AUTOMERGE }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 708bb7a..f250142 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -8,11 +8,11 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master - - name: Set up Python 3.8 - uses: actions/setup-python@v2.2.2 + - uses: actions/checkout@v4 + - name: Set up Python 3.13 + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: 3.13 - name: Install dependencies run: make install-test - name: Install mkdocs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index baac5cd..fa5f494 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,8 @@ name: release -on: push +on: + release: + types: [published] jobs: publish-pypi: @@ -16,8 +18,7 @@ jobs: - name: Generating distribution archives run: python setup.py sdist bdist_wheel - name: Publish distribution 📦 to PyPI - if: startsWith(github.event.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@master + uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.pypi_password }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8b1f1a0..777422f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,16 +1,16 @@ name: test -on: [push, pull_request] +on: push jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2.2.2 + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: 3.13 - name: Install dependencies run: make install-test - name: Lint @@ -20,11 +20,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9, '3.10'] + python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2.2.2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -35,18 +35,19 @@ jobs: coverage: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v2.2.2 + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: 3.13 - name: Install dependencies run: make install-test - name: Generate coverage report run: pytest --cov-report=xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1.5.0 + uses: codecov/codecov-action@v5 with: + token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.xml flags: unittests name: codecov-umbrella diff --git a/Makefile b/Makefile index 126355d..46e6780 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ SHELL := bash PATH := ./venv/bin:${PATH} -PYTHON = python3.9 +PYTHON = python3.13 PROJECT = facturapi isort = isort $(PROJECT) tests setup.py examples -black = black -S -l 79 --target-version py38 $(PROJECT) tests setup.py examples +black = black -S -l 79 --target-version py313 $(PROJECT) tests setup.py examples all: test diff --git a/facturapi/http/client.py b/facturapi/http/client.py index ed8f3a9..1ee8243 100644 --- a/facturapi/http/client.py +++ b/facturapi/http/client.py @@ -1,9 +1,9 @@ import os -from typing import Any, Dict, MutableMapping, Optional, Union +from typing import Any, MutableMapping from urllib.parse import urljoin -import requests -from requests import Response +import httpx +from httpx import Response from ..types.exc import FacturapiResponseException from ..version import CLIENT_VERSION @@ -21,18 +21,18 @@ class Client: Attributes: host (str): Base URL to perform requests. - session (requests.Session): The requests session used + client (httpx.Client): The httpx client used to perform requests. api_key (str): API KEY for Facturapi """ host: str = API_HOST - session: requests.Session + client: httpx.Client - def __init__(self): - self.session = requests.Session() - self.session.headers.update( + def __init__(self) -> None: + self.client = httpx.Client() + self.client.headers.update( { 'User-Agent': f'facturapi-python/{CLIENT_VERSION}', 'Content-Type': 'application/json', @@ -41,9 +41,9 @@ def __init__(self): # Auth self.api_key = os.getenv('FACTURAPI_KEY', '') - self.session.auth = (self.api_key, '') + self.client.auth = httpx.BasicAuth(self.api_key, '') - def configure(self, api_key: str): + def configure(self, api_key: str) -> None: """Configure the http client. Import the client and configure it passing the `API_KEY` @@ -54,25 +54,25 @@ def configure(self, api_key: str): """ self.api_key = api_key - self.session.auth = (self.api_key, '') + self.client.auth = httpx.BasicAuth(self.api_key, '') def get( self, endpoint: str, - params: Union[None, bytes, MutableMapping[str, str]] = None, - ) -> Dict[str, Any]: + params: bytes | MutableMapping[str, str] | None = None, + ) -> dict[str, Any]: """Performs GET request to Facturapi.""" return self.request('get', endpoint, params=params) - def post(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]: + def post(self, endpoint: str, data: dict[str, Any]) -> dict[str, Any]: """Performs POST request to Facturapi.""" return self.request('post', endpoint, data=data) - def put(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]: + def put(self, endpoint: str, data: dict[str, Any]) -> dict[str, Any]: """Performs PUT request to Facturapi.""" return self.request('put', endpoint, data=data) - def delete(self, endpoint: str) -> Dict[str, Any]: + def delete(self, endpoint: str) -> dict[str, Any]: """Performs DELETE request to Facturapi.""" return self.request('delete', endpoint) @@ -80,10 +80,10 @@ def request( self, method: str, endpoint: str, - params: Union[None, bytes, MutableMapping[str, str]] = None, - data: Optional[Dict[str, Union[int, str]]] = None, + params: bytes | MutableMapping[str, str] | None = None, + data: dict[str, int | str] | None = None, **kwargs, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Performs a request to Facturapi. Given a `method` and `endpoint`, perform a request to @@ -97,14 +97,14 @@ def request( **kwargs: Arbitrary keyword arguments. Returns: - Dict[str, Any]: JSON of the request's response. + dict[str, Any]: JSON of the request's response. Raises: FacturapiResponseException: If response is not successful. """ - response = self.session.request( + response = self.client.request( method=method, url=('https://' + self.host + urljoin('/', endpoint)), json=data, @@ -133,7 +133,7 @@ def download_request( successful. """ - response = self.session.request( + response = self.client.request( method='GET', url=('https://' + self.host + urljoin('/', endpoint)), **kwargs, @@ -142,8 +142,8 @@ def download_request( return response.content @staticmethod - def _check_response(response: Response): - if not response.ok: + def _check_response(response: Response) -> None: + if not response.is_success: raise FacturapiResponseException( json=response.json(), status_code=response.status_code, diff --git a/facturapi/resources/base.py b/facturapi/resources/base.py index bf14814..c9ee833 100644 --- a/facturapi/resources/base.py +++ b/facturapi/resources/base.py @@ -6,7 +6,7 @@ """ from dataclasses import asdict, fields -from typing import Any, ClassVar, Dict, Generator, List, Optional +from typing import Any, ClassVar, Generator from urllib.parse import urlencode from pydantic.dataclasses import dataclass @@ -31,7 +31,7 @@ class corresponds to. """ _resource: ClassVar[str] - _relations: ClassVar[List[str]] = [] + _relations: ClassVar[list[str]] = [] id: str @@ -40,12 +40,12 @@ def __init__(self, **_): # pragma: no cover ... @classmethod - def _from_dict(cls, obj_dict: Dict[str, Any]) -> 'Resource': + def _from_dict(cls, obj_dict: dict[str, Any]) -> 'Resource': cls._filter_excess_fields(obj_dict) return cls(**obj_dict) @classmethod - def _filter_excess_fields(cls, obj_dict: Dict[str, Any]) -> None: + def _filter_excess_fields(cls, obj_dict: dict[str, Any]) -> None: """ dataclasses don't allow __init__ to be called with excess fields. This method allows the API to add fields in the response body without @@ -62,7 +62,7 @@ def _filter_excess_fields(cls, obj_dict: Dict[str, Any]) -> None: obj_dict[f'{f}_info'] = obj_dict[f] del obj_dict[f] - def to_dict(self) -> Dict: + def to_dict(self) -> dict: return asdict(self, dict_factory=SanitizedDict) @@ -90,7 +90,7 @@ def retrieve(cls, id: str) -> Resource: response = client.get(f'/{cls._resource}/{id}') return cls._from_dict(response) - def refresh(self): + def refresh(self) -> None: """Refresh a resource Refresh resource's data to be sure its the latest. It @@ -256,7 +256,7 @@ def one(cls, **query_params) -> Resource: return cls._from_dict(items[0]) @classmethod - def first(cls, **query_params) -> Optional[Resource]: + def first(cls, **query_params) -> Resource | None: """Retrieve the first resource found given a query or none. Args: diff --git a/facturapi/resources/customers.py b/facturapi/resources/customers.py index 5e93b2f..a5032b7 100644 --- a/facturapi/resources/customers.py +++ b/facturapi/resources/customers.py @@ -5,7 +5,7 @@ """ import datetime as dt -from typing import ClassVar, Optional, cast +from typing import ClassVar, cast from pydantic import BaseModel from pydantic.dataclasses import dataclass @@ -34,7 +34,7 @@ class CustomerRequest(BaseModel): tax_id: str tax_system: TaxSystemType email: str - phone: Optional[str] + phone: str | None = None address: CustomerAddress @@ -53,12 +53,12 @@ class CustomerUpdateRequest(BaseModel): """ - legal_name: Optional[str] - tax_id: Optional[str] - tax_system: Optional[TaxSystemType] - email: Optional[str] - phone: Optional[str] - address: Optional[CustomerAddress] + legal_name: str | None = None + tax_id: str | None = None + tax_system: TaxSystemType | None = None + email: str | None = None + phone: str | None = None + address: CustomerAddress | None = None @dataclass @@ -89,8 +89,8 @@ class Customer(Creatable, Queryable, Retrievable, Updatable): tax_id: str email: str address: CustomerAddress - tax_system: Optional[TaxSystemType] = None - phone: Optional[str] = None + tax_system: TaxSystemType | None = None + phone: str | None = None @classmethod def create(cls, data: CustomerRequest) -> 'Customer': @@ -103,7 +103,7 @@ def create(cls, data: CustomerRequest) -> 'Customer': Customer: The created customer resource. """ - cleaned_data = data.dict(exclude_unset=True, exclude_none=True) + cleaned_data = data.model_dump(exclude_unset=True, exclude_none=True) return cast('Customer', cls._create(**cleaned_data)) @classmethod @@ -118,5 +118,5 @@ def update(cls, id: str, data: CustomerUpdateRequest) -> 'Customer': Customer: The udpated customer resource. """ - cleaned_data = data.dict(exclude_unset=True, exclude_none=True) + cleaned_data = data.model_dump(exclude_unset=True, exclude_none=True) return cast('Customer', cls._update(id=id, **cleaned_data)) diff --git a/facturapi/resources/invoices.py b/facturapi/resources/invoices.py index e7ab43d..1d328d3 100644 --- a/facturapi/resources/invoices.py +++ b/facturapi/resources/invoices.py @@ -6,7 +6,7 @@ class to create the resource and a class to represent an """ import datetime as dt -from typing import ClassVar, Dict, List, Optional, Union, cast +from typing import ClassVar, cast from pydantic import BaseModel from pydantic.dataclasses import dataclass @@ -34,27 +34,27 @@ class InvoiceItem(BaseModel): to `1`. discount (float): Discount on the item price if any. Defaults to `0`. - product (Union[str, ProductBasicInfo, Dict]): Product + product (Union[str, ProductBasicInfo, dict]): Product ID, info or request to create a resource. - custom_keys (List[str]): List of custom product keys. + custom_keys (list[str]): list of custom product keys. Optional. complement (str): XML code with additional info to add to the invoice. Optional. - parts (List[ItemParts]): If the concept includes parts. + parts (list[ItemParts]): If the concept includes parts. Optional. property_tax_account (str): 'Predial' number. Optional. """ - quantity: Optional[int] = 1 - discount: Optional[float] = 0 - product: Union[ - str, ProductBasicInfo, Dict - ] # TO DO: Change Dict for ProductRequest - customs_keys: Optional[List[str]] - complement: Optional[str] - parts: Optional[List[ItemPart]] - property_tax_account: Optional[str] + quantity: int | None = 1 + discount: float | None = 0 + product: ( + str | ProductBasicInfo | dict + ) # TO DO: Change dict for ProductRequest + customs_keys: list[str] | None = None + complement: str | None = None + parts: list[ItemPart] | None = None + property_tax_account: str | None = None class InvoiceRequest(BaseModel): @@ -65,7 +65,7 @@ class InvoiceRequest(BaseModel): Attributes: customer (Union[str, CustomerRequest]): Customer ID or a CustomerRequest to create a new one. - items (List[InvoiceItem]): List of items of the invoice. + items (list[InvoiceItem]): list of items of the invoice. payment_form (PaymentForm): Form of payment. payment_method (PaymentMethod): Method of payment. Defaults to `PaymentMethod.contado`. @@ -78,35 +78,35 @@ class InvoiceRequest(BaseModel): exchange (float): If a currency is present, the exchange value to Mexican Pesos. Defaults to `1.0`. conditions (str): Payment conditions. Optional. - foreign_trade (Dict): Info to add a 'Complemento de Comercio + foreign_trade (dict): Info to add a 'Complemento de Comercio Exterior'. Optional. - related (List[str]): UUID list of related invoices. Optional. + related (list[str]): UUID list of related invoices. Optional. relation (InvoiceRelation): If related invoices are given, their relation key from the SAT catalogue. Optional. pdf_custom_section (str): HTML string code to include content to the invoice's PDF. Optional addenda (str): XML code with Addenda. Optional. - namespaces (List[Namespace]): If `addenda` or an item complement + namespaces (list[Namespace]): If `addenda` or an item complement is given, the special namespaces of the XML code. Optional. """ - customer: Union[str, CustomerRequest] - items: List[InvoiceItem] + customer: str | CustomerRequest + items: list[InvoiceItem] payment_form: PaymentForm - payment_method: Optional[PaymentMethod] = PaymentMethod.contado - use: Optional[InvoiceUse] = InvoiceUse.adquisicion_mercancias - folio_number: Optional[int] - series: Optional[str] - currency: Optional[str] = 'MXN' - exchange: Optional[float] = 1.0 - conditions: Optional[str] - foreign_trade: Optional[Dict] - related: Optional[List[str]] - relation: Optional[InvoiceRelation] - pdf_custom_section: Optional[str] - addenda: Optional[str] - namespaces: Optional[Namespace] + payment_method: PaymentMethod | None = PaymentMethod.contado + use: InvoiceUse | None = InvoiceUse.adquisicion_mercancias + folio_number: int | None = None + series: str | None = None + currency: str | None = 'MXN' + exchange: float | None = 1.0 + conditions: str | None = None + foreign_trade: dict | None = None + related: list[str] | None = None + relation: InvoiceRelation | None = None + pdf_custom_section: str | None = None + addenda: str | None = None + namespaces: Namespace | None = None @dataclass @@ -128,14 +128,14 @@ class Invoice(Creatable, Deletable, Downloadable, Queryable, Retrievable): total (float): Invoice total. uuid (str): 'Folio fiscal' assigned by SAT. payment_form (PaymentForm): Form of payment of the Invoice. - items (List[InvoiceItem]): List of items of the Invoice. + items (list[InvoiceItem]): list of items of the Invoice. currency (str): Currency of the invoice in ISO format. exchange (float): Exchange value to Mexican Pesos. cancellation_status (str): If the Invoice was cancelled, the status of the cancellation. Optional. folio_number (int): Folio number. Optional. series (str): Custom series string. Optional. Defaults to `None`. - related (List[str]): UUID of related invoices. Defaults to + related (list[str]): UUID of related invoices. Defaults to `None`. relation (InvoiceRelation): Relation key from the SAT catalogue. Defaults to `None`. @@ -154,14 +154,14 @@ class Invoice(Creatable, Deletable, Downloadable, Queryable, Retrievable): total: float uuid: str payment_form: PaymentForm - items: List[InvoiceItem] + items: list[InvoiceItem] currency: str exchange: float - cancellation_status: Optional[str] - folio_number: Optional[int] - series: Optional[str] = None - related: Optional[List[str]] = None - relation: Optional[InvoiceRelation] = None + cancellation_status: str | None = None + folio_number: int | None = None + series: str | None = None + related: list[str] | None = None + relation: InvoiceRelation | None = None @classmethod def create(cls, data: InvoiceRequest) -> 'Invoice': @@ -174,7 +174,7 @@ def create(cls, data: InvoiceRequest) -> 'Invoice': Invoice: The created resource. """ - cleaned_data = data.dict(exclude_unset=True, exclude_none=True) + cleaned_data = data.model_dump(exclude_unset=True, exclude_none=True) return cast('Invoice', cls._create(**cleaned_data)) @classmethod @@ -196,7 +196,7 @@ def cancel(cls, invoice_id: str, motive: str) -> 'Invoice': def send_by_email( cls, invoice_id: str, - recipients: Optional[Union[str, List[str]]] = None, + recipients: str | list[str] | None = None, ) -> bool: """Send an invoice by email. diff --git a/facturapi/resources/resources.py b/facturapi/resources/resources.py index f423da9..f0b228c 100644 --- a/facturapi/resources/resources.py +++ b/facturapi/resources/resources.py @@ -1,10 +1,10 @@ import re -from typing import Dict, cast +from typing import cast from .base import Retrievable ENDPOINT_RE = re.compile(r'(?P[a-z]+)/(?P.+)$') -RESOURCES: Dict[str, Retrievable] = {} # set in ./__init__.py after imports +RESOURCES: dict[str, Retrievable] = {} # set in ./__init__.py after imports def retrieve_property(uri: str) -> Retrievable: diff --git a/facturapi/types/exc.py b/facturapi/types/exc.py index 7ae7674..a80b92c 100644 --- a/facturapi/types/exc.py +++ b/facturapi/types/exc.py @@ -1,9 +1,9 @@ from dataclasses import dataclass -from typing import Any, Dict +from typing import Any class FacturapiException(Exception): - ... + pass class NoResultFound(FacturapiException): @@ -16,7 +16,7 @@ class MultipleResultsFound(FacturapiException): @dataclass class FacturapiResponseException(FacturapiException): - json: Dict[str, Any] + json: dict[str, Any] status_code: int def __str__(self) -> str: diff --git a/facturapi/types/general.py b/facturapi/types/general.py index 913db82..122e232 100644 --- a/facturapi/types/general.py +++ b/facturapi/types/general.py @@ -1,5 +1,3 @@ -from typing import List, Optional - from pydantic import BaseModel from .validators import sanitize_dict @@ -21,15 +19,15 @@ class CustomerAddress(BaseModel): """ - street: Optional[str] - exterior: Optional[str] - interior: Optional[str] - neighborhood: Optional[str] - zip: Optional[str] - city: Optional[str] - municipality: Optional[str] - state: Optional[str] - country: Optional[str] + street: str | None = None + exterior: str | None = None + interior: str | None = None + neighborhood: str | None = None + zip: str | None = None + city: str | None = None + municipality: str | None = None + state: str | None = None + country: str | None = None class CustomerBasicInfo(BaseModel): @@ -45,18 +43,18 @@ class ItemPart(BaseModel): description: str product_key: str - quantity: Optional[int] = 1 - sku: Optional[str] - unit_price: Optional[float] - customs_keys: Optional[List[str]] + quantity: int | None = 1 + sku: str | None = None + unit_price: float | None = None + customs_keys: list[str] | None = None class Namespace(BaseModel): """Namespace for spceial XML namespaces for an invoice.""" - prefix: Optional[str] - uri: Optional[str] - schema_location: Optional[str] + prefix: str | None = None + uri: str | None = None + schema_location: str | None = None class ProductBasicInfo(BaseModel): diff --git a/facturapi/types/queries.py b/facturapi/types/queries.py index 9902fe4..da058ee 100644 --- a/facturapi/types/queries.py +++ b/facturapi/types/queries.py @@ -1,20 +1,13 @@ import datetime as dt -from typing import Any, Dict, Optional, Union +from typing import Annotated, Any -from pydantic import BaseModel, Extra -from pydantic.types import ConstrainedInt +from pydantic import BaseModel, Field MAX_PAGE_SIZE = 50 MIN_PAGE = 1 - -class PageSize(ConstrainedInt): - gt = 0 - le = MAX_PAGE_SIZE - - -class Page(ConstrainedInt): - gt = MIN_PAGE +PageSize = Annotated[int, Field(gt=0, le=MAX_PAGE_SIZE)] +Page = Annotated[int, Field(ge=MIN_PAGE)] class DateFilter(BaseModel): @@ -34,10 +27,10 @@ class DateFilter(BaseModel): """ - gt: Optional[Union[str, dt.datetime]] - gte: Optional[Union[str, dt.datetime]] - lt: Optional[Union[str, dt.datetime]] - lte: Optional[Union[str, dt.datetime]] + gt: str | dt.datetime | None = None + gte: str | dt.datetime | None = None + lt: str | dt.datetime | None = None + lte: str | dt.datetime | None = None class BaseQuery(BaseModel): @@ -58,20 +51,19 @@ class BaseQuery(BaseModel): """ - q: Optional[str] - limit: Optional[PageSize] = PageSize(MAX_PAGE_SIZE) - page: Optional[Page] = Page(MIN_PAGE) - date: Optional[DateFilter] + q: str | None = None + limit: PageSize | None = MAX_PAGE_SIZE + page: Page | None = MIN_PAGE + date: DateFilter | None = None - class Config: - extra = Extra.forbid + model_config = {"extra": "forbid"} - def dict(self, *args, **kwargs) -> Dict[str, Any]: + def dict(self, *args, **kwargs) -> dict[str, Any]: kwargs.setdefault('exclude_none', True) kwargs.setdefault('exclude_unset', True) - d = super().dict(*args, **kwargs) + d = super().model_dump(*args, **kwargs) return d class InvoiceQuery(BaseQuery): - motive: Optional[str] + motive: str | None = None diff --git a/facturapi/version.py b/facturapi/version.py index 6631f1b..83ed8a8 100644 --- a/facturapi/version.py +++ b/facturapi/version.py @@ -1,2 +1,2 @@ -__version__ = '0.2.0' # pragma: no cover +__version__ = '1.0.0' CLIENT_VERSION = __version__ diff --git a/requirements-test.txt b/requirements-test.txt index 7a60c10..80fa901 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,7 +1,7 @@ -pytest==6.2.* -pytest-cov==3.0.* -pytest-vcr==1.0.* -black==22.3.0 -isort==5.7.* -flake8==3.8.* -mypy==0.812 +pytest==8.* +black==25.* +pytest-cov==6.* +pytest-vcr==1.* +isort==6.* +flake8==7.* +mypy==1.* diff --git a/requirements.txt b/requirements.txt index 8cc3387..8c3b52c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ -requests==2.28.1 -pydantic==1.7.3 -dataclasses>=0.7;python_version<"3.7" +httpx==0.28.1 +pydantic==2.10.6 diff --git a/setup.py b/setup.py index 57ae11b..8cc9c39 100644 --- a/setup.py +++ b/setup.py @@ -21,17 +21,14 @@ packages=find_packages(), include_package_data=True, package_data=dict(facturapi=['py.typed']), - python_requires='>=3.6', - install_requires=[ - 'requests>=2.4,<2.26', - 'pydantic==1.7.3', - 'dataclasses>=0.7;python_version<"3.7"', - ], + python_requires='>=3.10', + install_requires=['httpx>=0.28.0,<1.0.0', 'pydantic>=2.10.0,<3.0.0'], classifiers=[ 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', ], diff --git a/tests/resources/test_invoices.py b/tests/resources/test_invoices.py index 318a0f5..480a47e 100644 --- a/tests/resources/test_invoices.py +++ b/tests/resources/test_invoices.py @@ -150,7 +150,7 @@ def test_download_invoice(): ) assert invoice_bytes - assert type(invoice_bytes) == bytes + assert type(invoice_bytes) is bytes @pytest.mark.vcr @@ -161,7 +161,7 @@ def test_download_invoice_xml(): ) assert invoice_bytes - assert type(invoice_bytes) == bytes + assert type(invoice_bytes) is bytes @pytest.mark.vcr @@ -172,7 +172,7 @@ def test_download_invoice_zip(): ) assert invoice_bytes - assert type(invoice_bytes) == bytes + assert type(invoice_bytes) is bytes @pytest.mark.vcr diff --git a/tests/types/test_queries.py b/tests/types/test_queries.py index f226a2f..bb980cf 100644 --- a/tests/types/test_queries.py +++ b/tests/types/test_queries.py @@ -6,7 +6,9 @@ def test_base_query(): q = BaseQuery(q='Frida') - assert q.dict() == dict(q='Frida') + assert q.model_dump(exclude_unset=True, exclude_none=True) == dict( + q='Frida' + ) def test_base_query_page_size():