From 6fccc8c7ef66e7770251f1db3e4b83ace23cde3c Mon Sep 17 00:00:00 2001 From: "aiste.grigaliunaite@decodo.com" Date: Mon, 26 Oct 2026 17:10:10 +0200 Subject: [PATCH 1/2] feat: support python 3.10 --- .github/workflows/test.yml | 5 ++++- README.md | 6 +++--- pyproject.toml | 7 ++++++- src/decodo/_compat.py | 20 +++++++++++++++++++ .../web_scraping_api/generate_targets.py | 2 +- src/decodo/schema/types.py | 4 +++- src/decodo/targets.py | 2 +- 7 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 src/decodo/_compat.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7c17005..4fe6d6e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,12 +9,15 @@ on: jobs: test: runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: - python-version: "3.12" + python-version: ${{ matrix.python-version }} - name: Install dependencies run: pip install -e ".[dev]" diff --git a/README.md b/README.md index 8616206..84ab52a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Decodo Python SDK -![Python](https://img.shields.io/badge/python-%3E%3D3.12-green) +![Python](https://img.shields.io/badge/python-%3E%3D3.10-green) ![License](https://img.shields.io/badge/license-MIT-blue) [![](https://dcbadge.limes.pink/api/server/https://discord.gg/Ja8dqKgvbZ)](https://discord.gg/Ja8dqKgvbZ) @@ -14,7 +14,7 @@ Build scraping workflows for search engines, eCommerce platforms, social media, - Fully typed targets and parameters with IDE autocomplete - Sync, async, and batch scraping methods -- Built on `httpx`, Python 3.12+ +- Built on `httpx`, Python 3.10+ - Typed error hierarchy for safer integrations - Built for Python and modern tooling @@ -34,7 +34,7 @@ Instead of manually constructing HTTP requests and validating payloads, you can ## Requirements -- Python 3.12+ +- Python 3.10+ ## Installation diff --git a/pyproject.toml b/pyproject.toml index 026ec5f..15a1d51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,16 +8,20 @@ authors = [{ name = "Decodo" }] keywords = ["decodo", "scraping", "web-scraping", "api"] classifiers = [ "Programming Language :: Python :: 3", + "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", "Intended Audience :: Developers", "Topic :: Internet :: WWW/HTTP", ] -requires-python = ">=3.12" +requires-python = ">=3.10" dependencies = [ "httpx>=0.27.0", "pydantic>=2.0", "jsonschema>=4.0", + "typing-extensions>=4.6", ] [project.urls] @@ -54,6 +58,7 @@ select = ["E", "F", "I", "UP"] [tool.mypy] strict = true +python_version = "3.10" packages = ["decodo"] exclude = "src/decodo/generated|^examples/|^build/" diff --git a/src/decodo/_compat.py b/src/decodo/_compat.py new file mode 100644 index 0000000..bd83ee2 --- /dev/null +++ b/src/decodo/_compat.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +import sys +from enum import Enum + +if sys.version_info >= (3, 11): + from enum import StrEnum +else: + + class StrEnum(str, Enum): + """Backport of ``enum.StrEnum`` (3.11+): members are strings and ``str()`` gives the value.""" + + def __str__(self) -> str: + return str(self.value) + + def __format__(self, format_spec: str) -> str: + return str.__format__(str(self.value), format_spec) + + +__all__ = ["StrEnum"] diff --git a/src/decodo/codegen/web_scraping_api/generate_targets.py b/src/decodo/codegen/web_scraping_api/generate_targets.py index 78c19b1..57cea32 100644 --- a/src/decodo/codegen/web_scraping_api/generate_targets.py +++ b/src/decodo/codegen/web_scraping_api/generate_targets.py @@ -58,7 +58,7 @@ def _get_targets_enum_file_contents(api: WebScrapingApiIR) -> str: lines.append("# Run `python -m decodo.codegen.codegen` to regenerate when the IR schema changes.") lines.append("from __future__ import annotations") lines.append("") - lines.append("from enum import StrEnum") + lines.append("from decodo._compat import StrEnum") lines.append("") lines.append("") diff --git a/src/decodo/schema/types.py b/src/decodo/schema/types.py index 66d3651..f428c5e 100644 --- a/src/decodo/schema/types.py +++ b/src/decodo/schema/types.py @@ -1,6 +1,8 @@ from __future__ import annotations -from typing import Any, Protocol, Required, TypedDict +from typing import Any, Protocol + +from typing_extensions import Required, TypedDict class TargetMeta(TypedDict): diff --git a/src/decodo/targets.py b/src/decodo/targets.py index ee96c84..b4413a0 100644 --- a/src/decodo/targets.py +++ b/src/decodo/targets.py @@ -2,7 +2,7 @@ # Run `python -m decodo.codegen.codegen` to regenerate when the IR schema changes. from __future__ import annotations -from enum import StrEnum +from decodo._compat import StrEnum class Target(StrEnum): From b5ed5ffb996e25840f4ef91e224eb78bc2715a96 Mon Sep 17 00:00:00 2001 From: "aiste.grigaliunaite@decodo.com" Date: Mon, 26 Oct 2026 17:32:55 +0200 Subject: [PATCH 2/2] chore: await all python version tests in CI --- .github/workflows/test.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4fe6d6e..3e79102 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,8 @@ on: pull_request: jobs: - test: + test-matrix: + name: test (${{ matrix.python-version }}) runs-on: ubuntu-latest strategy: matrix: @@ -38,3 +39,10 @@ jobs: python -m venv /tmp/wheel-env /tmp/wheel-env/bin/pip install dist/*.whl cd /tmp && /tmp/wheel-env/bin/python -c "import decodo; assert decodo._codegen_available; from decodo import ScrapeRequest" + + test: + needs: test-matrix + if: always() + runs-on: ubuntu-latest + steps: + - run: test "${{ needs.test-matrix.result }}" = success