From e26ccba7fe15bb956d1d8e656da7408851fbab9e Mon Sep 17 00:00:00 2001 From: NotoriousRebel <36310667+NotoriousRebel@users.noreply.github.com> Date: Sun, 16 Aug 2026 18:06:31 -0400 Subject: [PATCH] refactor: replace ujson with standard json Refs NotoriousRebel/theHarvester#325 --- CHANGELOG.md | 3 +- pyproject.toml | 2 - tests/test_runtime_dependencies.py | 49 ++++++++++++++++++++ theHarvester/__main__.py | 5 +- theHarvester/discovery/commoncrawl.py | 13 +----- theHarvester/discovery/gitlabsearch.py | 13 +----- theHarvester/discovery/robtex.py | 13 +----- theHarvester/discovery/subdomainfinderc99.py | 4 +- theHarvester/discovery/windvane.py | 41 +++++++++------- theHarvester/lib/core.py | 7 +-- uv.lock | 47 ------------------- 11 files changed, 85 insertions(+), 112 deletions(-) create mode 100644 tests/test_runtime_dependencies.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cfe2140..5cb168aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Raised the minimum supported Python version to 3.14, selected it for source checkouts, and limited release validation to that runtime. +- Replaced the maintenance-only UltraJSON dependency with Python's standard `json` module for provider payloads and legacy reports, removing the native runtime extension without changing JSONL or API serialization. - Centralized passive-source completion reporting in an immutable `SourceExecutionReport` returned by adapters, with the source runner alone deriving partial and no-result outcomes from retained evidence; removed mutable per-adapter `execution_status` and `stop_reason` state and made the runner reject adapters that still expose either field. - Updated BeVigil, Dymo, FOFA, FullHunt, Hunter.how, Netlas, ONYPHE, SecurityScorecard, SecurityTrails, SherlockEye, SubdomainFinder C99, VirusTotal, WhoisXML, and ZoomEye provider contracts to retain scoped partial evidence and report authentication, quota, transport, HTTP, and malformed-response outcomes truthfully. FOFA and ONYPHE now honor the operator result limit across documented pagination, while ZoomEye uses the current `POST /v2/search` API and no longer stops after five empty result pages. - Replaced runtime takeover fingerprint downloads and global body-substring matches with pinned provider-gated DNS, wildcard controls, and compound HTTP rules. Every checked hostname is now stored as an indicator, no-indicator, or inconclusive outcome with typed DNS, HTTP, rule, and error details in JSONL, SQLite, the API, and HarvestView. Direct checks share one cookie-free HTTP session, keep bounded response bodies, and rely on the whole-run deadline instead of silently inheriting aiohttp's default timeout. @@ -69,7 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Replaced deprecated hostname resolution with `getaddrinfo`-based handling ([6a847435](https://github.com/laramies/theHarvester/commit/6a847435)). - Reworked routine CI to use read-only permissions, non-mutating Ruff checks, offline tests, and explicit opt-in live provider checks ([72e5820f](https://github.com/laramies/theHarvester/commit/72e5820f)). - Grouped GitHub Actions, Python, and Docker Dependabot updates with a seven-day cooldown, and added a seven-day `uv` dependency freshness window ([7a947b66](https://github.com/laramies/theHarvester/commit/7a947b66), [52a79cdb](https://github.com/laramies/theHarvester/commit/52a79cdb)). -- Updated runtime dependencies: `aiohttp` to `3.14.1`, `beautifulsoup4` to `4.15.0`, `certifi` to `2026.6.17`, `fastapi` to `0.138.1`, `ujson` to `5.13.0`, and `uvicorn` to `0.49.0`. +- Updated runtime dependencies: `aiohttp` to `3.14.1`, `beautifulsoup4` to `4.15.0`, `certifi` to `2026.6.17`, `fastapi` to `0.138.1`, and `uvicorn` to `0.49.0`. - Updated development dependencies: `pytest` to `9.1.1`, `ruff` to `0.15.20`, and `ty` to `0.0.54`. - Updated CI and container maintenance pins, including `actions/checkout`, `astral-sh/setup-uv`, `astral-sh/ruff-action`, `github/codeql-action`, StepSecurity Harden-Runner, Docker actions, and the Python base image. - Expanded offline regression coverage for discovery providers, configuration contracts, logging, output, documentation, workflow policy, and scope boundaries. diff --git a/pyproject.toml b/pyproject.toml index 623de78f..d846eb82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,6 @@ dependencies = [ "httpx==0.28.1", "retrying==1.4.2", "sqlalchemy==2.0.51", - "ujson==5.13.0", "uvicorn==0.52.1", "uvloop==0.22.1; platform_system != 'Windows'", "winloop==0.6.3; platform_system == 'Windows'", @@ -52,7 +51,6 @@ dev = [ "types-python-dateutil==2.9.0.20260518", "types-PyYAML==6.0.12.20250915", "ruff==0.16.1", - "types-ujson==5.10.0.20250822", "wheel==0.47.0", "ty==0.0.69", ] diff --git a/tests/test_runtime_dependencies.py b/tests/test_runtime_dependencies.py new file mode 100644 index 00000000..3aeefa64 --- /dev/null +++ b/tests/test_runtime_dependencies.py @@ -0,0 +1,49 @@ +import subprocess +import sys +import textwrap +import tomllib +from pathlib import Path + + +def test_json_extensions_are_not_installation_requirements() -> None: + project = tomllib.loads(Path('pyproject.toml').read_text(encoding='utf-8')) + + assert all(not dependency.startswith('ujson') for dependency in project['project']['dependencies']) + assert all(not dependency.startswith('types-ujson') for dependency in project['dependency-groups']['dev']) + + +def test_runtime_imports_without_optional_json_extensions() -> None: + script = textwrap.dedent( + """ + import builtins + import importlib + + real_import = builtins.__import__ + + def import_without_ujson(name, *args, **kwargs): + if name == 'ujson': + raise ModuleNotFoundError('ujson is unavailable') + return real_import(name, *args, **kwargs) + + builtins.__import__ = import_without_ujson + for module in ( + 'theHarvester.__main__', + 'theHarvester.discovery.commoncrawl', + 'theHarvester.discovery.gitlabsearch', + 'theHarvester.discovery.robtex', + 'theHarvester.discovery.subdomainfinderc99', + 'theHarvester.discovery.windvane', + 'theHarvester.lib.core', + ): + importlib.import_module(module) + """ + ) + + result = subprocess.run( + [sys.executable, '-c', script], + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode == 0, result.stderr diff --git a/theHarvester/__main__.py b/theHarvester/__main__.py index 666c2201..3696842d 100644 --- a/theHarvester/__main__.py +++ b/theHarvester/__main__.py @@ -20,7 +20,6 @@ from uuid import UUID, uuid4 import anyio import netaddr -import ujson from theHarvester.discovery import ( api_endpoints, @@ -589,7 +588,7 @@ async def start( for host in canonical_hosts: if host.ip not in shodan_hosts: output_logger.info( - ujson.dumps( + json.dumps( {'type': 'shodan-host', 'value': host.ip, 'details': host.to_details()}, indent=4, sort_keys=True, @@ -2144,7 +2143,7 @@ async def start( json_dict['shodan'] = shodanres async with await anyio.open_file(filename, 'w+') as fp: - dumped_json = ujson.dumps(json_dict, sort_keys=True) + dumped_json = json.dumps(json_dict, separators=(',', ':'), sort_keys=True) await fp.write(dumped_json) output_logger.info('[*] JSON File saved.') except (OSError, ValueError, TypeError, UnicodeEncodeError) as er: diff --git a/theHarvester/discovery/commoncrawl.py b/theHarvester/discovery/commoncrawl.py index 281462e4..055dbc11 100644 --- a/theHarvester/discovery/commoncrawl.py +++ b/theHarvester/discovery/commoncrawl.py @@ -1,8 +1,7 @@ import asyncio -import json as _stdlib_json +import json import logging from datetime import datetime, timedelta -from types import ModuleType from urllib.parse import urlencode, urlsplit from theHarvester.lib.core import AsyncFetcher, Core @@ -10,16 +9,6 @@ from theHarvester.lib.source_execution import SourceExecutionReport logger = logging.getLogger(__name__) -json: ModuleType = _stdlib_json -try: - import ujson as _ujson - - json = _ujson -except ImportError: - pass -except Exception: - pass - class SearchCommoncrawl: """Gather subdomains from every crawl ending within one year of the newest catalog entry. diff --git a/theHarvester/discovery/gitlabsearch.py b/theHarvester/discovery/gitlabsearch.py index c533ad11..8c606486 100644 --- a/theHarvester/discovery/gitlabsearch.py +++ b/theHarvester/discovery/gitlabsearch.py @@ -1,7 +1,6 @@ -import json as _stdlib_json +import json import logging import re -from types import ModuleType from urllib.parse import quote from theHarvester.lib.core import AsyncFetcher, Core @@ -9,16 +8,6 @@ from theHarvester.lib.hostnames import normalize_scoped_hostname logger = logging.getLogger(__name__) -json: ModuleType = _stdlib_json -try: - import ujson as _ujson - - json = _ujson -except ImportError: - pass -except Exception: - pass - class SearchGitlab: """Search public GitLab project metadata, README files, and user profiles.""" diff --git a/theHarvester/discovery/robtex.py b/theHarvester/discovery/robtex.py index 1bbe55a0..ef828fa1 100644 --- a/theHarvester/discovery/robtex.py +++ b/theHarvester/discovery/robtex.py @@ -1,7 +1,6 @@ -import json as _stdlib_json +import json import logging from ipaddress import ip_address -from types import ModuleType import aiohttp @@ -10,16 +9,6 @@ from theHarvester.lib.source_execution import SourceExecutionReport logger = logging.getLogger(__name__) -json: ModuleType = _stdlib_json -try: - import ujson as _ujson - - json = _ujson -except ImportError as e: - logger.info(f"'ujson' not available. Falling back to standard 'json' module. Reason: {e}") -except (AttributeError, OSError, RuntimeError, SystemError, ValueError) as e: - logger.info(f"Unexpected error while importing 'ujson'. Falling back to standard 'json'. Reason: {e}") - class SearchRobtex: """Gather IP addresses for a hostname from the Robtex passive DNS API.""" diff --git a/theHarvester/discovery/subdomainfinderc99.py b/theHarvester/discovery/subdomainfinderc99.py index 02e8c341..19a0ed8d 100644 --- a/theHarvester/discovery/subdomainfinderc99.py +++ b/theHarvester/discovery/subdomainfinderc99.py @@ -1,6 +1,6 @@ import asyncio +import json -import ujson from bs4 import BeautifulSoup from bs4.element import Tag @@ -45,7 +45,7 @@ class SearchSubdomainfinderc99: second_resp = await AsyncFetcher.post_fetch( self.server, session=session, - data=ujson.dumps(data), + data=json.dumps(data, separators=(',', ':')), include_metadata=True, ) if error := provider_http_error(second_resp): diff --git a/theHarvester/discovery/windvane.py b/theHarvester/discovery/windvane.py index c2e50b15..54eebe7c 100644 --- a/theHarvester/discovery/windvane.py +++ b/theHarvester/discovery/windvane.py @@ -1,22 +1,11 @@ -import json as _stdlib_json +import json import logging -from types import ModuleType from theHarvester.lib.core import AsyncFetcher, Core from theHarvester.lib.hostnames import normalize_scoped_hostname logger = logging.getLogger(__name__) -json: ModuleType = _stdlib_json -try: - import ujson as _ujson - - json = _ujson -except ImportError: - pass -except Exception: - pass - class SearchWindvane: """Class uses the Windvane API to gather subdomains and domain intelligence @@ -109,7 +98,12 @@ class SearchWindvane: data = {'domain': self.word, 'page_request': {'page': page, 'count': 30}} try: - response = await AsyncFetcher.post_fetch(url, headers=headers, data=json.dumps(data), proxy=self.proxy) + response = await AsyncFetcher.post_fetch( + url, + headers=headers, + data=json.dumps(data, separators=(',', ':')), + proxy=self.proxy, + ) if response: response_data = self._safe_parse_json(response) @@ -147,7 +141,12 @@ class SearchWindvane: data = {'domain': self.word, 'page_request': {'page': page, 'count': 30}} try: - response = await AsyncFetcher.post_fetch(url, headers=headers, data=json.dumps(data), proxy=self.proxy) + response = await AsyncFetcher.post_fetch( + url, + headers=headers, + data=json.dumps(data, separators=(',', ':')), + proxy=self.proxy, + ) if response: response_data = self._safe_parse_json(response) @@ -186,7 +185,12 @@ class SearchWindvane: data = {'email': self.word, 'page_request': {'page': 1, 'count': 50}} try: - response = await AsyncFetcher.post_fetch(url, headers=headers, data=json.dumps(data), proxy=self.proxy) + response = await AsyncFetcher.post_fetch( + url, + headers=headers, + data=json.dumps(data, separators=(',', ':')), + proxy=self.proxy, + ) if response: response_data = self._safe_parse_json(response) @@ -221,7 +225,12 @@ class SearchWindvane: } try: - response = await AsyncFetcher.post_fetch(url, headers=headers, data=json.dumps(data), proxy=self.proxy) + response = await AsyncFetcher.post_fetch( + url, + headers=headers, + data=json.dumps(data, separators=(',', ':')), + proxy=self.proxy, + ) if response: response_data = self._safe_parse_json(response) diff --git a/theHarvester/lib/core.py b/theHarvester/lib/core.py index 1f14bd34..d6281f28 100644 --- a/theHarvester/lib/core.py +++ b/theHarvester/lib/core.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio import contextlib -import json as stdlib_json +import json as json_loader import logging import random import re @@ -13,9 +13,6 @@ from typing import TYPE_CHECKING, Any, ClassVar, Literal import aiohttp import certifi - -# need to import as different name as to not shadow already existing json var in post_fetch -import ujson as json_loader import yaml from aiohttp_socks import ProxyConnector @@ -857,7 +854,7 @@ class AsyncFetcher: text = body.decode('utf-8') if not text.strip(): raise ValueError('empty JSON response') - parsed = stdlib_json.loads(text, parse_constant=_reject_json_constant) + parsed = json_loader.loads(text, parse_constant=_reject_json_constant) except (UnicodeDecodeError, ValueError, RecursionError) as error: raise ResponseStreamError('invalid-response') from error return FetcherResponse(body=parsed, status=response.status, headers=response_headers) diff --git a/uv.lock b/uv.lock index 16a825f2..9dd6ca9f 100644 --- a/uv.lock +++ b/uv.lock @@ -1131,7 +1131,6 @@ dependencies = [ { name = "pyyaml" }, { name = "retrying" }, { name = "sqlalchemy" }, - { name = "ujson" }, { name = "uvicorn" }, { name = "uvloop", marker = "sys_platform != 'win32'" }, { name = "winloop", marker = "sys_platform == 'win32'" }, @@ -1150,7 +1149,6 @@ dev = [ { name = "types-chardet" }, { name = "types-python-dateutil" }, { name = "types-pyyaml" }, - { name = "types-ujson" }, { name = "wheel" }, ] @@ -1173,7 +1171,6 @@ requires-dist = [ { name = "pyyaml", specifier = "==6.0.3" }, { name = "retrying", specifier = "==1.4.2" }, { name = "sqlalchemy", specifier = "==2.0.51" }, - { name = "ujson", specifier = "==5.13.0" }, { name = "uvicorn", specifier = "==0.52.1" }, { name = "uvloop", marker = "sys_platform != 'win32'", specifier = "==0.22.1" }, { name = "winloop", marker = "sys_platform == 'win32'", specifier = "==0.6.3" }, @@ -1192,7 +1189,6 @@ dev = [ { name = "types-chardet", specifier = "==5.0.4.6" }, { name = "types-python-dateutil", specifier = "==2.9.0.20260518" }, { name = "types-pyyaml", specifier = "==6.0.12.20250915" }, - { name = "types-ujson", specifier = "==5.10.0.20250822" }, { name = "wheel", specifier = "==0.47.0" }, ] @@ -1257,15 +1253,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, ] -[[package]] -name = "types-ujson" -version = "5.10.0.20250822" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/bd/d372d44534f84864a96c19a7059d9b4d29db8541828b8b9dc3040f7a46d0/types_ujson-5.10.0.20250822.tar.gz", hash = "sha256:0a795558e1f78532373cf3f03f35b1f08bc60d52d924187b97995ee3597ba006", size = 8437, upload-time = "2025-08-22T03:02:19.433Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/f2/d812543c350674d8b3f6e17c8922248ee3bb752c2a76f64beb8c538b40cf/types_ujson-5.10.0.20250822-py3-none-any.whl", hash = "sha256:3e9e73a6dc62ccc03449d9ac2c580cd1b7a8e4873220db498f7dd056754be080", size = 7657, upload-time = "2025-08-22T03:02:18.699Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" @@ -1287,40 +1274,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] -[[package]] -name = "ujson" -version = "5.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/89/7a/c8bb37c8f6f3623d60c33d15d18cd6d6655d0f9c3eb31a9969f76361b199/ujson-5.13.0.tar.gz", hash = "sha256:d62e3d7625384c08082abad81a077af587fdef2761bb14c3822f4234b8d07d75", size = 7166784, upload-time = "2026-06-14T22:36:50.209Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/9a/b5139d696f5328f3cab70b9ec046f15e3f49497a4de6280974640602f539/ujson-5.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:cc9dfd41fed397ab03bb9d9fe1cbd83301211c772a17536033ce7d68877ac82b", size = 56897, upload-time = "2026-06-14T22:35:57.974Z" }, - { url = "https://files.pythonhosted.org/packages/53/55/477183aeddfdf0f88ae039ffee0ed866cfb993da0c0c9aa915807554aef8/ujson-5.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ca7ef2fa6c408a7c0f558e4d33d93b32ddc35ed6d3cfc505747931a64b7465d5", size = 54451, upload-time = "2026-06-14T22:35:58.932Z" }, - { url = "https://files.pythonhosted.org/packages/ea/63/55e5f23e156b4c8bca095d828b4cd3180c0b42aa3501ef88836d79606fea/ujson-5.13.0-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a554b2e5bee85030369514cef8b0b913cebe1a4c2c0c13541966d50bcba22b1a", size = 60053, upload-time = "2026-06-14T22:35:59.969Z" }, - { url = "https://files.pythonhosted.org/packages/26/b6/08c6cf5548bd6f4bb557c9fa7e8edf87324bb04c17249d1966028d61dde0/ujson-5.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ea939ff629ab03ae970d03eca6d1febd8ed55ba38ca44aec64ce997537cd3fa0", size = 53481, upload-time = "2026-06-14T22:36:01.007Z" }, - { url = "https://files.pythonhosted.org/packages/6e/b3/0ac9a03551467784067f505df1bb875c639ba32f1da79ce467ab15911ada/ujson-5.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b98bf2faa5e37ecfe752226ea08290031e375a0c43d425a0b955fb3e702a2a71", size = 55058, upload-time = "2026-06-14T22:36:02.297Z" }, - { url = "https://files.pythonhosted.org/packages/ba/be/ec91029aec067174473d022fa0f6c3c1431a173f888d7599739f05c668eb/ujson-5.13.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a4b92344b16e414aeb609e57f62c466500e53c94f1698f5b149dc0b7223ec3e", size = 58225, upload-time = "2026-06-14T22:36:03.321Z" }, - { url = "https://files.pythonhosted.org/packages/29/33/a948f329252ece3f9c93d177243de6e677927ebc6ac44256742dbbef3c39/ujson-5.13.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df805aad707507a1fa165fb716218ca3a89f142125dc4b23c9fcc08fa402d97", size = 57930, upload-time = "2026-06-14T22:36:04.385Z" }, - { url = "https://files.pythonhosted.org/packages/f1/0c/c33655218b8e0a8adbf066de0b999cae5c324061f3eaa4dda17423145d9e/ujson-5.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7576bdbef327c3528f011002a2d74486f6fe4e33289bdb7a042b7f1a6e9d8285", size = 1037728, upload-time = "2026-06-14T22:36:05.467Z" }, - { url = "https://files.pythonhosted.org/packages/2c/bd/d286947525ea7ce3f2d8dc55c15b9ffbe425bc455c96af7b8f8a402599a9/ujson-5.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6eee5d7cce3f32a468905f9ff61807a60287a90258d849460f6fa826e810870d", size = 1197146, upload-time = "2026-06-14T22:36:06.839Z" }, - { url = "https://files.pythonhosted.org/packages/3c/3c/9eb916377050b0785f048a34588c1c390ddd41ae00b78db68ee1ad022356/ujson-5.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:144e9d8a454cfa727e0f755e1863738ed68068583bda5463052cb446835bd56c", size = 1090223, upload-time = "2026-06-14T22:36:08.329Z" }, - { url = "https://files.pythonhosted.org/packages/dd/5f/242fd97a2628b842d4bfaa9b18e1f68187f934d67503291ebbaab1254637/ujson-5.13.0-cp314-cp314-win32.whl", hash = "sha256:576f35c35b918d67d41b933878062ec0a5c9f4d1e9e14e04aeef35384963feae", size = 41223, upload-time = "2026-06-14T22:36:09.644Z" }, - { url = "https://files.pythonhosted.org/packages/23/f3/7f2bd9ca0c507142d0c22347b3d6f8803be1d8851c31707e57f5923fdbea/ujson-5.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:d5e206e9f849ead27e51ef8da44e52b38da7c6dbd929a7340ab44533edcda8d7", size = 42265, upload-time = "2026-06-14T22:36:11.043Z" }, - { url = "https://files.pythonhosted.org/packages/b0/29/3e9a8fba321c031315f6d263510969a5d01f41fc471b5be107e413c1b2f8/ujson-5.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:dc470179775468f9a007d3a6a2734624248c94bf47c6645e808c7e50a5070d1a", size = 40205, upload-time = "2026-06-14T22:36:12.286Z" }, - { url = "https://files.pythonhosted.org/packages/12/e9/1c543837c6a3c6672361882a0fa269bd02daf9cc4c0ca88a9dccd9df98d9/ujson-5.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:69b4e36bb7d5f413ba8c00c8006b2ec627cc5ace97301462f6aadb66ec9d2979", size = 57402, upload-time = "2026-06-14T22:36:13.238Z" }, - { url = "https://files.pythonhosted.org/packages/99/e4/39862f0f7174ff07cfd1e2d0c9065ded34aeebdb7db8daf2f0e5bf89b46f/ujson-5.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8b644d50f66de5490c1823c7176618cead5e8e8a88cba9f40a6308ca52e79267", size = 54973, upload-time = "2026-06-14T22:36:14.432Z" }, - { url = "https://files.pythonhosted.org/packages/02/66/f53d3b32c3f177f846ca6b624e832f29000d8a213a2d8768e254bd470ced/ujson-5.13.0-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:15107aaa4f559d55201165ec32abb35c283a861be1fa67229578cb7d93fcd93a", size = 60683, upload-time = "2026-06-14T22:36:15.806Z" }, - { url = "https://files.pythonhosted.org/packages/e6/d4/dddc4646d2633c85c938c2ded7d5a9711cdad5be1e13b31b7dad76f61c83/ujson-5.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6e343c5f0c058523f1edbf6ae4eceb4e0d934205a53bbdd8d9a945c83324662a", size = 54167, upload-time = "2026-06-14T22:36:16.952Z" }, - { url = "https://files.pythonhosted.org/packages/5f/c0/d8608c3f4d3f05e6441364b63fde1d279700135c1a6577a773662c07fbcc/ujson-5.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02200035bc80e830f076ffc1b329a94c295aee6d9de8c9043647cb9a7bd4f76f", size = 55568, upload-time = "2026-06-14T22:36:17.975Z" }, - { url = "https://files.pythonhosted.org/packages/22/8e/dd12b735aaba0806c3d70c18184d50e1f9712e0757c7c0a4f376450cfe28/ujson-5.13.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7f19b81b73ff28f5c5022ee794f94122bfcda07a76423078e349465d71223a1", size = 59086, upload-time = "2026-06-14T22:36:19.071Z" }, - { url = "https://files.pythonhosted.org/packages/48/43/ad41e8752d5ec3a590a5e7b426a54e36b7aab911d9b5a4f7384dc62507ab/ujson-5.13.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82e1393e6dbe3c95fdfc95c6c528890e191351a1f024ef51126cf1f22543af52", size = 58667, upload-time = "2026-06-14T22:36:20.171Z" }, - { url = "https://files.pythonhosted.org/packages/e1/8e/b44a6afb77b94118655c029081b7932d64bb4c5b1c8ba2b7f5808b5d0bc2/ujson-5.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:38afcf994b28ed85ea2420e2a8d79a37d0a77348b3daf53850c16edda66f942d", size = 1038553, upload-time = "2026-06-14T22:36:21.245Z" }, - { url = "https://files.pythonhosted.org/packages/7e/93/fab1d786174c8780eb3e386c73f1925a435e97fbf77c957fea4fca83994d/ujson-5.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1bdf2518971586f2b413156c49d9dd8b56cc990a8647081e1bd00af60564d469", size = 1197938, upload-time = "2026-06-14T22:36:22.585Z" }, - { url = "https://files.pythonhosted.org/packages/f3/bc/2f073bb708f9d128f5d1cb39063a5f6421b1ce94c61be8661c55a189f407/ujson-5.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:751ad01042472f1c7c02f5c597c7aee79834e82a6cc384ca302173bbc8e8deb8", size = 1090938, upload-time = "2026-06-14T22:36:23.947Z" }, - { url = "https://files.pythonhosted.org/packages/c4/67/cdaa50bba29d7dc9eb19212755b09bb96f56596e75957c3717c6b85454de/ujson-5.13.0-cp314-cp314t-win32.whl", hash = "sha256:74f3dd61aeb01b7b2a6754e400224e819279041b3867935a55ccf57fb86a43b2", size = 41802, upload-time = "2026-06-14T22:36:25.418Z" }, - { url = "https://files.pythonhosted.org/packages/bd/66/a6e669e90083febdf6c0600d3807f6017fd4d3962d5bd6ddc605c73a06e5/ujson-5.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5c31317d5e4504dae8f98795358b6082fc0ef96e7394806db0a76a4a8717f446", size = 42790, upload-time = "2026-06-14T22:36:26.614Z" }, - { url = "https://files.pythonhosted.org/packages/3d/5f/fcc6c6a9d711fd8b020ca8ff65148212f0a712c809d173cd949e58de68c6/ujson-5.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aefd3c9c95f9b62348956396ff7b31818476f8f54dc4a4e64cbd4f0491db6fca", size = 40708, upload-time = "2026-06-14T22:36:27.721Z" }, -] - [[package]] name = "urllib3" version = "2.7.0"