From dbe6bebd548c0a7ee0dd4ef6ec8e53a06026d2eb Mon Sep 17 00:00:00 2001 From: MudassarTariq Date: Thu, 29 Oct 2026 23:01:16 +0500 Subject: [PATCH 1/3] Update Python SDK dependencies for 3.0.0 --- .github/workflows/checks.yml | 49 ++++ CHANGELOG.md | 6 + CONTRIBUTING.md | 13 +- MANIFEST.in | 3 + README.md | 2 +- postman/mocks/ipgeolocation/config.yaml | 7 + postman/mocks/ipgeolocation/default.js | 51 ++++ .../python-sdk/python-sdk.context.yaml | 23 ++ .../python-sdk/python-sdk.sim.yaml | 8 + pyproject.toml | 32 ++- tests/constraints-minimum.txt | 7 + tests/test_http_integration.py | 263 ++++++++++++++++++ 12 files changed, 447 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/checks.yml create mode 100644 MANIFEST.in create mode 100644 postman/mocks/ipgeolocation/config.yaml create mode 100644 postman/mocks/ipgeolocation/default.js create mode 100644 postman/simulations/python-sdk/python-sdk.context.yaml create mode 100644 postman/simulations/python-sdk/python-sdk.sim.yaml create mode 100644 tests/constraints-minimum.txt create mode 100644 tests/test_http_integration.py diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..9d955fb --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,49 @@ +name: Checks + +on: + pull_request: + push: + branches: [master] + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + python: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"] + include: + - python: "3.10" + minimum: true + env: + IPGEO_RUN_LIVE_TESTS: "false" + IPGEO_RUN_LIVE_HARDENING: "false" + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: ${{ matrix.python }} + - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: "24.22.1" + - run: npm install --global postman-cli@1.73.0 + - run: python -m pip install --upgrade pip + - name: Install dependencies + if: ${{ !matrix.minimum }} + run: python -m pip install -e '.[dev]' + - name: Install minimum dependencies + if: matrix.minimum + run: python -m pip install -c tests/constraints-minimum.txt -e '.[dev]' + - run: python -m pip check + - run: python -m pip_audit --skip-editable + - run: ruff check src tests + - run: mypy src + - run: pyright --pythonpath "$(command -v python)" + - run: pytest --cov=src/ipgeolocation --cov-report=term-missing + - run: python -m build diff --git a/CHANGELOG.md b/CHANGELOG.md index 4832125..dd55af4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 3.0.0 + +- Requires Python 3.10 or newer. +- Updated HTTP, certificate, test and build dependency minimums. +- Added dependency auditing and checks across supported Python versions. + ## 2.0.0 - Added the Python SDK for the IPGeolocation API unified endpoints. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7cdfcc1..5fc5a23 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,8 +2,9 @@ ## Requirements -- Python 3.8+ +- Python 3.10+ - `pip` +- Node.js 24 and Postman CLI 1.73.0 for local HTTP integration tests ## Setup @@ -11,6 +12,7 @@ python3 -m venv .venv source .venv/bin/activate pip install -e .[dev] +npm install --global postman-cli@1.73.0 ``` ## Run Tests @@ -19,6 +21,8 @@ pip install -e .[dev] pytest ``` +HTTP integration tests run both clients against a local Postman simulation. + ## Coverage Check ```bash @@ -33,6 +37,13 @@ mypy src pyright ``` +## Dependency and Build Checks + +```bash +pip-audit +python -m build +``` + ## Live Tests Live tests are disabled by default and consume API credits. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..6be68a2 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include CONTRIBUTING.md +include tests/constraints-minimum.txt +recursive-include postman *.yaml *.js diff --git a/README.md b/README.md index 7f8c524..7f468db 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Typed Python SDK for the [IPGeolocation.io](https://ipgeolocation.io/) IP Geoloc - Single lookup (`/v3/ipgeo`) and bulk lookup (`/v3/ipgeo-bulk`) - Sync client built on `requests` and async client built on `httpx` - Typed response models plus raw JSON/XML methods -- Python 3.8+ +- Python 3.10+ ## Table of Contents diff --git a/postman/mocks/ipgeolocation/config.yaml b/postman/mocks/ipgeolocation/config.yaml new file mode 100644 index 0000000..2965be1 --- /dev/null +++ b/postman/mocks/ipgeolocation/config.yaml @@ -0,0 +1,7 @@ +version: 1 +name: ipgeolocation +origin: https://api.ipgeolocation.io +interceptRequests: true +scenarios: + - name: default + path: default.js diff --git a/postman/mocks/ipgeolocation/default.js b/postman/mocks/ipgeolocation/default.js new file mode 100644 index 0000000..c52337d --- /dev/null +++ b/postman/mocks/ipgeolocation/default.js @@ -0,0 +1,51 @@ +const http = require('node:http'); + +http.createServer((req, res) => { + const url = new URL(req.url, 'http://localhost'); + const reply = (status, body) => { + res.writeHead(status, { + 'Content-Type': 'application/json', + 'X-Credits-Charged': '1', + 'X-Successful-Records': '1', + }); + res.end(JSON.stringify(body)); + }; + if (!['/v3/ipgeo', '/v3/ipgeo-bulk'].includes(url.pathname)) { + return reply(501, { message: 'Endpoint not modeled' }); + } + const bulk = url.pathname === '/v3/ipgeo-bulk'; + if (req.method !== (bulk ? 'POST' : 'GET')) { + return reply(400, { message: 'Unexpected HTTP method' }); + } + const key = url.searchParams.get('apiKey'); + const statuses = { BAD_KEY: 401, FORBIDDEN: 403, RATE_LIMIT: 429, SERVER_ERROR: 503 }; + if (statuses[key]) return reply(statuses[key], { message: `Test failure for ${key}` }); + if (key === 'BAD_JSON') { res.writeHead(200); return res.end('{'); } + if (key === 'BAD_ENCODING') { + res.writeHead(200, { 'Content-Type': 'application/json; charset=unknown-encoding' }); + return res.end(JSON.stringify({ ip: '8.8.8.8', location: { city: '東京' } })); + } + if (key === 'LARGE_RESPONSE') { + return reply(200, { ip: '8.8.8.8', location: { city: '東京'.repeat(2000) } }); + } + if (key === 'SLOW_REQUEST') { + return setTimeout(() => reply(200, { ip: '8.8.8.8' }), 300); + } + if (key !== 'TEST_KEY &+?=#%' && !(key === null && req.headers.origin === 'https://app.example.test')) { + return reply(401, { message: 'Invalid test key' }); + } + if (bulk && (req.headers['content-type'] !== 'application/json' || !Array.isArray(req.body?.ips))) { + return reply(400, { message: 'Expected JSON ips array' }); + } + const result = { + ip: bulk ? req.body.ips[0] : url.searchParams.get('ip'), + location: { city: 'Test City' }, + _request: { + method: req.method, + query: Object.fromEntries(url.searchParams), + headers: req.headers, + body: bulk ? req.body : null, + }, + }; + reply(200, bulk ? [result, { message: 'IP address not found' }] : result); +}).listen(process.env.PORT || 4500); diff --git a/postman/simulations/python-sdk/python-sdk.context.yaml b/postman/simulations/python-sdk/python-sdk.context.yaml new file mode 100644 index 0000000..6d2f5d7 --- /dev/null +++ b/postman/simulations/python-sdk/python-sdk.context.yaml @@ -0,0 +1,23 @@ +purpose: + stands_in_for: The IPGeolocation single and bulk endpoints used by the Python SDK. + unblocks: HTTP transport, encoding, authentication, parsing, timeout and response-size regression tests. +assumptions: + - id: transport-compatibility + status: supported + claim: Both clients preserve request and response behavior with the updated dependencies. + reason: Both clients passed the same real HTTP cases using current and minimum dependency versions; the fixture echoes requests for comparison. + verification: On 2026-10-29, 26 HTTP cases passed with both dependency sets. They cover single and bulk lookups, raw responses, query encoding, headers, metadata, errors, malformed JSON, unknown encoding, timeouts and streamed response limits. Simulation request events confirm that the real transports reached the stand-in. + source: tests/test_http_integration.py; src/ipgeolocation/_transport.py + updated_at: '2026-10-29' + - id: live-provider-behavior + status: unknown + claim: The simulation establishes live API behavior and account permissions. + reason: The official API Hub workspace listed no published code mock, and Orbit discovery returned HTTP 403. The fixture models the existing SDK tests and client request builders. + verification: No live provider request or API credential is used. + source: src/ipgeolocation/client.py; src/ipgeolocation/async_client.py; postman/mocks/ipgeolocation/default.js + updated_at: '2026-10-29' +fingerprints: + - id: python-sdk-security-update + date: '2026-10-29' + commit: 65d06151e88d23f1018b6a509cb24cc4302b8342 plus dependency and integration-test changes. + cli: Postman CLI 1.73.0. diff --git a/postman/simulations/python-sdk/python-sdk.sim.yaml b/postman/simulations/python-sdk/python-sdk.sim.yaml new file mode 100644 index 0000000..e8763b7 --- /dev/null +++ b/postman/simulations/python-sdk/python-sdk.sim.yaml @@ -0,0 +1,8 @@ +simulation: python-sdk +routing: path +mocks: + - name: ipgeolocation + routeKey: ipgeolocation + origin: + - api.ipgeolocation.io + path: ../../mocks/ipgeolocation/config.yaml diff --git a/pyproject.toml b/pyproject.toml index 9d8da0d..b79e6aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,15 @@ [build-system] -requires = ["setuptools>=69", "wheel"] +requires = ["setuptools>=84.1.0", "wheel>=0.48.0"] build-backend = "setuptools.build_meta" [project] name = "ipgeolocationio" -version = "2.0.0" +version = "3.0.0" description = "Typed Python SDK for the IPGeolocation.io IP location API, with sync and async clients." readme = "README.md" license = "MIT" license-files = ["LICENSE"] -requires-python = ">=3.8" +requires-python = ">=3.10" authors = [ { name = "IPGeolocation Team", email = "support@ipgeolocation.io" } ] @@ -27,29 +27,35 @@ classifiers = [ "Intended Audience :: Developers", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "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", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Typing :: Typed", "Topic :: Internet", "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ - "httpx>=0.28,<1", - "requests>=2.32.4,<3", + "httpx>=0.28.1,<1", + "requests>=2.33.0,<3", + "urllib3>=2.8.0,<3", + "anyio>=4.14.2,<5", + "certifi>=2026.10.23", ] [project.optional-dependencies] dev = [ - "pytest>=8.0.0,<9", + "pytest>=9.0.3,<10", "pytest-cov>=7.0.0,<8", "pyright>=1.1.408,<2", "ruff>=0.15.6,<0.16", "mypy>=1.19.1,<2", "types-requests>=2.32.4,<3", + "build>=1.6.1,<2", + "pip-audit>=2.10.1,<3", + "setuptools>=84.1.0", ] [project.urls] @@ -82,16 +88,14 @@ show_missing = true skip_covered = false [tool.ruff] -target-version = "py38" +target-version = "py310" line-length = 100 [tool.ruff.lint] select = ["E", "F", "I"] [tool.mypy] -# mypy 1.19+ no longer supports targeting Python 3.8 directly. -# Pyright covers the real Python 3.8 compatibility check for this SDK. -python_version = "3.9" +python_version = "3.10" files = ["src"] check_untyped_defs = true warn_unused_ignores = true @@ -101,7 +105,5 @@ no_implicit_optional = true [tool.pyright] include = ["src"] -pythonVersion = "3.8" +pythonVersion = "3.10" typeCheckingMode = "standard" -venvPath = "." -venv = ".venv" diff --git a/tests/constraints-minimum.txt b/tests/constraints-minimum.txt new file mode 100644 index 0000000..851311c --- /dev/null +++ b/tests/constraints-minimum.txt @@ -0,0 +1,7 @@ +requests==2.33.0 +httpx==0.28.1 +urllib3==2.8.0 +anyio==4.14.2 +certifi==2026.10.23 +pytest==9.0.3 +setuptools==84.1.0 diff --git a/tests/test_http_integration.py b/tests/test_http_integration.py new file mode 100644 index 0000000..abd6d4f --- /dev/null +++ b/tests/test_http_integration.py @@ -0,0 +1,263 @@ +"""Exercise both SDK transports against a local Postman simulation.""" + +from __future__ import annotations + +import asyncio +import json +import os +import shutil +import signal +import subprocess +import time +from pathlib import Path + +import pytest + +from ipgeolocation import ( + ApiException, + AsyncIpGeolocationClient, + BulkLookupError, + BulkLookupIpGeolocationRequest, + BulkLookupSuccess, + IpGeolocationClient, + IpGeolocationClientConfig, + LookupIpGeolocationRequest, + RateLimitException, + RequestTimeoutException, + SerializationException, + ServerErrorException, + TransportException, + UnauthorizedException, +) +from ipgeolocation._transport import HttpRequestData, HttpxAsyncTransport, RequestsHttpTransport + +TEST_KEY = "TEST_KEY &+?=#%" + + +@pytest.fixture(scope="module") +def api_url(tmp_path_factory): + postman = shutil.which("postman") + if postman is None or os.name != "posix": + pytest.skip("HTTP integration tests require Postman CLI on Linux or macOS") + log_path = tmp_path_factory.mktemp("simulation") / "requests.ndjson" + with log_path.open("w") as output, pytest.MonkeyPatch.context() as env: + env.setenv("NO_PROXY", "localhost,127.0.0.1") + process = subprocess.Popen( + [ + postman, + "simulation", + "run", + "postman/simulations/python-sdk/python-sdk.sim.yaml", + "--port", + "auto", + "--no-history", + "--output", + "ndjson", + ], + cwd=Path(__file__).resolve().parents[1], + stdout=output, + stderr=subprocess.STDOUT, + start_new_session=True, + ) + try: + deadline = time.monotonic() + 20 + while time.monotonic() < deadline: + for line in log_path.read_text().splitlines(): + try: + event = json.loads(line) + except json.JSONDecodeError: + continue + if event.get("event") == "listening": + yield event["mocks"][0]["address"] + assert '"event":"request"' in log_path.read_text(errors="replace") + return + assert process.poll() is None, log_path.read_text() + time.sleep(0.1) + pytest.fail("Postman simulation did not start: " + log_path.read_text()) + finally: + try: + os.killpg(process.pid, signal.SIGINT) + except ProcessLookupError: + pass + try: + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + process.wait(timeout=5) + + +@pytest.fixture(params=[IpGeolocationClient, AsyncIpGeolocationClient], ids=["requests", "httpx"]) +def client_type(request): + return request.param + + +def _call(client_type, api_url, method, request, *, api_key=TEST_KEY, origin=None, timeout=1): + config = IpGeolocationClientConfig( + api_key=api_key, + request_origin=origin, + base_url=api_url, + connect_timeout=min(0.1, timeout), + read_timeout=timeout, + ) + if client_type is AsyncIpGeolocationClient: + + async def run(): + async with client_type(config) as client: + return await getattr(client, method)(request) + + return asyncio.run(run()) + with client_type(config) as client: + return getattr(client, method)(request) + + +def test_http_lookup(client_type, api_url): + result = _call( + client_type, + api_url, + "lookup_ip_geolocation", + LookupIpGeolocationRequest(ip="2001:db8::1", include=["security"]), + ) + assert result.data.ip == "2001:db8::1" + assert result.data.location.city == "Test City" + assert result.metadata.status_code == 200 + assert result.metadata.credits_charged == 1 + assert result.metadata.header_values("x-credits-charged") == ("1",) + assert result.metadata.first_header_value("X-CREDITS-CHARGED") == "1" + + +def test_http_raw_lookup_preserves_query_and_headers(client_type, api_url): + result = _call( + client_type, + api_url, + "lookup_ip_geolocation_raw", + LookupIpGeolocationRequest( + ip="8.8.8.8", fields=["location.city"], headers={"X-Request-Test": "kept"} + ), + ) + request = json.loads(result.data)["_request"] + assert request["query"]["apiKey"] == TEST_KEY + assert request["query"]["fields"] == "location.city" + assert request["headers"]["x-request-test"] == "kept" + assert request["headers"]["user-agent"] == client_type.default_user_agent() + + +def test_http_bulk_lookup(client_type, api_url): + result = _call( + client_type, + api_url, + "bulk_lookup_ip_geolocation", + BulkLookupIpGeolocationRequest(ips=["8.8.8.8", "invalid-ip"]), + ) + assert isinstance(result.data[0], BulkLookupSuccess) + assert result.data[0].data.ip == "8.8.8.8" + assert isinstance(result.data[1], BulkLookupError) + assert result.data[1].error.message == "IP address not found" + + +def test_http_raw_bulk_lookup(client_type, api_url): + result = _call( + client_type, + api_url, + "bulk_lookup_ip_geolocation_raw", + BulkLookupIpGeolocationRequest(ips=["8.8.8.8", "invalid-ip"]), + ) + request = json.loads(result.data)[0]["_request"] + assert request["method"] == "POST" + assert request["body"] == {"ips": ["8.8.8.8", "invalid-ip"]} + + +@pytest.mark.parametrize( + "key,exception,status", + [ + ("BAD_KEY", UnauthorizedException, 401), + ("FORBIDDEN", ApiException, 403), + ("RATE_LIMIT", RateLimitException, 429), + ("SERVER_ERROR", ServerErrorException, 503), + ], +) +def test_http_errors(client_type, api_url, key, exception, status): + with pytest.raises(exception) as error: + _call( + client_type, + api_url, + "lookup_ip_geolocation", + LookupIpGeolocationRequest(ip="8.8.8.8"), + api_key=key, + ) + assert error.value.status_code == status + assert error.value.api_message == f"Test failure for {key}" + + +def test_http_invalid_json(client_type, api_url): + with pytest.raises(SerializationException): + _call( + client_type, + api_url, + "lookup_ip_geolocation", + LookupIpGeolocationRequest(ip="8.8.8.8"), + api_key="BAD_JSON", + ) + + +def test_http_unknown_encoding_falls_back_to_utf8(client_type, api_url): + result = _call( + client_type, + api_url, + "lookup_ip_geolocation", + LookupIpGeolocationRequest(ip="8.8.8.8"), + api_key="BAD_ENCODING", + ) + assert result.data.location.city == "東京" + + +def test_http_timeout(client_type, api_url): + with pytest.raises(RequestTimeoutException): + _call( + client_type, + api_url, + "lookup_ip_geolocation", + LookupIpGeolocationRequest(ip="8.8.8.8"), + api_key="SLOW_REQUEST", + timeout=0.05, + ) + + +def test_http_origin_authentication(client_type, api_url): + result = _call( + client_type, + api_url, + "lookup_ip_geolocation", + LookupIpGeolocationRequest(ip="8.8.8.8"), + api_key=None, + origin="https://app.example.test", + ) + assert result.data.ip == "8.8.8.8" + + +def test_http_streaming_size_limit(client_type, api_url): + request = HttpRequestData( + url=f"{api_url}/v3/ipgeo?apiKey=LARGE_RESPONSE", + method="GET", + headers={}, + body=None, + connect_timeout=0.1, + read_timeout=1, + ) + if client_type is AsyncIpGeolocationClient: + + async def run(): + transport = HttpxAsyncTransport(max_response_body_chars=128) + try: + with pytest.raises(TransportException, match="Response body exceeded max size"): + await transport.send(request) + finally: + await transport.close() + + asyncio.run(run()) + else: + transport = RequestsHttpTransport(max_response_body_chars=128) + try: + with pytest.raises(TransportException, match="Response body exceeded max size"): + transport.send(request) + finally: + transport.close() From 20e6b8a7d241342c5e3cb1cdc0e3bd1f325b6aba Mon Sep 17 00:00:00 2001 From: MudassarTariq Date: Thu, 29 Oct 2026 23:07:02 +0500 Subject: [PATCH 2/3] Run dependency audits with supported scanner dependencies --- .github/workflows/checks.yml | 19 ++++++++++++++++++- .gitignore | 1 + CONTRIBUTING.md | 10 +++++++++- pyproject.toml | 5 ++++- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 9d955fb..70a1638 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -33,6 +33,10 @@ jobs: with: node-version: "24.22.1" - run: npm install --global postman-cli@1.73.0 + - name: Create test environment + run: | + python -m venv .venv + echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH" - run: python -m pip install --upgrade pip - name: Install dependencies if: ${{ !matrix.minimum }} @@ -41,9 +45,22 @@ jobs: if: matrix.minimum run: python -m pip install -c tests/constraints-minimum.txt -e '.[dev]' - run: python -m pip check - - run: python -m pip_audit --skip-editable - run: ruff check src tests - run: mypy src - run: pyright --pythonpath "$(command -v python)" - run: pytest --cov=src/ipgeolocation --cov-report=term-missing - run: python -m build + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: "3.11" + - name: Create audit environment + run: | + python -m venv .venv-audit + .venv-audit/bin/python -m pip install --upgrade pip + .venv-audit/bin/python -m pip install -e '.[audit]' + - name: Audit scanner dependencies + run: .venv-audit/bin/pip-audit --skip-editable + - name: Audit SDK dependencies + run: >- + .venv-audit/bin/pip-audit --skip-editable + --path ".venv/lib/python${{ matrix.python }}/site-packages" diff --git a/.gitignore b/.gitignore index 1578a04..19e8efb 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ build/ dist/ *.egg-info/ .venv/ +.venv-audit/ venv/ .claude/ CLAUDE-REVIEW.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5fc5a23..eedf8c5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,10 +40,18 @@ pyright ## Dependency and Build Checks ```bash -pip-audit python -m build ``` +Dependency audits use Python 3.11+ for the scanner and inspect the SDK's test environment: + +```bash +python3.11 -m venv .venv-audit +.venv-audit/bin/python -m pip install -e '.[audit]' +.venv-audit/bin/pip-audit --skip-editable \ + --path "$(.venv/bin/python -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')" +``` + ## Live Tests Live tests are disabled by default and consume API credits. diff --git a/pyproject.toml b/pyproject.toml index b79e6aa..2360df1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,9 +54,12 @@ dev = [ "mypy>=1.19.1,<2", "types-requests>=2.32.4,<3", "build>=1.6.1,<2", - "pip-audit>=2.10.1,<3", "setuptools>=84.1.0", ] +audit = [ + "pip-audit>=2.10.1,<3", + "filelock>=4.2.1", +] [project.urls] Homepage = "https://ipgeolocation.io/" From 40d57a83f0c72812458addf77b527e93d1e3c8b0 Mon Sep 17 00:00:00 2001 From: MudassarTariq Date: Thu, 29 Oct 2026 23:10:01 +0500 Subject: [PATCH 3/3] Require patched setuptools in the audit environment --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 2360df1..be8e274 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ dev = [ audit = [ "pip-audit>=2.10.1,<3", "filelock>=4.2.1", + "setuptools>=84.1.0", ] [project.urls]