diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6c916c0..d909020 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,9 +17,8 @@ jobs: fail-fast: false matrix: include: - - python-version: "3.9" + - python-version: "3.10" tox: min - - python-version: "3.9" - python-version: "3.10" tox: min-x402 - python-version: "3.10" @@ -53,7 +52,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.13"] # Keep in sync with .readthedocs.yml + python-version: ["3.13"] # In sync with pyproject.toml tox-job: ["mypy", "docs"] steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c15bfa9..37e3c29 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,3 +20,7 @@ repos: rev: v1.0.0 hooks: - id: sphinx-lint +- repo: https://github.com/scrapy/sphinx-scrapy + rev: 0.8.3 + hooks: + - id: sphinx-scrapy diff --git a/.readthedocs.yml b/.readthedocs.yml index 64e40c4..6d1aeb5 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,14 +1,10 @@ version: 2 -formats: all -sphinx: - configuration: docs/conf.py build: os: ubuntu-24.04 tools: - # For available versions, see: - # https://docs.readthedocs.io/en/stable/config-file/v2.html#build-tools-python - python: "3.13" # Keep in sync with .github/workflows/test.yml -python: - install: - - requirements: docs/requirements.txt - - path: . + python: "3.13" + commands: + - pip install tox + - tox -e docs + - mkdir -p $READTHEDOCS_OUTPUT/html + - cp -a docs/_build/all/. $READTHEDOCS_OUTPUT/html/ diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index dab18b2..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -include CHANGES.rst -include LICENSE -include README.rst - -recursive-include tests * diff --git a/README.rst b/README.rst index e93ded4..7984fe4 100644 --- a/README.rst +++ b/README.rst @@ -43,7 +43,7 @@ Or, to use x402_: pip install zyte-api[x402] -.. note:: Python 3.9+ is required; Python 3.10+ if using x402. +.. note:: Python 3.10+ is required. .. install-end diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..ba65b13 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +/_build/ diff --git a/docs/conf.py b/docs/conf.py index aebd0ea..48e54df 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -37,10 +37,8 @@ sys.path.insert(0, str(Path(__file__).parent.absolute())) # _ext extensions = [ "_ext", - "sphinx.ext.autodoc", - "sphinx.ext.intersphinx", + "sphinx_scrapy", "sphinx.ext.ifconfig", - "sphinx.ext.viewcode", "sphinx.ext.githubpages", "sphinxarg.ext", ] @@ -188,29 +186,6 @@ # not turned into –api-key in descriptions. smartquotes = False - -# -- Extension configuration ------------------------------------------------- - -# -- Options for intersphinx extension --------------------------------------- -intersphinx_mapping = { - "python": ( - "https://docs.python.org/3", - None, - ), - "aiohttp": ( - "https://docs.aiohttp.org/en/stable/", - None, - ), - "tenacity": ( - "https://tenacity.readthedocs.io/en/latest/", - None, - ), - "zyte": ( - "https://docs.zyte.com", - None, - ), -} - autodoc_default_options = { # 'special-members': '__init__,__call__', # 'undoc-members': True, @@ -218,3 +193,9 @@ } add_module_names = False + +scrapy_intersphinx_enable = [ + "aiohttp", + "tenacity", + "zyte", +] diff --git a/docs/requirements.in b/docs/requirements.in new file mode 100644 index 0000000..8a704ae --- /dev/null +++ b/docs/requirements.in @@ -0,0 +1,6 @@ +aiohttp +sphinx +sphinx-argparse +sphinx-rtd-theme +sphinx-scrapy @ git+https://github.com/scrapy/sphinx-scrapy.git@0.8.3 +tenacity diff --git a/docs/requirements.txt b/docs/requirements.txt index 164cfa9..7b286f9 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,107 @@ -aiohttp >= 3.6.0 -Sphinx >= 4.2.0 -sphinx-argparse -sphinx-rtd-theme >= 0.4 -tenacity +# This file was autogenerated by uv via the following command: +# uv pip compile -p 3.14 requirements.in -o requirements.txt +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.5 + # via -r requirements.in +aiosignal==1.4.0 + # via aiohttp +alabaster==1.0.0 + # via sphinx +attrs==26.1.0 + # via aiohttp +babel==2.18.0 + # via sphinx +certifi==2026.2.25 + # via requests +charset-normalizer==3.4.7 + # via requests +docutils==0.22.4 + # via + # sphinx + # sphinx-argparse + # sphinx-markdown-builder + # sphinx-rtd-theme +frozenlist==1.8.0 + # via + # aiohttp + # aiosignal +idna==3.12 + # via + # requests + # yarl +imagesize==2.0.0 + # via sphinx +jinja2==3.1.6 + # via sphinx +markupsafe==3.0.3 + # via jinja2 +multidict==6.7.1 + # via + # aiohttp + # yarl +packaging==26.1 + # via + # sphinx + # sphinx-scrapy +propcache==0.4.1 + # via + # aiohttp + # yarl +pygments==2.20.0 + # via sphinx +requests==2.33.1 + # via sphinx +roman-numerals==4.1.0 + # via sphinx +snowballstemmer==3.0.1 + # via sphinx +sphinx==9.1.0 + # via + # -r requirements.in + # sphinx-argparse + # sphinx-copybutton + # sphinx-last-updated-by-git + # sphinx-llms-txt + # sphinx-markdown-builder + # sphinx-rtd-theme + # sphinx-scrapy + # sphinxcontrib-jquery +sphinx-argparse==0.5.2 + # via -r requirements.in +sphinx-copybutton==0.5.2 + # via sphinx-scrapy +sphinx-last-updated-by-git==0.3.8 + # via sphinx-sitemap +sphinx-llms-txt @ git+https://github.com/zytedata/sphinx-llms-txt.git@5e8866cb0cc249aa2017ad9050b3b83a7ca16f69 + # via sphinx-scrapy +sphinx-markdown-builder @ git+https://github.com/zytedata/sphinx-markdown-builder.git@cfe4c0bfd7b4542f7e6b65a58cdf9ec765829940 + # via sphinx-scrapy +sphinx-rtd-theme==3.1.0 + # via -r requirements.in +sphinx-scrapy @ git+https://github.com/scrapy/sphinx-scrapy.git@f20366277f2598d0c8a60e55fe282aff2da40dcf + # via -r requirements.in +sphinx-sitemap==2.9.0 + # via sphinx-scrapy +sphinxcontrib-applehelp==2.0.0 + # via sphinx +sphinxcontrib-devhelp==2.0.0 + # via sphinx +sphinxcontrib-htmlhelp==2.1.0 + # via sphinx +sphinxcontrib-jquery==4.1 + # via sphinx-rtd-theme +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==2.0.0 + # via sphinx +sphinxcontrib-serializinghtml==2.0.0 + # via sphinx +tabulate==0.10.0 + # via sphinx-markdown-builder +tenacity==9.1.4 + # via -r requirements.in +urllib3==2.6.3 + # via requests +yarl==1.23.0 + # via aiohttp diff --git a/pyproject.toml b/pyproject.toml index 74a88d0..74d5773 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,68 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "zyte-api" +dynamic = ["version"] +description = "Python interface to Zyte API" +readme = { file = "README.rst", content-type = "text/x-rst" } +authors = [ + { name = "Zyte Group Ltd", email = "opensource@zyte.com" }, +] +license = { file = "LICENSE" } +requires-python = ">=3.10" +dependencies = [ + "aiohttp>=3.8.0", + "attrs>=20.1.0", + "brotli>=0.5.2", + "runstats>=0.0.1", + "tenacity>=8.2.0", + "tqdm>=4.16.0", + "w3lib>=2.1.1", +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] + +[project.optional-dependencies] +x402 = [ + "eth-account>=0.13.7", + "x402>=0.1.1,<2.0.0", +] + +[project.scripts] +zyte-api = "zyte_api.__main__:_main" + +[project.urls] +Homepage = "https://github.com/zytedata/python-zyte-api" +Documentation = "https://python-zyte-api.readthedocs.io/en/stable/" + +[tool.hatch.build.targets.sdist] +include = [ + "/docs", + "/tests", + "/zyte_api", + "/.pre-commit-config.yaml", + "/CHANGES.rst", + "/tox.ini", +] + +[tool.hatch.version] +path = "zyte_api/__version__.py" + +[tool.hatch.build.targets.wheel] +packages = ["zyte_api"] + [tool.bumpversion] current_version = "0.9.0" commit = true @@ -13,9 +78,6 @@ regex = true [[tool.bumpversion.files]] filename = "docs/conf.py" -[[tool.bumpversion.files]] -filename = "setup.py" - [[tool.bumpversion.files]] filename = "zyte_api/__version__.py" @@ -188,3 +250,6 @@ split-on-trailing-comma = false [tool.ruff.lint.pydocstyle] convention = "pep257" + +[tool.sphinx-scrapy] +python-version = "3.13" # In sync with test.yml diff --git a/setup.py b/setup.py deleted file mode 100755 index 71dd048..0000000 --- a/setup.py +++ /dev/null @@ -1,50 +0,0 @@ -from pathlib import Path - -from setuptools import find_packages, setup - -setup( - name="zyte-api", - version="0.9.0", - description="Python interface to Zyte API", - long_description=Path("README.rst").read_text(encoding="utf-8"), - long_description_content_type="text/x-rst", - author="Zyte Group Ltd", - author_email="opensource@zyte.com", - url="https://github.com/zytedata/python-zyte-api", - packages=find_packages(exclude=["tests", "examples"]), - package_data={ - "zyte_api": ["py.typed"], - }, - include_package_data=True, - entry_points={ - "console_scripts": ["zyte-api=zyte_api.__main__:_main"], - }, - install_requires=[ - "aiohttp>=3.8.0", - "attrs>=20.1.0", - "brotli>=0.5.2", - "runstats>=0.0.1", - "tenacity>=8.2.0", - "tqdm>=4.16.0", - "w3lib>=2.1.1", - ], - extras_require={ - "x402": [ - "eth-account>=0.13.7", - "x402>=0.1.1,<2.0.0", - ] - }, - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - ], -) diff --git a/tox.ini b/tox.ini index 18d31e6..db78250 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,7 @@ [tox] -envlist = pre-commit,mypy,min,min-x402,py39,py310,py311,py312,py313,x402,docs,twine +requires = + sphinx-scrapy[tox] @ git+https://github.com/scrapy/sphinx-scrapy.git@0.8.3 +envlist = pre-commit,mypy,min,min-x402,py310,py311,py312,py313,x402,docs,twine [testenv] deps = @@ -31,7 +33,7 @@ deps = w3lib==2.1.1 [testenv:min] -basepython = python3.9 +basepython = python3.10 deps = {[min]deps} [testenv:min-x402] @@ -54,14 +56,6 @@ commands = mypy \ zyte_api \ tests -[testenv:docs] -changedir = docs -deps = - -rdocs/requirements.txt -basepython = python3 -commands = - sphinx-build -W -b html . {envtmpdir}/html - [testenv:pre-commit] deps = pre-commit commands = pre-commit run --all-files --show-diff-on-failure diff --git a/zyte_api/_retry.py b/zyte_api/_retry.py index d784d23..886f8ce 100644 --- a/zyte_api/_retry.py +++ b/zyte_api/_retry.py @@ -5,7 +5,7 @@ from collections import Counter from datetime import timedelta from itertools import count -from typing import TYPE_CHECKING, Any, Callable, Union, cast +from typing import TYPE_CHECKING, Any, cast from warnings import warn from aiohttp import client_exceptions @@ -28,6 +28,8 @@ from ._errors import RequestError if TYPE_CHECKING: + from collections.abc import Callable + from tenacity.wait import wait_base logger = logging.getLogger(__name__) @@ -80,7 +82,7 @@ def __call__(self, retry_state: RetryCallState) -> bool: return retry_state.counter[self._counter_id] >= self._max_count # type: ignore[attr-defined] -time_unit_type = Union[int, float, timedelta] +time_unit_type = int | float | timedelta def to_seconds(time_unit: time_unit_type) -> float: diff --git a/zyte_api/stats.py b/zyte_api/stats.py index 8ecdf5c..f00f41c 100644 --- a/zyte_api/stats.py +++ b/zyte_api/stats.py @@ -3,7 +3,7 @@ import functools import time from collections import Counter -from typing import TYPE_CHECKING, Callable, Optional +from typing import TYPE_CHECKING, Optional import attr from runstats import Statistics @@ -11,9 +11,11 @@ from zyte_api.errors import ParsedError if TYPE_CHECKING: - # typing.ParamSpec requires Python 3.10 + from collections.abc import Callable + from typing import ParamSpec + # typing.Self requires Python 3.11 - from typing_extensions import ParamSpec, Self + from typing_extensions import Self _P = ParamSpec("_P")