diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index f30945a461..13b65e118e 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -21,10 +21,10 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.13"] + python-version: ["3.9", "3.14"] test_name: ["Everything else", "Inference only", "Xet only"] include: - - python-version: "3.13" # LFS not ran on 3.9 + - python-version: "3.14" # LFS not ran on 3.9 test_name: "lfs" - python-version: "3.9" test_name: "fastai" @@ -34,6 +34,8 @@ jobs: test_name: "Python 3.9, torch_1.11" - python-version: "3.12" # test torch latest on python 3.12 only. test_name: "torch_latest" + - python-version: "3.13" # gradio not supported on 3.14 -> test it on 3.13 + test_name: "gradio" steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -69,6 +71,10 @@ jobs: uv pip install "huggingface_hub[fastai] @ ." ;; + gradio) + uv pip install "huggingface_hub[gradio] @ ." + ;; + torch_latest) uv pip install "huggingface_hub[torch] @ ." uv pip install --upgrade torch @@ -117,6 +123,10 @@ jobs: eval "$PYTEST ../tests/test_fastai*" ;; + gradio) + eval "$PYTEST ../tests/test_webhooks_server.py" + ;; + "Python 3.9, torch_1.11" | torch_latest) eval "$PYTEST ../tests/test_hub_mixin*" eval "$PYTEST ../tests/test_serialization.py" @@ -148,7 +158,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.11"] + python-version: ["3.9", "3.14"] test_name: ["Everything else", "Xet only"] steps: diff --git a/setup.py b/setup.py index 3ce2091365..d4edfff484 100644 --- a/setup.py +++ b/setup.py @@ -65,7 +65,6 @@ def get_version() -> str: "urllib3<2.0", # VCR.py broken with urllib3 2.0 (see https://urllib3.readthedocs.io/en/stable/v2-migration-guide.html) "soundfile", "Pillow", - "requests", # for gradio "numpy", # for embeddings "fastapi", # To build the documentation ] @@ -74,8 +73,10 @@ def get_version() -> str: if sys.version_info >= (3, 10): # We need gradio to test webhooks server # But gradio 5.0+ only supports python 3.10+ so we don't want to test earlier versions - extras["testing"].append("gradio>=5.0.0") - extras["testing"].append("requests") # see https://github.com/gradio-app/gradio/pull/11830 + extras["gradio"] = [ + "gradio>=5.0.0", + "requests", # see https://github.com/gradio-app/gradio/pull/11830 + ] # Typing extra dependencies list is duplicated in `.pre-commit-config.yaml` # Please make sure to update the list there when adding a new typing dependency. @@ -135,6 +136,7 @@ def get_version() -> str: "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Scientific/Engineering :: Artificial Intelligence", ], include_package_data=True, diff --git a/src/huggingface_hub/dataclasses.py b/src/huggingface_hub/dataclasses.py index 2fcec6b774..13f01d29d6 100644 --- a/src/huggingface_hub/dataclasses.py +++ b/src/huggingface_hub/dataclasses.py @@ -305,12 +305,7 @@ def validate_typed_dict(schema: type[TypedDictType], data: dict) -> None: @lru_cache def _build_strict_cls_from_typed_dict(schema: type[TypedDictType]) -> Type: # Extract type hints from the TypedDict class - type_hints = { - # We do not use `get_type_hints` here to avoid evaluating ForwardRefs (which might fail). - # ForwardRefs are not validated by @strict anyway. - name: value if value is not None else type(None) - for name, value in schema.__dict__.get("__annotations__", {}).items() - } + type_hints = _get_typed_dict_annotations(schema) # If the TypedDict is not total, wrap fields as NotRequired (unless explicitly Required or NotRequired) if not getattr(schema, "__total__", True): @@ -338,6 +333,22 @@ def _build_strict_cls_from_typed_dict(schema: type[TypedDictType]) -> Type: return strict(make_dataclass(schema.__name__, fields)) +def _get_typed_dict_annotations(schema: type[TypedDictType]) -> dict[str, Any]: + """Extract type annotations from a TypedDict class.""" + try: + # Available in Python 3.14+ + import annotationlib + + return annotationlib.get_annotations(schema) + except ImportError: + return { + # We do not use `get_type_hints` here to avoid evaluating ForwardRefs (which might fail). + # ForwardRefs are not validated by @strict anyway. + name: value if value is not None else type(None) + for name, value in schema.__dict__.get("__annotations__", {}).items() + } + + def validated_field( validator: Union[list[Validator_T], Validator_T], default: Union[Any, _MISSING_TYPE] = MISSING, diff --git a/tests/test_utils_http.py b/tests/test_utils_http.py index 1fc04be802..99bbd0f184 100644 --- a/tests/test_utils_http.py +++ b/tests/test_utils_http.py @@ -1,9 +1,7 @@ -import os import threading import time import unittest from http.server import BaseHTTPRequestHandler, HTTPServer -from multiprocessing import Process, Queue from typing import Generator, Optional from unittest.mock import Mock, call, patch from urllib.parse import urlparse @@ -222,24 +220,6 @@ def _get_session_in_thread(index: int) -> None: for j in range(N): self.assertIs(clients[i], clients[j]) - @unittest.skipIf(os.name == "nt", "Works differently on Windows.") - def test_get_session_in_forked_process(self): - # Get main process client - main_client = get_session() - - def _child_target(): - # Put `repr(client)` in queue because putting the `Client` object directly would duplicate it. - # Repr looks like this: "" - process_queue.put(repr(get_session())) - - # Fork a new process and get client in it - process_queue = Queue() - Process(target=_child_target).start() - child_client = process_queue.get() - - # Check clients are the same instance - self.assertEqual(repr(main_client), child_client) - class OfflineModeSessionTest(unittest.TestCase): def tearDown(self) -> None: