From c055e17b41ebeeb5e3f145eacd7024e1a722b54a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Thu, 28 Nov 2024 22:31:10 +0100 Subject: [PATCH 001/161] Add retry strategy to clients. Make submissions and test cases iterable. Increase retry frequency for default implicit Sulu client. --- src/judge0/__init__.py | 4 +-- src/judge0/api.py | 27 +++++++++------ src/judge0/base_types.py | 18 +++------- src/judge0/clients.py | 72 ++++++++++++++++++++++++---------------- src/judge0/filesystem.py | 7 ++-- src/judge0/retry.py | 12 +++---- src/judge0/submission.py | 8 ++--- 7 files changed, 77 insertions(+), 71 deletions(-) diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index 8f41ec09..18a70133 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -98,9 +98,9 @@ def _get_implicit_client(flavor: Flavor) -> Client: # the preview Sulu client based on the flavor. if client is None: if flavor == Flavor.CE: - client = SuluJudge0CE() + client = SuluJudge0CE(retry_strategy=RegularPeriodRetry(0.5)) else: - client = SuluJudge0ExtraCE() + client = SuluJudge0ExtraCE(retry_strategy=RegularPeriodRetry(0.5)) if flavor == Flavor.CE: JUDGE0_IMPLICIT_CE_CLIENT = client diff --git a/src/judge0/api.py b/src/judge0/api.py index b5fd64de..43105cb1 100644 --- a/src/judge0/api.py +++ b/src/judge0/api.py @@ -1,10 +1,10 @@ -from typing import Iterable, Optional, Union +from typing import Optional, Union -from .base_types import Flavor, TestCase, TestCases +from .base_types import Flavor, Iterable, TestCase, TestCases from .clients import Client from .common import batched -from .retry import RegularPeriodRetry, RetryMechanism +from .retry import RegularPeriodRetry, RetryStrategy from .submission import Submission, Submissions @@ -31,7 +31,7 @@ def _resolve_client( if isinstance(client, Flavor): return get_client(client) - if client is None and isinstance(submissions, list) and len(submissions) == 0: + if client is None and isinstance(submissions, Iterable) and len(submissions) == 0: raise ValueError("Client cannot be determined from empty submissions.") # client is None and we have to determine a flavor of the client from the @@ -57,6 +57,7 @@ def _resolve_client( def create_submissions( + *, client: Optional[Client] = None, submissions: Optional[Union[Submission, Submissions]] = None, ) -> Union[Submission, Submissions]: @@ -81,7 +82,7 @@ def get_submissions( *, client: Optional[Client] = None, submissions: Optional[Union[Submission, Submissions]] = None, - fields: Union[str, Iterable[str], None] = None, + fields: Optional[Union[str, Iterable[str]]] = None, ) -> Union[Submission, Submissions]: client = _resolve_client(client=client, submissions=submissions) @@ -108,12 +109,15 @@ def wait( *, client: Optional[Client] = None, submissions: Optional[Union[Submission, Submissions]] = None, - retry_mechanism: Optional[RetryMechanism] = None, + retry_strategy: Optional[RetryStrategy] = None, ) -> Union[Submission, Submissions]: client = _resolve_client(client, submissions) - if retry_mechanism is None: - retry_mechanism = RegularPeriodRetry() + if retry_strategy is None: + if client.retry_strategy is None: + retry_strategy = RegularPeriodRetry() + else: + retry_strategy = client.retry_strategy if isinstance(submissions, Submission): submissions_to_check = { @@ -124,7 +128,7 @@ def wait( submission.token: submission for submission in submissions } - while len(submissions_to_check) > 0 and not retry_mechanism.is_done(): + while len(submissions_to_check) > 0 and not retry_strategy.is_done(): get_submissions(client=client, submissions=list(submissions_to_check.values())) for token in list(submissions_to_check): submission = submissions_to_check[token] @@ -135,8 +139,8 @@ def wait( if len(submissions_to_check) == 0: break - retry_mechanism.wait() - retry_mechanism.step() + retry_strategy.wait() + retry_strategy.step() return submissions @@ -204,6 +208,7 @@ def _execute( if submissions is None and source_code is None: raise ValueError("Neither source_code nor submissions argument are provided.") + # Internally, let's rely on Submission's dataclass. if source_code is not None: submissions = Submission(source_code=source_code, **kwargs) diff --git a/src/judge0/base_types.py b/src/judge0/base_types.py index b1d42107..e99ce199 100644 --- a/src/judge0/base_types.py +++ b/src/judge0/base_types.py @@ -1,19 +1,11 @@ from abc import ABC, abstractmethod from dataclasses import dataclass from enum import IntEnum -from typing import Optional, Union - - -TestCases = Union[ - list["TestCase"], - tuple["TestCase"], - list[dict], - tuple[dict], - list[list], - list[tuple], - tuple[list], - tuple[tuple], -] +from typing import Optional, Sequence, Union + +Iterable = Sequence + +TestCases = Iterable["TestCase"] @dataclass(frozen=True) diff --git a/src/judge0/clients.py b/src/judge0/clients.py index 0797e9a6..ada06cf5 100644 --- a/src/judge0/clients.py +++ b/src/judge0/clients.py @@ -1,26 +1,29 @@ -from typing import Iterable, Union +from typing import Optional, Union import requests -from .base_types import Config, Language, LanguageAlias +from .base_types import Config, Iterable, Language, LanguageAlias from .data import LANGUAGE_TO_LANGUAGE_ID +from .retry import RetryStrategy from .submission import Submission, Submissions class Client: - API_KEY_ENV = "JUDGE0_API_KEY" - DEFAULT_MAX_SUBMISSION_BATCH_SIZE = 20 - ENABLED_BATCHED_SUBMISSIONS = True - EFFECTIVE_SUBMISSION_BATCH_SIZE = ( - DEFAULT_MAX_SUBMISSION_BATCH_SIZE if ENABLED_BATCHED_SUBMISSIONS else 1 - ) + API_KEY_ENV = None - def __init__(self, endpoint, auth_headers) -> None: + def __init__( + self, + endpoint, + auth_headers, + *, + retry_strategy: Optional[RetryStrategy] = None, + ) -> None: self.endpoint = endpoint self.auth_headers = auth_headers + self.retry_strategy = retry_strategy try: - self.languages = [Language(**lang) for lang in self.get_languages()] + self.languages = tuple(Language(**lang) for lang in self.get_languages()) self.config = Config(**self.get_config_info()) except Exception as e: raise RuntimeError( @@ -113,7 +116,7 @@ def get_submission( self, submission: Submission, *, - fields: Union[str, Iterable[str], None] = None, + fields: Optional[Union[str, Iterable[str]]] = None, ) -> Submission: """Check the submission status.""" @@ -168,7 +171,7 @@ def get_submissions( self, submissions: Submissions, *, - fields: Union[str, Iterable[str], None] = None, + fields: Optional[Union[str, Iterable[str]]] = None, ) -> Submissions: params = { "base64_encoded": "true", @@ -201,7 +204,7 @@ def get_submissions( class ATD(Client): API_KEY_ENV = "JUDGE0_ATD_API_KEY" - def __init__(self, endpoint, host_header_value, api_key): + def __init__(self, endpoint, host_header_value, api_key, **kwargs): self.api_key = api_key super().__init__( endpoint, @@ -209,6 +212,7 @@ def __init__(self, endpoint, host_header_value, api_key): "x-apihub-host": host_header_value, "x-apihub-key": api_key, }, + **kwargs, ) def _update_endpoint_header(self, header_value): @@ -232,11 +236,12 @@ class ATDJudge0CE(ATD): DEFAULT_CREATE_SUBMISSIONS_ENDPOINT: str = "402b857c-1126-4450-bfd8-22e1f2cbff2f" DEFAULT_GET_SUBMISSIONS_ENDPOINT: str = "e42f2a26-5b02-472a-80c9-61c4bdae32ec" - def __init__(self, api_key): + def __init__(self, api_key, **kwargs): super().__init__( self.DEFAULT_ENDPOINT, self.DEFAULT_HOST, api_key, + **kwargs, ) def get_about(self) -> dict: @@ -267,7 +272,7 @@ def get_submission( self, submission: Submission, *, - fields: Union[str, Iterable[str], None] = None, + fields: Optional[Union[str, Iterable[str]]] = None, ) -> Submission: self._update_endpoint_header(self.DEFAULT_GET_SUBMISSION_ENDPOINT) return super().get_submission(submission, fields=fields) @@ -280,7 +285,7 @@ def get_submissions( self, submissions: Submissions, *, - fields: Union[str, Iterable[str], None] = None, + fields: Optional[Union[str, Iterable[str]]] = None, ) -> Submissions: self._update_endpoint_header(self.DEFAULT_GET_SUBMISSIONS_ENDPOINT) return super().get_submissions(submissions, fields=fields) @@ -303,11 +308,12 @@ class ATDJudge0ExtraCE(ATD): DEFAULT_CREATE_SUBMISSIONS_ENDPOINT: str = "c64df5d3-edfd-4b08-8687-561af2f80d2f" DEFAULT_GET_SUBMISSIONS_ENDPOINT: str = "5d173718-8e6a-4cf5-9d8c-db5e6386d037" - def __init__(self, api_key): + def __init__(self, api_key, **kwargs): super().__init__( self.DEFAULT_ENDPOINT, self.DEFAULT_HOST, api_key, + **kwargs, ) def get_about(self) -> dict: @@ -338,7 +344,7 @@ def get_submission( self, submission: Submission, *, - fields: Union[str, Iterable[str], None] = None, + fields: Optional[Union[str, Iterable[str]]] = None, ) -> Submission: self._update_endpoint_header(self.DEFAULT_GET_SUBMISSION_ENDPOINT) return super().get_submission(submission, fields=fields) @@ -351,7 +357,7 @@ def get_submissions( self, submissions: Submissions, *, - fields: Union[str, Iterable[str], None] = None, + fields: Optional[Union[str, Iterable[str]]] = None, ) -> Submissions: self._update_endpoint_header(self.DEFAULT_GET_SUBMISSIONS_ENDPOINT) return super().get_submissions(submissions, fields=fields) @@ -360,7 +366,7 @@ def get_submissions( class Rapid(Client): API_KEY_ENV = "JUDGE0_RAPID_API_KEY" - def __init__(self, endpoint, host_header_value, api_key): + def __init__(self, endpoint, host_header_value, api_key, **kwargs): self.api_key = api_key super().__init__( endpoint, @@ -368,6 +374,7 @@ def __init__(self, endpoint, host_header_value, api_key): "x-rapidapi-host": host_header_value, "x-rapidapi-key": api_key, }, + **kwargs, ) @@ -376,11 +383,12 @@ class RapidJudge0CE(Rapid): DEFAULT_HOST: str = "judge0-ce.p.rapidapi.com" HOME_URL: str = "https://rapidapi.com/judge0-official/api/judge0-ce" - def __init__(self, api_key): + def __init__(self, api_key, **kwargs): super().__init__( self.DEFAULT_ENDPOINT, self.DEFAULT_HOST, api_key, + **kwargs, ) @@ -389,22 +397,24 @@ class RapidJudge0ExtraCE(Rapid): DEFAULT_HOST: str = "judge0-extra-ce.p.rapidapi.com" HOME_URL: str = "https://rapidapi.com/judge0-official/api/judge0-extra-ce" - def __init__(self, api_key): + def __init__(self, api_key, **kwargs): super().__init__( self.DEFAULT_ENDPOINT, self.DEFAULT_HOST, api_key, + **kwargs, ) class Sulu(Client): API_KEY_ENV = "JUDGE0_SULU_API_KEY" - def __init__(self, endpoint, api_key=None): + def __init__(self, endpoint, api_key=None, **kwargs): self.api_key = api_key super().__init__( endpoint, {"Authorization": f"Bearer {api_key}"} if api_key else None, + **kwargs, ) @@ -412,17 +422,21 @@ class SuluJudge0CE(Sulu): DEFAULT_ENDPOINT: str = "https://judge0-ce.p.sulu.sh" HOME_URL: str = "https://sparkhub.sulu.sh/apis/judge0/judge0-ce/readme" - def __init__(self, api_key=None): - super().__init__(self.DEFAULT_ENDPOINT, api_key) + def __init__(self, api_key=None, **kwargs): + super().__init__( + self.DEFAULT_ENDPOINT, + api_key, + **kwargs, + ) class SuluJudge0ExtraCE(Sulu): DEFAULT_ENDPOINT: str = "https://judge0-extra-ce.p.sulu.sh" HOME_URL: str = "https://sparkhub.sulu.sh/apis/judge0/judge0-extra-ce/readme" - def __init__(self, api_key=None): - super().__init__(self.DEFAULT_ENDPOINT, api_key) + def __init__(self, api_key=None, **kwargs): + super().__init__(self.DEFAULT_ENDPOINT, api_key, **kwargs) -CE = [RapidJudge0CE, SuluJudge0CE, ATDJudge0CE] -EXTRA_CE = [RapidJudge0ExtraCE, SuluJudge0ExtraCE, ATDJudge0ExtraCE] +CE = (RapidJudge0CE, SuluJudge0CE, ATDJudge0CE) +EXTRA_CE = (RapidJudge0ExtraCE, SuluJudge0ExtraCE, ATDJudge0ExtraCE) diff --git a/src/judge0/filesystem.py b/src/judge0/filesystem.py index 590795c5..bbdb11b4 100644 --- a/src/judge0/filesystem.py +++ b/src/judge0/filesystem.py @@ -3,10 +3,9 @@ import zipfile from base64 import b64decode, b64encode -from collections import abc -from typing import Iterable, Optional, Union +from typing import Optional, Union -from .base_types import Encodeable +from .base_types import Encodeable, Iterable class File: @@ -42,7 +41,7 @@ def __init__( for file_name in zip_file.namelist(): with zip_file.open(file_name) as fp: self.files.append(File(file_name, fp.read())) - elif isinstance(content, abc.Iterable): + elif isinstance(content, Iterable): self.files = list(content) elif isinstance(content, File): self.files = [content] diff --git a/src/judge0/retry.py b/src/judge0/retry.py index 33acc52a..20b42ef7 100644 --- a/src/judge0/retry.py +++ b/src/judge0/retry.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod -class RetryMechanism(ABC): +class RetryStrategy(ABC): @abstractmethod def is_done(self) -> bool: pass @@ -11,12 +11,11 @@ def is_done(self) -> bool: def wait(self) -> None: pass - @abstractmethod def step(self) -> None: pass -class MaxRetries(RetryMechanism): +class MaxRetries(RetryStrategy): """Check for submissions status every 100 ms and retry a maximum of `max_retries` times.""" @@ -34,7 +33,7 @@ def is_done(self) -> bool: return self.n_retries >= self.max_retries -class MaxWaitTime(RetryMechanism): +class MaxWaitTime(RetryStrategy): """Check for submissions status every 100 ms and wait for all submissions a maximum of `max_wait_time` (seconds).""" @@ -52,15 +51,12 @@ def is_done(self): return self.total_wait_time >= self.max_wait_time_sec -class RegularPeriodRetry(RetryMechanism): +class RegularPeriodRetry(RetryStrategy): """Check for submissions status periodically for indefinite amount of time.""" def __init__(self, wait_time_sec: float = 0.1): self.wait_time_sec = wait_time_sec - def step(self): - pass - def wait(self): time.sleep(self.wait_time_sec) diff --git a/src/judge0/submission.py b/src/judge0/submission.py index 2ffa1789..7b3d937d 100644 --- a/src/judge0/submission.py +++ b/src/judge0/submission.py @@ -1,10 +1,10 @@ import copy from datetime import datetime -from typing import Optional, Union +from typing import Any, Optional, Union from judge0.filesystem import Filesystem -from .base_types import LanguageAlias, Status +from .base_types import Iterable, LanguageAlias, Status from .common import decode, encode ENCODED_REQUEST_FIELDS = { @@ -63,7 +63,7 @@ "wall_time_limit", } -Submissions = Union[list["Submission"], tuple["Submission"]] +Submissions = Iterable["Submission"] class Submission: @@ -138,7 +138,7 @@ def __init__( self.memory = None self.post_execution_filesystem = None - def set_attributes(self, attributes): + def set_attributes(self, attributes: dict[str, Any]) -> None: for attr, value in attributes.items(): if attr in SKIP_FIELDS: continue From 25b07403a335316fcd88849cb7fe543e91dd063e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 29 Nov 2024 18:01:00 +0100 Subject: [PATCH 002/161] Initial commit of a handling preview client's 429 Too Many Requests error. --- src/judge0/__init__.py | 2 ++ src/judge0/clients.py | 13 +++++++++++- src/judge0/utils.py | 47 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/judge0/utils.py diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index 18a70133..5ccf40bb 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -113,6 +113,8 @@ def _get_implicit_client(flavor: Flavor) -> Client: CE = Flavor.CE EXTRA_CE = Flavor.EXTRA_CE +# TODO: Let's use getattr and setattr for this language ALIASES and raise an +# exception if a value already exists. PYTHON = LanguageAlias.PYTHON CPP = LanguageAlias.CPP JAVA = LanguageAlias.JAVA diff --git a/src/judge0/clients.py b/src/judge0/clients.py index ada06cf5..a9a63bb0 100644 --- a/src/judge0/clients.py +++ b/src/judge0/clients.py @@ -6,6 +6,7 @@ from .data import LANGUAGE_TO_LANGUAGE_ID from .retry import RetryStrategy from .submission import Submission, Submissions +from .utils import handle_too_many_requests_error_for_preview_client class Client: @@ -22,6 +23,7 @@ def __init__( self.auth_headers = auth_headers self.retry_strategy = retry_strategy + # TODO: Should be handled differently. try: self.languages = tuple(Language(**lang) for lang in self.get_languages()) self.config = Config(**self.get_config_info()) @@ -30,6 +32,7 @@ def __init__( f"Authentication failed. Visit {self.HOME_URL} to get or review your authentication credentials." ) from e + @handle_too_many_requests_error_for_preview_client def get_about(self) -> dict: r = requests.get( f"{self.endpoint}/about", @@ -38,6 +41,7 @@ def get_about(self) -> dict: r.raise_for_status() return r.json() + @handle_too_many_requests_error_for_preview_client def get_config_info(self) -> dict: r = requests.get( f"{self.endpoint}/config_info", @@ -46,18 +50,21 @@ def get_config_info(self) -> dict: r.raise_for_status() return r.json() + @handle_too_many_requests_error_for_preview_client def get_language(self, language_id) -> dict: request_url = f"{self.endpoint}/languages/{language_id}" r = requests.get(request_url, headers=self.auth_headers) r.raise_for_status() return r.json() + @handle_too_many_requests_error_for_preview_client def get_languages(self) -> list[dict]: request_url = f"{self.endpoint}/languages" r = requests.get(request_url, headers=self.auth_headers) r.raise_for_status() return r.json() + @handle_too_many_requests_error_for_preview_client def get_statuses(self) -> list[dict]: r = requests.get( f"{self.endpoint}/statuses", @@ -74,7 +81,7 @@ def version(self): return self._version def get_language_id(self, language: Union[LanguageAlias, int]) -> int: - """Get language id for the corresponding language alias for the client.""" + """Get language id corresponding to the language alias for the client.""" if isinstance(language, LanguageAlias): supported_language_ids = LANGUAGE_TO_LANGUAGE_ID[self.version] language = supported_language_ids.get(language, -1) @@ -85,6 +92,7 @@ def is_language_supported(self, language: Union[LanguageAlias, int]) -> bool: language_id = self.get_language_id(language) return any(language_id == lang.id for lang in self.languages) + @handle_too_many_requests_error_for_preview_client def create_submission(self, submission: Submission) -> Submission: # Check if the client supports the language specified in the submission. if not self.is_language_supported(language=submission.language): @@ -112,6 +120,7 @@ def create_submission(self, submission: Submission) -> Submission: return submission + @handle_too_many_requests_error_for_preview_client def get_submission( self, submission: Submission, @@ -143,6 +152,7 @@ def get_submission( return submission + @handle_too_many_requests_error_for_preview_client def create_submissions(self, submissions: Submissions) -> Submissions: # Check if all submissions contain supported language. for submission in submissions: @@ -167,6 +177,7 @@ def create_submissions(self, submissions: Submissions) -> Submissions: return submissions + @handle_too_many_requests_error_for_preview_client def get_submissions( self, submissions: Submissions, diff --git a/src/judge0/utils.py b/src/judge0/utils.py new file mode 100644 index 00000000..184c3681 --- /dev/null +++ b/src/judge0/utils.py @@ -0,0 +1,47 @@ +"""Module containing different utility functions for Judge0 Python SDK.""" + +from functools import wraps +from http import HTTPStatus + +from requests import HTTPError + + +def is_http_too_many_requests_error(exception: Exception) -> bool: + return ( + isinstance(exception, HTTPError) + and exception.response is not None + and exception.response.status_code == HTTPStatus.TOO_MANY_REQUESTS + ) + + +def handle_too_many_requests_error_for_preview_client(func): + @wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except HTTPError as err: + if is_http_too_many_requests_error(exception=err): + # If the raised exception is inside the one of the Sulu clients + # let's check if we are dealing with the implicit client. + if args: + instance = args[0] + class_name = instance.__class__.__name__ + # Check if we are using a preview version of the client. + if ( + class_name in ("SuluJudge0CE", "SuluJudge0ExtraCE") + and instance.api_key is None + ): + raise RuntimeError( + "You are using a preview version of the Sulu " + "clients and you've hit a rate limit on the preview " + f"clients. Visit {instance.HOME_URL} to get or " + "review your authentication credentials." + ) from err + else: + raise err from None + else: + raise err from None + except Exception as err: + raise err from None + + return wrapper From 1343fb1396cdd3477b75ad9666f393d7ac814f72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 29 Nov 2024 20:54:45 +0100 Subject: [PATCH 003/161] Add pre-commit for checking docstrings. --- .pre-commit-config.yaml | 9 +++++++++ pyproject.toml | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e2a0428b..fdebef77 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,3 +6,12 @@ repos: additional_dependencies: - black == 24.8.0 - usort == 1.0.8.post1 + - repo: https://github.com/pycqa/flake8 + rev: 7.1.1 + hooks: + - id: flake8 + args: + - --docstring-convention=numpydoc + additional_dependencies: + - flake8-docstrings + - pydocstyle diff --git a/pyproject.toml b/pyproject.toml index aeef3515..53a98547 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,3 +38,8 @@ Issues = "https://github.com/judge0/judge0-python/issues" [project.optional-dependencies] test = ["pytest", "mkdocs"] + +[tool.flake8] +docstring-convention = "numpydoc" +extend-ignore = ["D205", "D400", "D105"] +max-line-length = 88 From 941b463ed0923dc16c9c81207ed108f4c65f27b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 29 Nov 2024 20:59:55 +0100 Subject: [PATCH 004/161] Add docstring and typing to Submission. Fix flake8 pre-commit and take into account pyproject.toml. --- .pre-commit-config.yaml | 3 +- Pipfile | 1 + pyproject.toml | 4 +- src/judge0/submission.py | 137 ++++++++++++++++++++++++++++++--------- 4 files changed, 109 insertions(+), 36 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fdebef77..8adce633 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,8 +10,7 @@ repos: rev: 7.1.1 hooks: - id: flake8 - args: - - --docstring-convention=numpydoc additional_dependencies: + - "flake8-pyproject" - flake8-docstrings - pydocstyle diff --git a/Pipfile b/Pipfile index f7f341b2..ec5d4e18 100644 --- a/Pipfile +++ b/Pipfile @@ -12,6 +12,7 @@ pre-commit = "==3.8.0" pytest = "==8.3.3" python-dotenv = "==1.0.1" pytest-cov = "6.0.0" +flake8-docstrings = "1.7.0" [requires] python_version = "3.9" diff --git a/pyproject.toml b/pyproject.toml index 53a98547..fb18e0a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,6 @@ Issues = "https://github.com/judge0/judge0-python/issues" test = ["pytest", "mkdocs"] [tool.flake8] -docstring-convention = "numpydoc" -extend-ignore = ["D205", "D400", "D105"] +docstring-convention = "numpy" +extend-ignore = ["D205", "D400", "D105", "D100", "F821"] max-line-length = 88 diff --git a/src/judge0/submission.py b/src/judge0/submission.py index 7b3d937d..55a7d3c6 100644 --- a/src/judge0/submission.py +++ b/src/judge0/submission.py @@ -69,6 +69,61 @@ class Submission: """ Stores a representation of a Submission to/from Judge0. + + Parameters + ---------- + source_code : str, optional + The source code to be executed. + language : LanguageAlias or int, optional + The programming language of the source code. Defaults to `LanguageAlias.PYTHON`. + additional_files : base64 encoded string, optional + Additional files that should be available alongside the source code. + Value of this string should represent the content of a .zip that + contains additional files. This attribute is required for multi-file + programs. + compiler_options : str, optional + Options for the compiler (i.e. compiler flags). + command_line_arguments : str, optional + Command line arguments for the program. + stdin : str, optional + Input to be fed via standard input during execution. + expected_output : str, optional + The expected output of the program. + cpu_time_limit : float, optional + Maximum CPU time allowed for execution, in seconds. Time in which the + OS assigns the processor to different tasks is not counted. Depends on + configuration. + cpu_extra_time : float, optional + Additional CPU time allowance in case of time extension. Depends on + configuration. + wall_time_limit : float, optional + Maximum wall clock time allowed for execution, in seconds. Depends on + configuration. + memory_limit : float, optional + Maximum memory allocation allowed for the process, in kilobytes. + Depends on configuration. + stack_limit : int, optional + Maximum stack size allowed, in kilobytes. Depends on configuration. + max_processes_and_or_threads : int, optional + Maximum number of processes and/or threads program can create. Depends + on configuration. + enable_per_process_and_thread_time_limit : bool, optional + If True, enforces time limits per process/thread. Depends on + configuration. + enable_per_process_and_thread_memory_limit : bool, optional + If True, enforces memory limits per process/thread. Depends on + configuration. + max_file_size : int, optional + Maximum file size allowed for output files, in kilobytes. Depends on + configuration. + redirect_stderr_to_stdout : bool, optional + If True, redirects standard error output to standard output. + enable_network : bool, optional + If True, enables network access during execution. + number_of_runs : int, optional + Number of times the code should be executed. + callback_url : str, optional + URL for a callback to report execution results or status. """ def __init__( @@ -76,24 +131,24 @@ def __init__( *, source_code: Optional[str] = None, language: Union[LanguageAlias, int] = LanguageAlias.PYTHON, - additional_files=None, - compiler_options=None, - command_line_arguments=None, - stdin=None, - expected_output=None, - cpu_time_limit=None, - cpu_extra_time=None, - wall_time_limit=None, - memory_limit=None, - stack_limit=None, - max_processes_and_or_threads=None, - enable_per_process_and_thread_time_limit=None, - enable_per_process_and_thread_memory_limit=None, - max_file_size=None, - redirect_stderr_to_stdout=None, - enable_network=None, - number_of_runs=None, - callback_url=None, + additional_files: Optional[str] = None, + compiler_options: Optional[str] = None, + command_line_arguments: Optional[str] = None, + stdin: Optional[str] = None, + expected_output: Optional[str] = None, + cpu_time_limit: Optional[float] = None, + cpu_extra_time: Optional[float] = None, + wall_time_limit: Optional[float] = None, + memory_limit: Optional[float] = None, + stack_limit: Optional[int] = None, + max_processes_and_or_threads: Optional[int] = None, + enable_per_process_and_thread_time_limit: Optional[bool] = None, + enable_per_process_and_thread_memory_limit: Optional[bool] = None, + max_file_size: Optional[int] = None, + redirect_stderr_to_stdout: Optional[bool] = None, + enable_network: Optional[bool] = None, + number_of_runs: Optional[int] = None, + callback_url: Optional[str] = None, ): self.source_code = source_code self.language = language @@ -123,22 +178,31 @@ def __init__( self.callback_url = callback_url # Post-execution submission attributes. - self.stdout = None - self.stderr = None - self.compile_output = None - self.message = None - self.exit_code = None - self.exit_signal = None - self.status = None - self.created_at = None - self.finished_at = None - self.token = "" - self.time = None - self.wall_time = None - self.memory = None - self.post_execution_filesystem = None + self.stdout: Optional[str] = None + self.stderr: Optional[str] = None + self.compile_output: Optional[str] = None + self.message: Optional[str] = None + self.exit_code: Optional[int] = None + self.exit_signal: Optional[int] = None + self.status: Optional[Status] = None + self.created_at: Optional[datetime] = None + self.finished_at: Optional[datetime] = None + self.token: str = "" + self.time: Optional[float] = None + self.wall_time: Optional[float] = None + self.memory: Optional[float] = None + self.post_execution_filesystem: Optional[Filesystem] = None def set_attributes(self, attributes: dict[str, Any]) -> None: + """Set Submissions attributes while taking into account different + attribute's types. + + Parameters + ---------- + attributes : dict + Key-value pairs of Submission attributes and the corresponding + value. + """ for attr, value in attributes.items(): if attr in SKIP_FIELDS: continue @@ -157,6 +221,9 @@ def set_attributes(self, attributes: dict[str, Any]) -> None: setattr(self, attr, value) def as_body(self, client: "Client") -> dict: + """Prepare Submission as a dictionary while taking into account + the `client`'s restrictions. + """ body = { "source_code": encode(self.source_code), "language_id": client.get_language_id(self.language), @@ -175,12 +242,18 @@ def as_body(self, client: "Client") -> dict: return body def is_done(self) -> bool: + """Check if submission is finished processing. + + Submission is considered finished if the submission status is not + IN_QUEUE and not PROCESSING. + """ if self.status is None: return False else: return self.status not in (Status.IN_QUEUE, Status.PROCESSING) def pre_execution_copy(self) -> "Submission": + """Create a deep copy of a submission.""" new_submission = Submission() for attr in REQUEST_FIELDS: setattr(new_submission, attr, copy.deepcopy(getattr(self, attr))) From ebaa986bceee6e85dcf6da2601a760f93ebd8e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 30 Nov 2024 15:30:26 +0100 Subject: [PATCH 005/161] Add docstring to some Client functions. --- pyproject.toml | 2 +- src/judge0/clients.py | 72 ++++++++++++++++++++++++++++++++++++++++--- src/judge0/errors.py | 9 ++++++ 3 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 src/judge0/errors.py diff --git a/pyproject.toml b/pyproject.toml index fb18e0a9..f8126c57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,5 +41,5 @@ test = ["pytest", "mkdocs"] [tool.flake8] docstring-convention = "numpy" -extend-ignore = ["D205", "D400", "D105", "D100", "F821"] +extend-ignore = ["D205", "D400", "D105", "D100", "D101", "D102", "F821"] max-line-length = 88 diff --git a/src/judge0/clients.py b/src/judge0/clients.py index a9a63bb0..13ccdc63 100644 --- a/src/judge0/clients.py +++ b/src/judge0/clients.py @@ -29,7 +29,8 @@ def __init__( self.config = Config(**self.get_config_info()) except Exception as e: raise RuntimeError( - f"Authentication failed. Visit {self.HOME_URL} to get or review your authentication credentials." + f"Authentication failed. Visit {self.HOME_URL} to get or " + "review your authentication credentials." ) from e @handle_too_many_requests_error_for_preview_client @@ -94,6 +95,20 @@ def is_language_supported(self, language: Union[LanguageAlias, int]) -> bool: @handle_too_many_requests_error_for_preview_client def create_submission(self, submission: Submission) -> Submission: + """Send submission for execution to a client. + + Directly send a submission to create_submission route for execution. + + Parameters + ---------- + submission : Submission + A submission to create. + + Returns + ------- + Submission + A submission with updated token attribute. + """ # Check if the client supports the language specified in the submission. if not self.is_language_supported(language=submission.language): raise RuntimeError( @@ -127,8 +142,21 @@ def get_submission( *, fields: Optional[Union[str, Iterable[str]]] = None, ) -> Submission: - """Check the submission status.""" + """Get submissions status. + + Directly send submission's token to get_submission route for status + check. By default, all submissions attributes (fields) are requested. + + Parameters + ---------- + submission : Submission + Submission to update. + Returns + ------- + Submission + A Submission with updated attributes. + """ params = { "base64_encoded": "true", } @@ -154,7 +182,21 @@ def get_submission( @handle_too_many_requests_error_for_preview_client def create_submissions(self, submissions: Submissions) -> Submissions: - # Check if all submissions contain supported language. + """Send submissions for execution to a client. + + Directly send submissions to create_submissions route for execution. + Cannot handle more submissions than the client supports. + + Parameters + ---------- + submissions : Submissions + A sequence of submissions to create. + + Returns + ------- + Submissions + A sequence of submissions with updated token attribute. + """ for submission in submissions: if not self.is_language_supported(language=submission.language): raise RuntimeError( @@ -162,6 +204,9 @@ def create_submissions(self, submissions: Submissions) -> Submissions: f"{submission.language}!" ) + # TODO: Maybe raise an exception if the number of submissions is bigger + # than the batch size a client supports? + submissions_body = [submission.as_body(self) for submission in submissions] resp = requests.post( @@ -184,6 +229,24 @@ def get_submissions( *, fields: Optional[Union[str, Iterable[str]]] = None, ) -> Submissions: + """Get submissions status. + + Directly send submissions' tokens to get_submissions route for status + check. By default, all submissions attributes (fields) are requested. + Cannot handle more submissions than the client supports. + + Parameters + ---------- + submissions : Submissions + Submissions to update. + + Returns + ------- + Submissions + A sequence of submissions with updated attributes. + """ + # TODO: Maybe raise an exception if the number of submissions is bigger + # than the batch size a client supports? params = { "base64_encoded": "true", } @@ -306,7 +369,8 @@ class ATDJudge0ExtraCE(ATD): DEFAULT_ENDPOINT: str = "https://judge0-extra-ce.proxy-production.allthingsdev.co" DEFAULT_HOST: str = "Judge0-Extra-CE.allthingsdev.co" HOME_URL: str = ( - "https://www.allthingsdev.co/apimarketplace/judge0-extra-ce/66b68838b7b7ad054eb70690" + "https://www.allthingsdev.co/apimarketplace/judge0-extra-ce/" + "66b68838b7b7ad054eb70690" ) DEFAULT_ABOUT_ENDPOINT: str = "1fd631a1-be6a-47d6-bf4c-987e357e3096" diff --git a/src/judge0/errors.py b/src/judge0/errors.py new file mode 100644 index 00000000..a1835a5d --- /dev/null +++ b/src/judge0/errors.py @@ -0,0 +1,9 @@ +"""Library specific errors.""" + + +class PreviewClientLimitError(RuntimeError): + """Limited usage of a preview client exceeded.""" + + +class ClientResolutionError(RuntimeError): + """Failed resolution of an unspecified client.""" From 4d5e24b91aba7c5a079bbea7288e2f2bf4d547dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 30 Nov 2024 15:42:36 +0100 Subject: [PATCH 006/161] Replace runtime errors with library specific ones. Add docstring to some functions. --- pyproject.toml | 2 +- src/judge0/api.py | 153 ++++++++++++++++++++++++++++++++++----- src/judge0/base_types.py | 10 +-- src/judge0/utils.py | 11 +-- 4 files changed, 143 insertions(+), 33 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f8126c57..e4c72e75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,5 +41,5 @@ test = ["pytest", "mkdocs"] [tool.flake8] docstring-convention = "numpy" -extend-ignore = ["D205", "D400", "D105", "D100", "D101", "D102", "F821"] +extend-ignore = ["D205", "D400", "D105", "D100", "D101", "D102", "D103", "F821"] max-line-length = 88 diff --git a/src/judge0/api.py b/src/judge0/api.py index 43105cb1..0d8504fd 100644 --- a/src/judge0/api.py +++ b/src/judge0/api.py @@ -1,9 +1,9 @@ from typing import Optional, Union -from .base_types import Flavor, Iterable, TestCase, TestCases +from .base_types import Flavor, Iterable, TestCase, TestCases, TestCaseType from .clients import Client from .common import batched - +from .errors import ClientResolutionError from .retry import RegularPeriodRetry, RetryStrategy from .submission import Submission, Submissions @@ -24,6 +24,13 @@ def _resolve_client( client: Optional[Union[Client, Flavor]] = None, submissions: Optional[Union[Submission, Submissions]] = None, ) -> Client: + """Resolve a client from flavor or submission(s) arguments. + + Raises + ------ + ClientResolutionError + Raised if client resolution fails. + """ # User explicitly passed a client. if isinstance(client, Client): return client @@ -49,7 +56,7 @@ def _resolve_client( ): return client - raise RuntimeError( + raise ClientResolutionError( "Failed to resolve the client from submissions argument. " "None of the implicit clients supports all languages from the submissions. " "Please explicitly provide the client argument." @@ -61,6 +68,21 @@ def create_submissions( client: Optional[Client] = None, submissions: Optional[Union[Submission, Submissions]] = None, ) -> Union[Submission, Submissions]: + """Create submissions to a client. + + Parameters + ---------- + client : Client, optional + A Client where submissions should be created. If None, will try to + be automatically resolved. + submissions: Submission, Submissions + A submission or submissions to create. + + Raises + ------ + ClientResolutionError + Raised if client resolution fails. + """ client = _resolve_client(client=client, submissions=submissions) if isinstance(submissions, Submission): @@ -84,6 +106,21 @@ def get_submissions( submissions: Optional[Union[Submission, Submissions]] = None, fields: Optional[Union[str, Iterable[str]]] = None, ) -> Union[Submission, Submissions]: + """Create submissions to a client. + + Parameters + ---------- + client : Client, optional + A Client where submissions should be created. If None, will try to + be automatically resolved. + submissions: Submission, Submissions + A submission or submissions to create. + + Raises + ------ + ClientResolutionError + Raised if client resolution fails. + """ client = _resolve_client(client=client, submissions=submissions) if isinstance(submissions, Submission): @@ -120,20 +157,23 @@ def wait( retry_strategy = client.retry_strategy if isinstance(submissions, Submission): - submissions_to_check = { - submission.token: submission for submission in [submissions] - } + submissions_list = [submissions] else: - submissions_to_check = { - submission.token: submission for submission in submissions - } + submissions_list = submissions + + submissions_to_check = { + submission.token: submission for submission in submissions_list + } while len(submissions_to_check) > 0 and not retry_strategy.is_done(): get_submissions(client=client, submissions=list(submissions_to_check.values())) - for token in list(submissions_to_check): - submission = submissions_to_check[token] - if submission.is_done(): - submissions_to_check.pop(token) + finished_submissions = [ + token + for token, submission in submissions_to_check.items() + if submission.is_done() + ] + for token in finished_submissions: + submissions_to_check.pop(token) # Don't wait if there is no submissions to check for anymore. if len(submissions_to_check) == 0: @@ -147,12 +187,12 @@ def wait( def create_submissions_from_test_cases( submissions: Union[Submission, Submissions], - test_cases: Optional[Union[TestCase, TestCases]] = None, + test_cases: Optional[Union[TestCaseType, TestCases]] = None, ): - """Utility function for creating submissions from the (submission, test_case) pairs. + """Create submissions from the (submission, test_case) pairs. - The following table contains the return type based on the types of `submissions` - and `test_cases` arguments: + The following table contains the return type based on the types of + `submissions` and `test_cases` arguments: | submissions | test_cases | returns | |:------------|:-----------|:------------| @@ -196,10 +236,11 @@ def _execute( client: Optional[Union[Client, Flavor]] = None, submissions: Optional[Union[Submission, Submissions]] = None, source_code: Optional[str] = None, - test_cases: Optional[Union[TestCase, TestCases]] = None, + test_cases: Optional[Union[TestCaseType, TestCases]] = None, wait_for_result: bool = False, **kwargs, ) -> Union[Submission, Submissions]: + if submissions is not None and source_code is not None: raise ValueError( "Both submissions and source_code arguments are provided. " @@ -227,9 +268,45 @@ def async_execute( client: Optional[Union[Client, Flavor]] = None, submissions: Optional[Union[Submission, Submissions]] = None, source_code: Optional[str] = None, - test_cases: Optional[Union[TestCase, TestCases]] = None, + test_cases: Optional[Union[TestCaseType, TestCases]] = None, **kwargs, ) -> Union[Submission, Submissions]: + """Create submission(s). + + Parameters + ---------- + client : Client or Flavor, optional + A client where submissions should be created. If None, will try to be + resolved. + submissions : Submission or Submissions, optional + Submission or submissions for execution. + source_code: str, optional + A source code of a program. + test_cases: TestCaseType or TestCases, optional + A single test or a list of test cases + + Returns + ------- + Submission or Submissions + A single submission or a list of submissions. + + The following table contains the return type based on the types of + `submissions` (or `source_code`) and `test_cases` arguments: + + | submissions | test_cases | returns | + |:------------|:-----------|:------------| + | Submission | TestCase | Submission | + | Submission | TestCases | Submissions | + | Submissions | TestCase | Submissions | + | Submissions | TestCases | Submissions | + + Raises + ------ + ClientResolutionError + If client cannot be resolved from the submissions or the flavor. + ValueError + If both or neither submissions and source_code arguments are provided. + """ return _execute( client=client, submissions=submissions, @@ -245,9 +322,45 @@ def sync_execute( client: Optional[Union[Client, Flavor]] = None, submissions: Optional[Union[Submission, Submissions]] = None, source_code: Optional[str] = None, - test_cases: Optional[Union[TestCase, TestCases]] = None, + test_cases: Optional[Union[TestCaseType, TestCases]] = None, **kwargs, ) -> Union[Submission, Submissions]: + """Create submission(s) and wait for their finish. + + Parameters + ---------- + client : Client or Flavor, optional + A client where submissions should be created. If None, will try to be + resolved. + submissions : Submission or Submissions, optional + Submission or submissions for execution. + source_code: str, optional + A source code of a program. + test_cases: TestCaseType or TestCases, optional + A single test or a list of test cases + + Returns + ------- + Submission or Submissions + A single submission or a list of submissions. + + The following table contains the return type based on the types of + `submissions` (or `source_code`) and `test_cases` arguments: + + | submissions | test_cases | returns | + |:------------|:-----------|:------------| + | Submission | TestCase | Submission | + | Submission | TestCases | Submissions | + | Submissions | TestCase | Submissions | + | Submissions | TestCases | Submissions | + + Raises + ------ + ClientResolutionError + If client cannot be resolved from the submissions or the flavor. + ValueError + If both or neither submissions and source_code arguments are provided. + """ return _execute( client=client, submissions=submissions, diff --git a/src/judge0/base_types.py b/src/judge0/base_types.py index e99ce199..e89c8d57 100644 --- a/src/judge0/base_types.py +++ b/src/judge0/base_types.py @@ -5,21 +5,17 @@ Iterable = Sequence -TestCases = Iterable["TestCase"] +TestCaseType = Union["TestCase", list, tuple, dict] +TestCases = Iterable[TestCaseType] @dataclass(frozen=True) class TestCase: - # Needed to disable pytest from recognizing it as a class containing different test cases. - __test__ = False - input: Optional[str] = None expected_output: Optional[str] = None @staticmethod - def from_record( - test_case: Optional[Union[tuple, list, dict, "TestCase"]] = None - ) -> "TestCase": + def from_record(test_case: Optional[TestCaseType] = None) -> "TestCase": if isinstance(test_case, (tuple, list)): test_case = { field: value diff --git a/src/judge0/utils.py b/src/judge0/utils.py index 184c3681..e38b41f5 100644 --- a/src/judge0/utils.py +++ b/src/judge0/utils.py @@ -5,6 +5,8 @@ from requests import HTTPError +from .errors import PreviewClientLimitError + def is_http_too_many_requests_error(exception: Exception) -> bool: return ( @@ -31,11 +33,10 @@ def wrapper(*args, **kwargs): class_name in ("SuluJudge0CE", "SuluJudge0ExtraCE") and instance.api_key is None ): - raise RuntimeError( - "You are using a preview version of the Sulu " - "clients and you've hit a rate limit on the preview " - f"clients. Visit {instance.HOME_URL} to get or " - "review your authentication credentials." + raise PreviewClientLimitError( + "You are using a preview version of a client and " + f"you've hit a rate limit on it. Visit {instance.HOME_URL} " + "to get your authentication credentials." ) from err else: raise err from None From a63bae6e2f97edc6d9ccf13b484ea0e4e0c31c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 30 Nov 2024 19:49:55 +0100 Subject: [PATCH 007/161] Use Session object for requests in client methods. --- src/judge0/clients.py | 64 +++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/src/judge0/clients.py b/src/judge0/clients.py index 13ccdc63..4f63850f 100644 --- a/src/judge0/clients.py +++ b/src/judge0/clients.py @@ -22,6 +22,7 @@ def __init__( self.endpoint = endpoint self.auth_headers = auth_headers self.retry_strategy = retry_strategy + self.session = requests.Session() # TODO: Should be handled differently. try: @@ -33,46 +34,49 @@ def __init__( "review your authentication credentials." ) from e + def __del__(self): + self.session.close() + @handle_too_many_requests_error_for_preview_client def get_about(self) -> dict: - r = requests.get( + response = self.session.get( f"{self.endpoint}/about", headers=self.auth_headers, ) - r.raise_for_status() - return r.json() + response.raise_for_status() + return response.json() @handle_too_many_requests_error_for_preview_client def get_config_info(self) -> dict: - r = requests.get( + response = self.session.get( f"{self.endpoint}/config_info", headers=self.auth_headers, ) - r.raise_for_status() - return r.json() + response.raise_for_status() + return response.json() @handle_too_many_requests_error_for_preview_client def get_language(self, language_id) -> dict: request_url = f"{self.endpoint}/languages/{language_id}" - r = requests.get(request_url, headers=self.auth_headers) - r.raise_for_status() - return r.json() + response = self.session.get(request_url, headers=self.auth_headers) + response.raise_for_status() + return response.json() @handle_too_many_requests_error_for_preview_client def get_languages(self) -> list[dict]: request_url = f"{self.endpoint}/languages" - r = requests.get(request_url, headers=self.auth_headers) - r.raise_for_status() - return r.json() + response = self.session.get(request_url, headers=self.auth_headers) + response.raise_for_status() + return response.json() @handle_too_many_requests_error_for_preview_client def get_statuses(self) -> list[dict]: - r = requests.get( + response = self.session.get( f"{self.endpoint}/statuses", headers=self.auth_headers, ) - r.raise_for_status() - return r.json() + response.raise_for_status() + return response.json() @property def version(self): @@ -123,15 +127,15 @@ def create_submission(self, submission: Submission) -> Submission: body = submission.as_body(self) - resp = requests.post( + response = self.session.post( f"{self.endpoint}/submissions", json=body, params=params, headers=self.auth_headers, ) - resp.raise_for_status() + response.raise_for_status() - submission.set_attributes(resp.json()) + submission.set_attributes(response.json()) return submission @@ -169,14 +173,14 @@ def get_submission( else: params["fields"] = "*" - resp = requests.get( + response = self.session.get( f"{self.endpoint}/submissions/{submission.token}", params=params, headers=self.auth_headers, ) - resp.raise_for_status() + response.raise_for_status() - submission.set_attributes(resp.json()) + submission.set_attributes(response.json()) return submission @@ -209,15 +213,15 @@ def create_submissions(self, submissions: Submissions) -> Submissions: submissions_body = [submission.as_body(self) for submission in submissions] - resp = requests.post( + response = self.session.post( f"{self.endpoint}/submissions/batch", headers=self.auth_headers, params={"base64_encoded": "true"}, json={"submissions": submissions_body}, ) - resp.raise_for_status() + response.raise_for_status() - for submission, attrs in zip(submissions, resp.json()): + for submission, attrs in zip(submissions, response.json()): submission.set_attributes(attrs) return submissions @@ -262,20 +266,22 @@ def get_submissions( tokens = ",".join(submission.token for submission in submissions) params["tokens"] = tokens - resp = requests.get( + response = self.session.get( f"{self.endpoint}/submissions/batch", params=params, headers=self.auth_headers, ) - resp.raise_for_status() + response.raise_for_status() - for submission, attrs in zip(submissions, resp.json()["submissions"]): + for submission, attrs in zip(submissions, response.json()["submissions"]): submission.set_attributes(attrs) return submissions class ATD(Client): + """Base class for all AllThingsDev clients.""" + API_KEY_ENV = "JUDGE0_ATD_API_KEY" def __init__(self, endpoint, host_header_value, api_key, **kwargs): @@ -439,6 +445,8 @@ def get_submissions( class Rapid(Client): + """Base class for all RapidAPI clients.""" + API_KEY_ENV = "JUDGE0_RAPID_API_KEY" def __init__(self, endpoint, host_header_value, api_key, **kwargs): @@ -482,6 +490,8 @@ def __init__(self, api_key, **kwargs): class Sulu(Client): + """Base class for all Sulu clients.""" + API_KEY_ENV = "JUDGE0_SULU_API_KEY" def __init__(self, endpoint, api_key=None, **kwargs): From 41aec341ba26d0dc57661dade491d23e7d9e6ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 1 Dec 2024 19:45:55 +0100 Subject: [PATCH 008/161] Minor docstring update in api functions. --- src/judge0/api.py | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/judge0/api.py b/src/judge0/api.py index 0d8504fd..fabcd4ff 100644 --- a/src/judge0/api.py +++ b/src/judge0/api.py @@ -273,6 +273,18 @@ def async_execute( ) -> Union[Submission, Submissions]: """Create submission(s). + Aliases: `async_run`. + + The following table contains the return type based on the types of + `submissions` (or `source_code`) and `test_cases` arguments: + + | submissions | test_cases | returns | + |:------------|:-----------|:------------| + | Submission | TestCase | Submission | + | Submission | TestCases | Submissions | + | Submissions | TestCase | Submissions | + | Submissions | TestCases | Submissions | + Parameters ---------- client : Client or Flavor, optional @@ -290,16 +302,6 @@ def async_execute( Submission or Submissions A single submission or a list of submissions. - The following table contains the return type based on the types of - `submissions` (or `source_code`) and `test_cases` arguments: - - | submissions | test_cases | returns | - |:------------|:-----------|:------------| - | Submission | TestCase | Submission | - | Submission | TestCases | Submissions | - | Submissions | TestCase | Submissions | - | Submissions | TestCases | Submissions | - Raises ------ ClientResolutionError @@ -327,6 +329,18 @@ def sync_execute( ) -> Union[Submission, Submissions]: """Create submission(s) and wait for their finish. + Aliases: `execute`, `run`, `sync_run`. + + The following table contains the return type based on the types of + `submissions` (or `source_code`) and `test_cases` arguments: + + | submissions | test_cases | returns | + |:------------|:-----------|:------------| + | Submission | TestCase | Submission | + | Submission | TestCases | Submissions | + | Submissions | TestCase | Submissions | + | Submissions | TestCases | Submissions | + Parameters ---------- client : Client or Flavor, optional @@ -344,16 +358,6 @@ def sync_execute( Submission or Submissions A single submission or a list of submissions. - The following table contains the return type based on the types of - `submissions` (or `source_code`) and `test_cases` arguments: - - | submissions | test_cases | returns | - |:------------|:-----------|:------------| - | Submission | TestCase | Submission | - | Submission | TestCases | Submissions | - | Submissions | TestCase | Submissions | - | Submissions | TestCases | Submissions | - Raises ------ ClientResolutionError From 755ee9adfa64980659a8b9d0f8564baadf1a71ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Mon, 2 Dec 2024 18:42:15 +0100 Subject: [PATCH 009/161] Remove Pipenv file. Add test/dev dependencies to pyproject.toml. --- .github/workflows/test.yml | 11 ++++++----- Pipfile | 19 ------------------- pyproject.toml | 11 +++++++++-- 3 files changed, 15 insertions(+), 26 deletions(-) delete mode 100644 Pipfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 62f57c94..86a0ddd0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ permissions: jobs: test: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -19,10 +19,10 @@ jobs: python-version: "3.9" - name: Install dependencies run: | + python -m venv venv + source venv/bin/activate python -m pip install --upgrade pip - pip install pipenv - pipenv install --dev - pipenv install -e . + pip install -e .[test] - name: Test with pytest env: # Add necessary api keys as env variables. JUDGE0_ATD_API_KEY: ${{ secrets.JUDGE0_ATD_API_KEY }} @@ -33,4 +33,5 @@ jobs: JUDGE0_TEST_CE_ENDPOINT: ${{ secrets.JUDGE0_TEST_CE_ENDPOINT }} JUDGE0_TEST_EXTRA_CE_ENDPOINT: ${{ secrets.JUDGE0_TEST_EXTRA_CE_ENDPOINT }} run: | - pipenv run pytest -vv tests/ + source venv/bin/activate + pytest -vv tests/ diff --git a/Pipfile b/Pipfile deleted file mode 100644 index ec5d4e18..00000000 --- a/Pipfile +++ /dev/null @@ -1,19 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -requests = "==2.32.3" - -[dev-packages] -ufmt = "==2.7.3" -pre-commit = "==3.8.0" -pytest = "==8.3.3" -python-dotenv = "==1.0.1" -pytest-cov = "6.0.0" -flake8-docstrings = "1.7.0" - -[requires] -python_version = "3.9" -python_full_version = "3.9.20" diff --git a/pyproject.toml b/pyproject.toml index e4c72e75..de238618 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed", ] -dependencies = ["requests>=2.32.3"] +dependencies = ["requests>=2.28.0,<3.0.0"] [build-system] requires = ["setuptools>=70.0"] @@ -37,7 +37,14 @@ Repository = "https://github.com/judge0/judge0-python.git" Issues = "https://github.com/judge0/judge0-python/issues" [project.optional-dependencies] -test = ["pytest", "mkdocs"] +test = [ + "ufmt==2.7.3", + "pre-commit==3.8.0", + "pytest==8.3.3", + "python-dotenv==1.0.1", + "pytest-cov==6.0.0", + "flake8-docstrings==1.7.0", +] [tool.flake8] docstring-convention = "numpy" From 3c022a0de493d613c67e3e791d094e4ad1beff0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Mon, 2 Dec 2024 18:54:23 +0100 Subject: [PATCH 010/161] Add CONTRIBUTING. --- CONTRIBUTING.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..f5ecd000 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# How to contribute + +## Preparing the development setup + +1. Install Python 3.9 + +```bash +sudo add-apt-repository ppa:deadsnakes/ppa +sudo apt update +sudo apt install python3.9 python3.9-venv +``` + +2. Clone the repo, create and activate a new virtual environment + +```bash +cd judge0-python +python3.9 -m venv venv +source venv/bin/activate +``` + +3. Install the library and development dependencies + +```bash +pip install -e .[test] +``` From e9fc9da0f9c956a80d82723a1746f784dc19f729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Mon, 2 Dec 2024 20:19:46 +0100 Subject: [PATCH 011/161] Minor update to CONTRIBUTING. --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f5ecd000..e85b3388 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,4 +22,5 @@ source venv/bin/activate ```bash pip install -e .[test] +pre-commit install ``` From b9c39ce0345052b5ef8133fa15e62950f35c46ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 7 Dec 2024 19:33:33 +0100 Subject: [PATCH 012/161] Add to_dict and from_dict to Submission and Filesystem objects. --- src/judge0/base_types.py | 16 ++++++++++----- src/judge0/common.py | 4 ++-- src/judge0/filesystem.py | 22 +++++++++++++++++++-- src/judge0/submission.py | 42 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/judge0/base_types.py b/src/judge0/base_types.py index e89c8d57..f6db9ebd 100644 --- a/src/judge0/base_types.py +++ b/src/judge0/base_types.py @@ -1,7 +1,6 @@ -from abc import ABC, abstractmethod from dataclasses import dataclass from enum import IntEnum -from typing import Optional, Sequence, Union +from typing import Optional, Protocol, runtime_checkable, Sequence, Union Iterable = Sequence @@ -33,10 +32,11 @@ def from_record(test_case: Optional[TestCaseType] = None) -> "TestCase": ) -class Encodeable(ABC): - @abstractmethod +@runtime_checkable +class Encodeable(Protocol): def encode(self) -> bytes: - pass + """Serialize the object to bytes.""" + ... @dataclass(frozen=True) @@ -46,6 +46,8 @@ class Language: class LanguageAlias(IntEnum): + """Language enumeration.""" + PYTHON = 0 CPP = 1 JAVA = 2 @@ -55,11 +57,15 @@ class LanguageAlias(IntEnum): class Flavor(IntEnum): + """Judge0 flavor enumeration.""" + CE = 0 EXTRA_CE = 1 class Status(IntEnum): + """Status enumeration.""" + IN_QUEUE = 1 PROCESSING = 2 ACCEPTED = 3 diff --git a/src/judge0/common.py b/src/judge0/common.py index 736895e2..1f07b632 100644 --- a/src/judge0/common.py +++ b/src/judge0/common.py @@ -2,7 +2,7 @@ from itertools import islice from typing import Union -from .base_types import Encodeable +from judge0.base_types import Encodeable def encode(content: Union[bytes, str, Encodeable]) -> str: @@ -26,7 +26,7 @@ def decode(content: Union[bytes, str]) -> str: def batched(iterable, n): - """Utility function for batching submissions. + """Iterate over an iterable in batches of a specified size. Adapted from https://docs.python.org/3/library/itertools.html#itertools.batched. """ diff --git a/src/judge0/filesystem.py b/src/judge0/filesystem.py index bbdb11b4..11507811 100644 --- a/src/judge0/filesystem.py +++ b/src/judge0/filesystem.py @@ -5,7 +5,7 @@ from base64 import b64decode, b64encode from typing import Optional, Union -from .base_types import Encodeable, Iterable +from .base_types import Iterable class File: @@ -24,7 +24,7 @@ def __str__(self): return self.content.decode(errors="backslashreplace") -class Filesystem(Encodeable): +class Filesystem: def __init__( self, content: Optional[Union[str, bytes, File, Iterable[File], "Filesystem"]] = None, @@ -47,6 +47,14 @@ def __init__( self.files = [content] elif isinstance(content, Filesystem): self.files = copy.deepcopy(content.files) + elif content is None: + pass + else: + raise ValueError( + "Unsupported type for content argument. Expected " + "one of str, bytes, File, Iterable[File], or Filesystem, " + f"got {type(content)}." + ) def __repr__(self) -> str: content_encoded = b64encode(self.encode()).decode() @@ -59,7 +67,17 @@ def encode(self) -> bytes: zip_file.writestr(file.name, file.content) return zip_buffer.getvalue() + def to_dict(self) -> dict: + """Pack the Filesystem object to a dictionary.""" + return {"filesystem": str(self)} + + @staticmethod + def from_dict(filesystem_dict: dict) -> "Filesystem": + """Create a Filesystem object from dictionary.""" + return Filesystem(filesystem_dict.get("filesystem")) + def __str__(self) -> str: + """Create string representation of Filesystem object.""" return b64encode(self.encode()).decode() def __iter__(self): diff --git a/src/judge0/submission.py b/src/judge0/submission.py index 55a7d3c6..ddfee950 100644 --- a/src/judge0/submission.py +++ b/src/judge0/submission.py @@ -17,7 +17,7 @@ "stdout", "stderr", "compile_output", - # "post_execution_filesystem", + "post_execution_filesystem", } ENCODED_FIELDS = ENCODED_REQUEST_FIELDS | ENCODED_RESPONSE_FIELDS EXTRA_REQUEST_FIELDS = { @@ -48,7 +48,6 @@ "time", "wall_time", "memory", - "post_execution_filesystem", } REQUEST_FIELDS = ENCODED_REQUEST_FIELDS | EXTRA_REQUEST_FIELDS RESPONSE_FIELDS = ENCODED_RESPONSE_FIELDS | EXTRA_RESPONSE_FIELDS @@ -207,7 +206,7 @@ def set_attributes(self, attributes: dict[str, Any]) -> None: if attr in SKIP_FIELDS: continue - if attr in ENCODED_FIELDS: + if attr in ENCODED_FIELDS and attr not in ("post_execution_filesystem",): value = decode(value) if value else None elif attr == "status": value = Status(value["id"]) @@ -241,6 +240,43 @@ def as_body(self, client: "Client") -> dict: return body + def to_dict(self) -> dict: + encoded_request_fields = { + field_name: encode(getattr(self, field_name)) + for field_name in ENCODED_REQUEST_FIELDS + if getattr(self, field_name) is not None + } + extra_request_fields = { + field_name: getattr(self, field_name) + for field_name in EXTRA_REQUEST_FIELDS + if getattr(self, field_name) is not None + } + encoded_response_fields = { + field_name: encode(getattr(self, field_name)) + for field_name in ENCODED_RESPONSE_FIELDS + if getattr(self, field_name) is not None + } + extra_response_fields = { + field_name: getattr(self, field_name) + for field_name in EXTRA_RESPONSE_FIELDS + if getattr(self, field_name) is not None + } + + submission_dict = ( + encoded_request_fields + | extra_request_fields + | encoded_response_fields + | extra_response_fields + ) + + return submission_dict + + @staticmethod + def from_dict(submission_dict) -> "Submission": + submission = Submission() + submission.set_attributes(submission_dict) + return submission + def is_done(self) -> bool: """Check if submission is finished processing. From 13703bc4594663080311a008136e30069e9832ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Mon, 9 Dec 2024 17:58:47 +0100 Subject: [PATCH 013/161] Add pydantic as dependency. Make Language and Config classes inherit BaseModel. --- pyproject.toml | 4 ++-- src/judge0/base_types.py | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index de238618..01fc8e2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.0.1" +version = "0.0.2dev" description = "The official Python library for Judge0." readme = "README.md" requires-python = ">=3.9" @@ -25,7 +25,7 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed", ] -dependencies = ["requests>=2.28.0,<3.0.0"] +dependencies = ["requests>=2.28.0,<3.0.0", "pydantic>=2.0.0,<3.0.0"] [build-system] requires = ["setuptools>=70.0"] diff --git a/src/judge0/base_types.py b/src/judge0/base_types.py index f6db9ebd..0c7f4508 100644 --- a/src/judge0/base_types.py +++ b/src/judge0/base_types.py @@ -2,6 +2,8 @@ from enum import IntEnum from typing import Optional, Protocol, runtime_checkable, Sequence, Union +from pydantic import BaseModel + Iterable = Sequence TestCaseType = Union["TestCase", list, tuple, dict] @@ -15,6 +17,7 @@ class TestCase: @staticmethod def from_record(test_case: Optional[TestCaseType] = None) -> "TestCase": + """Create a TestCase from built-in types.""" if isinstance(test_case, (tuple, list)): test_case = { field: value @@ -39,8 +42,7 @@ def encode(self) -> bytes: ... -@dataclass(frozen=True) -class Language: +class Language(BaseModel): id: int name: str @@ -85,8 +87,9 @@ def __str__(self): return self.name.lower().replace("_", " ").title() -@dataclass(frozen=True) -class Config: +class Config(BaseModel): + """Client config data.""" + allow_enable_network: bool allow_enable_per_process_and_thread_memory_limit: bool allow_enable_per_process_and_thread_time_limit: bool From e797c72b141cf1a5ceba133f36062908fabe52aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Mon, 9 Dec 2024 18:18:30 +0100 Subject: [PATCH 014/161] Make Submissions, File, Filesystem work with pydantic's BaseModel. --- src/judge0/filesystem.py | 44 ++++++------ src/judge0/submission.py | 146 +++++++++++---------------------------- 2 files changed, 60 insertions(+), 130 deletions(-) diff --git a/src/judge0/filesystem.py b/src/judge0/filesystem.py index 11507811..6773680c 100644 --- a/src/judge0/filesystem.py +++ b/src/judge0/filesystem.py @@ -5,18 +5,22 @@ from base64 import b64decode, b64encode from typing import Optional, Union +from pydantic import BaseModel + from .base_types import Iterable -class File: - def __init__(self, name: str, content: Optional[Union[str, bytes]] = None): - self.name = name +class File(BaseModel): + name: str + content: Optional[Union[str, bytes]] = None + def __init__(self, **data): + super().__init__(**data) # Let's keep content attribute internally encoded as bytes. - if isinstance(content, str): - self.content = content.encode() - elif isinstance(content, bytes): - self.content = content + if isinstance(self.content, str): + self.content = self.content.encode() + elif isinstance(self.content, bytes): + self.content = self.content else: self.content = b"" @@ -24,12 +28,13 @@ def __str__(self): return self.content.decode(errors="backslashreplace") -class Filesystem: - def __init__( - self, - content: Optional[Union[str, bytes, File, Iterable[File], "Filesystem"]] = None, - ): - self.files: list[File] = [] +class Filesystem(BaseModel): + files: list[File] = [] + + def __init__(self, **data): + content = data.pop("content", None) + super().__init__(**data) + self.files = [] if isinstance(content, (bytes, str)): if isinstance(content, bytes): @@ -40,7 +45,7 @@ def __init__( with zipfile.ZipFile(io.BytesIO(zip_bytes), "r") as zip_file: for file_name in zip_file.namelist(): with zip_file.open(file_name) as fp: - self.files.append(File(file_name, fp.read())) + self.files.append(File(name=file_name, content=fp.read())) elif isinstance(content, Iterable): self.files = list(content) elif isinstance(content, File): @@ -48,7 +53,7 @@ def __init__( elif isinstance(content, Filesystem): self.files = copy.deepcopy(content.files) elif content is None: - pass + self.files = [] else: raise ValueError( "Unsupported type for content argument. Expected " @@ -67,15 +72,6 @@ def encode(self) -> bytes: zip_file.writestr(file.name, file.content) return zip_buffer.getvalue() - def to_dict(self) -> dict: - """Pack the Filesystem object to a dictionary.""" - return {"filesystem": str(self)} - - @staticmethod - def from_dict(filesystem_dict: dict) -> "Filesystem": - """Create a Filesystem object from dictionary.""" - return Filesystem(filesystem_dict.get("filesystem")) - def __str__(self) -> str: """Create string representation of Filesystem object.""" return b64encode(self.encode()).decode() diff --git a/src/judge0/submission.py b/src/judge0/submission.py index ddfee950..57a5cc6f 100644 --- a/src/judge0/submission.py +++ b/src/judge0/submission.py @@ -2,10 +2,11 @@ from datetime import datetime from typing import Any, Optional, Union -from judge0.filesystem import Filesystem +from pydantic import BaseModel from .base_types import Iterable, LanguageAlias, Status from .common import decode, encode +from .filesystem import Filesystem ENCODED_REQUEST_FIELDS = { "source_code", @@ -65,7 +66,7 @@ Submissions = Iterable["Submission"] -class Submission: +class Submission(BaseModel): """ Stores a representation of a Submission to/from Judge0. @@ -125,72 +126,42 @@ class Submission: URL for a callback to report execution results or status. """ - def __init__( - self, - *, - source_code: Optional[str] = None, - language: Union[LanguageAlias, int] = LanguageAlias.PYTHON, - additional_files: Optional[str] = None, - compiler_options: Optional[str] = None, - command_line_arguments: Optional[str] = None, - stdin: Optional[str] = None, - expected_output: Optional[str] = None, - cpu_time_limit: Optional[float] = None, - cpu_extra_time: Optional[float] = None, - wall_time_limit: Optional[float] = None, - memory_limit: Optional[float] = None, - stack_limit: Optional[int] = None, - max_processes_and_or_threads: Optional[int] = None, - enable_per_process_and_thread_time_limit: Optional[bool] = None, - enable_per_process_and_thread_memory_limit: Optional[bool] = None, - max_file_size: Optional[int] = None, - redirect_stderr_to_stdout: Optional[bool] = None, - enable_network: Optional[bool] = None, - number_of_runs: Optional[int] = None, - callback_url: Optional[str] = None, - ): - self.source_code = source_code - self.language = language - self.additional_files = additional_files - - # Extra pre-execution submission attributes. - self.compiler_options = compiler_options - self.command_line_arguments = command_line_arguments - self.stdin = stdin - self.expected_output = expected_output - self.cpu_time_limit = cpu_time_limit - self.cpu_extra_time = cpu_extra_time - self.wall_time_limit = wall_time_limit - self.memory_limit = memory_limit - self.stack_limit = stack_limit - self.max_processes_and_or_threads = max_processes_and_or_threads - self.enable_per_process_and_thread_time_limit = ( - enable_per_process_and_thread_time_limit - ) - self.enable_per_process_and_thread_memory_limit = ( - enable_per_process_and_thread_memory_limit - ) - self.max_file_size = max_file_size - self.redirect_stderr_to_stdout = redirect_stderr_to_stdout - self.enable_network = enable_network - self.number_of_runs = number_of_runs - self.callback_url = callback_url - - # Post-execution submission attributes. - self.stdout: Optional[str] = None - self.stderr: Optional[str] = None - self.compile_output: Optional[str] = None - self.message: Optional[str] = None - self.exit_code: Optional[int] = None - self.exit_signal: Optional[int] = None - self.status: Optional[Status] = None - self.created_at: Optional[datetime] = None - self.finished_at: Optional[datetime] = None - self.token: str = "" - self.time: Optional[float] = None - self.wall_time: Optional[float] = None - self.memory: Optional[float] = None - self.post_execution_filesystem: Optional[Filesystem] = None + source_code: Optional[str] = None + language: Union[LanguageAlias, int] = LanguageAlias.PYTHON + additional_files: Optional[str] = None + compiler_options: Optional[str] = None + command_line_arguments: Optional[str] = None + stdin: Optional[str] = None + expected_output: Optional[str] = None + cpu_time_limit: Optional[float] = None + cpu_extra_time: Optional[float] = None + wall_time_limit: Optional[float] = None + memory_limit: Optional[float] = None + stack_limit: Optional[int] = None + max_processes_and_or_threads: Optional[int] = None + enable_per_process_and_thread_time_limit: Optional[bool] = None + enable_per_process_and_thread_memory_limit: Optional[bool] = None + max_file_size: Optional[int] = None + redirect_stderr_to_stdout: Optional[bool] = None + enable_network: Optional[bool] = None + number_of_runs: Optional[int] = None + callback_url: Optional[str] = None + + # Post-execution submission attributes. + stdout: Optional[str] = None + stderr: Optional[str] = None + compile_output: Optional[str] = None + message: Optional[str] = None + exit_code: Optional[int] = None + exit_signal: Optional[int] = None + status: Optional[Status] = None + created_at: Optional[datetime] = None + finished_at: Optional[datetime] = None + token: str = "" + time: Optional[float] = None + wall_time: Optional[float] = None + memory: Optional[float] = None + post_execution_filesystem: Optional[Filesystem] = None def set_attributes(self, attributes: dict[str, Any]) -> None: """Set Submissions attributes while taking into account different @@ -215,7 +186,7 @@ def set_attributes(self, attributes: dict[str, Any]) -> None: elif attr in FLOATING_POINT_FIELDS and value is not None: value = float(value) elif attr == "post_execution_filesystem": - value = Filesystem(value) + value = Filesystem(content=value) setattr(self, attr, value) @@ -240,43 +211,6 @@ def as_body(self, client: "Client") -> dict: return body - def to_dict(self) -> dict: - encoded_request_fields = { - field_name: encode(getattr(self, field_name)) - for field_name in ENCODED_REQUEST_FIELDS - if getattr(self, field_name) is not None - } - extra_request_fields = { - field_name: getattr(self, field_name) - for field_name in EXTRA_REQUEST_FIELDS - if getattr(self, field_name) is not None - } - encoded_response_fields = { - field_name: encode(getattr(self, field_name)) - for field_name in ENCODED_RESPONSE_FIELDS - if getattr(self, field_name) is not None - } - extra_response_fields = { - field_name: getattr(self, field_name) - for field_name in EXTRA_RESPONSE_FIELDS - if getattr(self, field_name) is not None - } - - submission_dict = ( - encoded_request_fields - | extra_request_fields - | encoded_response_fields - | extra_response_fields - ) - - return submission_dict - - @staticmethod - def from_dict(submission_dict) -> "Submission": - submission = Submission() - submission.set_attributes(submission_dict) - return submission - def is_done(self) -> bool: """Check if submission is finished processing. From e9fc9334c4a5b0ba68f2c800a0bfac28f7d44014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 10 Dec 2024 17:34:36 +0100 Subject: [PATCH 015/161] Make Submission work with pydantic. --- .../1000_http_callback_aka_webhook/main.py | 33 +++-- src/judge0/common.py | 8 +- src/judge0/submission.py | 122 ++++++++++++------ tests/test_submission.py | 47 +++++++ 4 files changed, 149 insertions(+), 61 deletions(-) diff --git a/examples/1000_http_callback_aka_webhook/main.py b/examples/1000_http_callback_aka_webhook/main.py index dab2b77b..a65411d5 100755 --- a/examples/1000_http_callback_aka_webhook/main.py +++ b/examples/1000_http_callback_aka_webhook/main.py @@ -1,18 +1,10 @@ #!/usr/bin/env python3 -from fastapi import FastAPI, Depends -from pydantic import BaseModel - -import uvicorn import asyncio -import judge0 +import judge0 -class CallbackResponse(BaseModel): - created_at: str - finished_at: str - language: dict - status: dict - stdout: str +import uvicorn +from fastapi import Depends, FastAPI class AppContext: @@ -47,13 +39,14 @@ async def root(app_context=Depends(get_app_context)): @app.put("/callback") -async def callback(response: CallbackResponse): +async def callback(response: judge0.Submission): print(f"Received: {response}") -# We are using free service from https://localhost.run to get a public URL for our local server. -# This approach is not recommended for production use. It is only for demonstration purposes -# since domain names change regularly and there is a speed limit for the free service. +# We are using free service from https://localhost.run to get a public URL for +# our local server. This approach is not recommended for production use. It is +# only for demonstration purposes since domain names change regularly and there +# is a speed limit for the free service. async def run_ssh_tunnel(): app_context = get_app_context() @@ -69,7 +62,9 @@ async def run_ssh_tunnel(): ] process = await asyncio.create_subprocess_exec( - *command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT + *command, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.STDOUT, ) while True: @@ -86,7 +81,11 @@ async def run_ssh_tunnel(): async def run_server(): config = uvicorn.Config( - app, host="127.0.0.1", port=LOCAL_SERVER_PORT, workers=5, loop="asyncio" + app, + host="127.0.0.1", + port=LOCAL_SERVER_PORT, + workers=5, + loop="asyncio", ) server = uvicorn.Server(config) await server.serve() diff --git a/src/judge0/common.py b/src/judge0/common.py index 1f07b632..57ad8387 100644 --- a/src/judge0/common.py +++ b/src/judge0/common.py @@ -17,11 +17,13 @@ def encode(content: Union[bytes, str, Encodeable]) -> str: def decode(content: Union[bytes, str]) -> str: if isinstance(content, bytes): - return b64decode(content.decode(errors="backslashreplace")).decode( + return b64decode( + content.decode(errors="backslashreplace"), validate=True + ).decode(errors="backslashreplace") + if isinstance(content, str): + return b64decode(content.encode(), validate=True).decode( errors="backslashreplace" ) - if isinstance(content, str): - return b64decode(content.encode()).decode(errors="backslashreplace") raise ValueError(f"Unsupported type. Expected bytes or str, got {type(content)}!") diff --git a/src/judge0/submission.py b/src/judge0/submission.py index 57a5cc6f..069733c5 100644 --- a/src/judge0/submission.py +++ b/src/judge0/submission.py @@ -2,7 +2,7 @@ from datetime import datetime from typing import Any, Optional, Union -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict, Field, field_validator, UUID4 from .base_types import Iterable, LanguageAlias, Status from .common import decode, encode @@ -18,7 +18,7 @@ "stdout", "stderr", "compile_output", - "post_execution_filesystem", + # "post_execution_filesystem", } ENCODED_FIELDS = ENCODED_REQUEST_FIELDS | ENCODED_RESPONSE_FIELDS EXTRA_REQUEST_FIELDS = { @@ -126,42 +126,86 @@ class Submission(BaseModel): URL for a callback to report execution results or status. """ - source_code: Optional[str] = None - language: Union[LanguageAlias, int] = LanguageAlias.PYTHON - additional_files: Optional[str] = None - compiler_options: Optional[str] = None - command_line_arguments: Optional[str] = None - stdin: Optional[str] = None - expected_output: Optional[str] = None - cpu_time_limit: Optional[float] = None - cpu_extra_time: Optional[float] = None - wall_time_limit: Optional[float] = None - memory_limit: Optional[float] = None - stack_limit: Optional[int] = None - max_processes_and_or_threads: Optional[int] = None - enable_per_process_and_thread_time_limit: Optional[bool] = None - enable_per_process_and_thread_memory_limit: Optional[bool] = None - max_file_size: Optional[int] = None - redirect_stderr_to_stdout: Optional[bool] = None - enable_network: Optional[bool] = None - number_of_runs: Optional[int] = None - callback_url: Optional[str] = None + source_code: Optional[str] = Field(default=None, repr=True) + language: Union[LanguageAlias, int] = Field( + default=LanguageAlias.PYTHON, + repr=True, + ) + additional_files: Optional[str] = Field(default=None, repr=True) + compiler_options: Optional[str] = Field(default=None, repr=True) + command_line_arguments: Optional[str] = Field(default=None, repr=True) + stdin: Optional[str] = Field(default=None, repr=True) + expected_output: Optional[str] = Field(default=None, repr=True) + cpu_time_limit: Optional[float] = Field(default=None, repr=True) + cpu_extra_time: Optional[float] = Field(default=None, repr=True) + wall_time_limit: Optional[float] = Field(default=None, repr=True) + memory_limit: Optional[float] = Field(default=None, repr=True) + stack_limit: Optional[int] = Field(default=None, repr=True) + max_processes_and_or_threads: Optional[int] = Field(default=None, repr=True) + enable_per_process_and_thread_time_limit: Optional[bool] = Field( + default=None, repr=True + ) + enable_per_process_and_thread_memory_limit: Optional[bool] = Field( + default=None, repr=True + ) + max_file_size: Optional[int] = Field(default=None, repr=True) + redirect_stderr_to_stdout: Optional[bool] = Field(default=None, repr=True) + enable_network: Optional[bool] = Field(default=None, repr=True) + number_of_runs: Optional[int] = Field(default=None, repr=True) + callback_url: Optional[str] = Field(default=None, repr=True) # Post-execution submission attributes. - stdout: Optional[str] = None - stderr: Optional[str] = None - compile_output: Optional[str] = None - message: Optional[str] = None - exit_code: Optional[int] = None - exit_signal: Optional[int] = None - status: Optional[Status] = None - created_at: Optional[datetime] = None - finished_at: Optional[datetime] = None - token: str = "" - time: Optional[float] = None - wall_time: Optional[float] = None - memory: Optional[float] = None - post_execution_filesystem: Optional[Filesystem] = None + stdout: Optional[str] = Field(default=None, repr=True) + stderr: Optional[str] = Field(default=None, repr=True) + compile_output: Optional[str] = Field(default=None, repr=True) + message: Optional[str] = Field(default=None, repr=True) + exit_code: Optional[int] = Field(default=None, repr=True) + exit_signal: Optional[int] = Field(default=None, repr=True) + status: Optional[Status] = Field(default=None, repr=True) + created_at: Optional[datetime] = Field(default=None, repr=True) + finished_at: Optional[datetime] = Field(default=None, repr=True) + token: Optional[UUID4] = Field(default=None, repr=True) + time: Optional[float] = Field(default=None, repr=True) + wall_time: Optional[float] = Field(default=None, repr=True) + memory: Optional[float] = Field(default=None, repr=True) + post_execution_filesystem: Optional[Filesystem] = Field(default=None, repr=True) + + model_config = ConfigDict(extra="ignore") + + @field_validator(*ENCODED_FIELDS, mode="before") + @classmethod + def process_encoded_fields(cls, value: str) -> Optional[str]: + """Validate all encoded attributes.""" + if value is None: + return None + else: + try: + return decode(value) + except Exception: + return value + + @field_validator("post_execution_filesystem", mode="before") + @classmethod + def process_post_execution_filesystem(cls, content: str) -> Filesystem: + """Validate post_execution_filesystem attribute.""" + return Filesystem(content=content) + + @field_validator("status", mode="before") + @classmethod + def process_status(cls, value: dict) -> Status: + """Validate status attribute.""" + return Status(value["id"]) + + @field_validator("language", mode="before") + @classmethod + def process_language( + cls, value: Union[LanguageAlias, dict] + ) -> Union[LanguageAlias, int]: + """Validate status attribute.""" + if isinstance(value, dict): + return value["id"] + else: + return value def set_attributes(self, attributes: dict[str, Any]) -> None: """Set Submissions attributes while taking into account different @@ -177,7 +221,7 @@ def set_attributes(self, attributes: dict[str, Any]) -> None: if attr in SKIP_FIELDS: continue - if attr in ENCODED_FIELDS and attr not in ("post_execution_filesystem",): + if attr in ENCODED_FIELDS: value = decode(value) if value else None elif attr == "status": value = Status(value["id"]) @@ -229,10 +273,6 @@ def pre_execution_copy(self) -> "Submission": setattr(new_submission, attr, copy.deepcopy(getattr(self, attr))) return new_submission - def __repr__(self) -> str: - arguments = ", ".join(f"{field}={getattr(self, field)!r}" for field in FIELDS) - return f"{self.__class__.__name__}({arguments})" - def __iter__(self): if self.post_execution_filesystem is None: return iter([]) diff --git a/tests/test_submission.py b/tests/test_submission.py index c204bcbd..98903ed0 100644 --- a/tests/test_submission.py +++ b/tests/test_submission.py @@ -1,6 +1,53 @@ from judge0 import Status, Submission, wait +def test_from_json(): + submission_dict = { + "source_code": "cHJpbnQoJ0hlbGxvLCBXb3JsZCEnKQ==", + "language_id": 100, + "stdin": None, + "expected_output": None, + "stdout": "SGVsbG8sIFdvcmxkIQo=", + "status_id": 3, + "created_at": "2024-12-09T17:22:55.662Z", + "finished_at": "2024-12-09T17:22:56.045Z", + "time": "0.152", + "memory": 13740, + "stderr": None, + "token": "5513d8ca-975b-4499-b54b-342f1952d00e", + "number_of_runs": 1, + "cpu_time_limit": "5.0", + "cpu_extra_time": "1.0", + "wall_time_limit": "10.0", + "memory_limit": 128000, + "stack_limit": 64000, + "max_processes_and_or_threads": 60, + "enable_per_process_and_thread_time_limit": False, + "enable_per_process_and_thread_memory_limit": False, + "max_file_size": 1024, + "compile_output": None, + "exit_code": 0, + "exit_signal": None, + "message": None, + "wall_time": "0.17", + "compiler_options": None, + "command_line_arguments": None, + "redirect_stderr_to_stdout": False, + "callback_url": None, + "additional_files": None, + "enable_network": False, + "post_execution_filesystem": "UEsDBBQACAAIANyKiVkAAAAAAAAAABYAAAAJABwAc" + "2NyaXB0LnB5VVQJAANvJ1dncCdXZ3V4CwABBOgDAAAE6AMAACsoyswr0VD3SM3JyddRCM8v" + "yklRVNcEAFBLBwgynNLKGAAAABYAAABQSwECHgMUAAgACADciolZMpzSyhgAAAAWAAAACQA" + "YAAAAAAABAAAApIEAAAAAc2NyaXB0LnB5VVQFAANvJ1dndXgLAAEE6AMAAAToAwAAUEsFBg" + "AAAAABAAEATwAAAGsAAAAAAA==", + "status": {"id": 3, "description": "Accepted"}, + "language": {"id": 100, "name": "Python (3.12.5)"}, + } + + _ = Submission(**submission_dict) + + def test_status_before_and_after_submission(request): client = request.getfixturevalue("judge0_ce_client") submission = Submission(source_code='print("Hello World!")') From e5124cda243ca6aab04ebdc05328af183127a2db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Thu, 12 Dec 2024 19:18:47 +0100 Subject: [PATCH 016/161] Add additional attributes to Language class. Add minimal docstring to client classes. Redefine output types for get_languages, get_language, and get_config_info methods. --- src/judge0/base_types.py | 4 ++++ src/judge0/clients.py | 44 ++++++++++++++++++++++++---------------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/judge0/base_types.py b/src/judge0/base_types.py index 0c7f4508..48480e8b 100644 --- a/src/judge0/base_types.py +++ b/src/judge0/base_types.py @@ -45,6 +45,10 @@ def encode(self) -> bytes: class Language(BaseModel): id: int name: str + is_archived: Optional[bool] = None + source_file: Optional[str] = None + compile_cmd: Optional[str] = None + run_cmd: Optional[str] = None class LanguageAlias(IntEnum): diff --git a/src/judge0/clients.py b/src/judge0/clients.py index 4f63850f..29b1ce79 100644 --- a/src/judge0/clients.py +++ b/src/judge0/clients.py @@ -26,8 +26,8 @@ def __init__( # TODO: Should be handled differently. try: - self.languages = tuple(Language(**lang) for lang in self.get_languages()) - self.config = Config(**self.get_config_info()) + self.languages = self.get_languages() + self.config = self.get_config_info() except Exception as e: raise RuntimeError( f"Authentication failed. Visit {self.HOME_URL} to get or " @@ -47,27 +47,27 @@ def get_about(self) -> dict: return response.json() @handle_too_many_requests_error_for_preview_client - def get_config_info(self) -> dict: + def get_config_info(self) -> Config: response = self.session.get( f"{self.endpoint}/config_info", headers=self.auth_headers, ) response.raise_for_status() - return response.json() + return Config(**response.json()) @handle_too_many_requests_error_for_preview_client - def get_language(self, language_id) -> dict: + def get_language(self, language_id: int) -> Language: request_url = f"{self.endpoint}/languages/{language_id}" response = self.session.get(request_url, headers=self.auth_headers) response.raise_for_status() - return response.json() + return Language(**response.json()) @handle_too_many_requests_error_for_preview_client - def get_languages(self) -> list[dict]: + def get_languages(self) -> list[Language]: request_url = f"{self.endpoint}/languages" response = self.session.get(request_url, headers=self.auth_headers) response.raise_for_status() - return response.json() + return [Language(**lang_dict) for lang_dict in response.json()] @handle_too_many_requests_error_for_preview_client def get_statuses(self) -> list[dict]: @@ -249,8 +249,6 @@ def get_submissions( Submissions A sequence of submissions with updated attributes. """ - # TODO: Maybe raise an exception if the number of submissions is bigger - # than the batch size a client supports? params = { "base64_encoded": "true", } @@ -263,7 +261,7 @@ def get_submissions( else: params["fields"] = "*" - tokens = ",".join(submission.token for submission in submissions) + tokens = ",".join([submission.token for submission in submissions]) params["tokens"] = tokens response = self.session.get( @@ -300,6 +298,8 @@ def _update_endpoint_header(self, header_value): class ATDJudge0CE(ATD): + """AllThingsDev client for CE flavor.""" + DEFAULT_ENDPOINT: str = "https://judge0-ce.proxy-production.allthingsdev.co" DEFAULT_HOST: str = "Judge0-CE.allthingsdev.co" HOME_URL: str = ( @@ -328,15 +328,15 @@ def get_about(self) -> dict: self._update_endpoint_header(self.DEFAULT_ABOUT_ENDPOINT) return super().get_about() - def get_config_info(self) -> dict: + def get_config_info(self) -> Config: self._update_endpoint_header(self.DEFAULT_CONFIG_INFO_ENDPOINT) return super().get_config_info() - def get_language(self, language_id) -> dict: + def get_language(self, language_id) -> Language: self._update_endpoint_header(self.DEFAULT_LANGUAGE_ENDPOINT) return super().get_language(language_id) - def get_languages(self) -> list[dict]: + def get_languages(self) -> list[Language]: self._update_endpoint_header(self.DEFAULT_LANGUAGES_ENDPOINT) return super().get_languages() @@ -372,6 +372,8 @@ def get_submissions( class ATDJudge0ExtraCE(ATD): + """AllThingsDev client for Extra CE flavor.""" + DEFAULT_ENDPOINT: str = "https://judge0-extra-ce.proxy-production.allthingsdev.co" DEFAULT_HOST: str = "Judge0-Extra-CE.allthingsdev.co" HOME_URL: str = ( @@ -401,15 +403,15 @@ def get_about(self) -> dict: self._update_endpoint_header(self.DEFAULT_ABOUT_ENDPOINT) return super().get_about() - def get_config_info(self) -> dict: + def get_config_info(self) -> Config: self._update_endpoint_header(self.DEFAULT_CONFIG_INFO_ENDPOINT) return super().get_config_info() - def get_language(self, language_id) -> dict: + def get_language(self, language_id) -> Language: self._update_endpoint_header(self.DEFAULT_LANGUAGE_ENDPOINT) return super().get_language(language_id) - def get_languages(self) -> list[dict]: + def get_languages(self) -> list[Language]: self._update_endpoint_header(self.DEFAULT_LANGUAGES_ENDPOINT) return super().get_languages() @@ -462,6 +464,8 @@ def __init__(self, endpoint, host_header_value, api_key, **kwargs): class RapidJudge0CE(Rapid): + """RapidAPI client for CE flavor.""" + DEFAULT_ENDPOINT: str = "https://judge0-ce.p.rapidapi.com" DEFAULT_HOST: str = "judge0-ce.p.rapidapi.com" HOME_URL: str = "https://rapidapi.com/judge0-official/api/judge0-ce" @@ -476,6 +480,8 @@ def __init__(self, api_key, **kwargs): class RapidJudge0ExtraCE(Rapid): + """RapidAPI client for Extra CE flavor.""" + DEFAULT_ENDPOINT: str = "https://judge0-extra-ce.p.rapidapi.com" DEFAULT_HOST: str = "judge0-extra-ce.p.rapidapi.com" HOME_URL: str = "https://rapidapi.com/judge0-official/api/judge0-extra-ce" @@ -504,6 +510,8 @@ def __init__(self, endpoint, api_key=None, **kwargs): class SuluJudge0CE(Sulu): + """Sulu client for CE flavor.""" + DEFAULT_ENDPOINT: str = "https://judge0-ce.p.sulu.sh" HOME_URL: str = "https://sparkhub.sulu.sh/apis/judge0/judge0-ce/readme" @@ -516,6 +524,8 @@ def __init__(self, api_key=None, **kwargs): class SuluJudge0ExtraCE(Sulu): + """Sulu client for Extra CE flavor.""" + DEFAULT_ENDPOINT: str = "https://judge0-extra-ce.p.sulu.sh" HOME_URL: str = "https://sparkhub.sulu.sh/apis/judge0/judge0-extra-ce/readme" From 16e1dd0bcafa1ec598564f01be5dcf8579e17388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Thu, 12 Dec 2024 20:03:07 +0100 Subject: [PATCH 017/161] Initial documentation setup. --- docs/Makefile | 20 ++++++++++++++++++++ docs/make.bat | 35 +++++++++++++++++++++++++++++++++++ docs/source/conf.py | 27 +++++++++++++++++++++++++++ docs/source/index.rst | 17 +++++++++++++++++ pyproject.toml | 1 + 5 files changed, 100 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/conf.py create mode 100644 docs/source/index.rst diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..d0c3cbf1 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..747ffb7b --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 00000000..7f7854a9 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,27 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "Judge0 Python SDK" +copyright = "2024, Judge0" +author = "Judge0" +release = "0.1.0" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [] + +templates_path = ["_templates"] +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "alabaster" +html_static_path = ["_static"] diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 00000000..7b56c952 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,17 @@ +.. Judge0 Python SDK documentation master file, created by + sphinx-quickstart on Thu Dec 12 19:59:23 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Judge0 Python SDK documentation +=============================== + +Add your content using ``reStructuredText`` syntax. See the +`reStructuredText `_ +documentation for details. + + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + diff --git a/pyproject.toml b/pyproject.toml index 01fc8e2f..d2d002d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,7 @@ test = [ "pytest-cov==6.0.0", "flake8-docstrings==1.7.0", ] +docs = ["sphinx==7.4.7"] [tool.flake8] docstring-convention = "numpy" From f6295ce4206349583e53d4ee7146705b4341aca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Thu, 12 Dec 2024 20:16:13 +0100 Subject: [PATCH 018/161] Add workflow file for docs. --- .github/workflows/docs.yml | 28 ++++++++++++++++++++++++++++ docs/requirements.txt | 1 + 2 files changed, 29 insertions(+) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/requirements.txt diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..6564b7c9 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,28 @@ +name: "Sphinx: Render docs" + +on: + push: + branches: ["master"] + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Build HTML + uses: ammaraskar/sphinx-action@master + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: html-docs + path: docs/build/html/ + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + if: github.ref == 'refs/heads/main' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: docs/build/html \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..32464913 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +furo==2021.11.16 \ No newline at end of file From 6676d45531bb64f874130982aa8d1ee5a01a5138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Thu, 12 Dec 2024 20:24:45 +0100 Subject: [PATCH 019/161] Add workflow dispatch trigger for docs workflow. --- .github/workflows/docs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6564b7c9..90ce995d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,9 +1,11 @@ name: "Sphinx: Render docs" on: + workflow_dispatch: push: branches: ["master"] + jobs: build: runs-on: ubuntu-latest From c64cfa1a5e46a864b0bac8bf317031addfbe279e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Thu, 12 Dec 2024 20:29:55 +0100 Subject: [PATCH 020/161] Update branch in docs workflow. --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 90ce995d..6ba05364 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,7 +24,7 @@ jobs: path: docs/build/html/ - name: Deploy uses: peaceiris/actions-gh-pages@v3 - if: github.ref == 'refs/heads/main' + if: github.ref == 'refs/heads/master' with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: docs/build/html \ No newline at end of file From 4d12e302a2e3a6592327e7ff2c85e44e38f7c41a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Thu, 12 Dec 2024 21:08:07 +0100 Subject: [PATCH 021/161] Add base url to sphinx conf. --- docs/source/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 7f7854a9..fea2c58b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -23,5 +23,6 @@ # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output +html_baseurl = "https://docs.judge0.com/python" html_theme = "alabaster" html_static_path = ["_static"] From f1762855d69552fb29932d4b0fff9068835dad6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Thu, 12 Dec 2024 21:12:38 +0100 Subject: [PATCH 022/161] Add extension for githubpages. --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index fea2c58b..42c6e3ad 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,7 +14,7 @@ # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = [] +extensions = ["sphinx.ext.githubpages"] templates_path = ["_templates"] exclude_patterns = [] From 43408011b868a0723b01b7d2581916c24ba68f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Thu, 12 Dec 2024 21:27:19 +0100 Subject: [PATCH 023/161] Update html_baseurl. --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 42c6e3ad..7db48bbd 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -23,6 +23,6 @@ # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_baseurl = "https://docs.judge0.com/python" +html_baseurl = "https://docs.judge0.com/python/" html_theme = "alabaster" html_static_path = ["_static"] From 90e39c8de9da757384911ace5a7ec373aa715417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Thu, 12 Dec 2024 21:34:44 +0100 Subject: [PATCH 024/161] Remove base url. Set release to 0.1. --- docs/source/conf.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 7db48bbd..5eda3539 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -9,12 +9,12 @@ project = "Judge0 Python SDK" copyright = "2024, Judge0" author = "Judge0" -release = "0.1.0" +release = "0.1" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = ["sphinx.ext.githubpages"] +extensions = [] templates_path = ["_templates"] exclude_patterns = [] @@ -23,6 +23,5 @@ # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_baseurl = "https://docs.judge0.com/python/" html_theme = "alabaster" html_static_path = ["_static"] From cb37a811c078bf171e00945e073d3e5d07a6a027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Thu, 12 Dec 2024 21:43:41 +0100 Subject: [PATCH 025/161] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 24baa57b..de2703eb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # Judge0 Python SDK -The official Python library for Judge0. +The official Python SDK for Judge0. From dc1fd09d9030053ffe1fcf56d713ff08d66f8cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Thu, 12 Dec 2024 21:45:41 +0100 Subject: [PATCH 026/161] Create CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 128 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..0a27e2e9 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +contact@judge0.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. From a63ed35d2cda3982b5d364160784fa3ba61c190f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Thu, 12 Dec 2024 21:46:03 +0100 Subject: [PATCH 027/161] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d2d002d7..b53813e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ version = "0.0.2dev" description = "The official Python library for Judge0." readme = "README.md" requires-python = ">=3.9" -authors = [{ name = "Judge0", email = "support@judge0.com" }] +authors = [{ name = "Judge0", email = "contact@judge0.com" }] classifiers = [ "Intended Audience :: Developers", From 767cd326790da2ddd2b5ae84c293e60f804066b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Thu, 12 Dec 2024 21:47:03 +0100 Subject: [PATCH 028/161] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..dd84ea78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 0dc5e6f25cab055aa20b791140bbf000a32e779f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Thu, 12 Dec 2024 21:53:47 +0100 Subject: [PATCH 029/161] Create RELEASE_NOTES_TEMPLATE.md --- RELEASE_NOTES_TEMPLATE.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 RELEASE_NOTES_TEMPLATE.md diff --git a/RELEASE_NOTES_TEMPLATE.md b/RELEASE_NOTES_TEMPLATE.md new file mode 100644 index 00000000..1a46e3dc --- /dev/null +++ b/RELEASE_NOTES_TEMPLATE.md @@ -0,0 +1,15 @@ +# vX.Y.Z (YYYY-MM-DD) + +## API Changes + +## New Features + +## Improvements + +## Security Improvements + +## Bug Fixes + +## Security Fixes + +## Other Changes From a906318e7633b88ab9bfa19e410c6f55888f90d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 13 Dec 2024 16:07:47 +0100 Subject: [PATCH 030/161] Update test workflow to run only when code changes. --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 86a0ddd0..38c14e80 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,6 +3,7 @@ name: Test judge0-python on: push: branches: ["master"] + paths: ["src/**", "tests/**"] permissions: contents: read From 5b12ca5e215ddf4ca6f6bad151ee6f3bd449523c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 13 Dec 2024 16:17:10 +0100 Subject: [PATCH 031/161] Use RTD theme. --- docs/source/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 5eda3539..fe56503c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,7 +14,7 @@ # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = [] +extensions = ["sphinx_rtd_theme"] templates_path = ["_templates"] exclude_patterns = [] @@ -23,5 +23,5 @@ # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = "alabaster" +html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] From fd9c33777fe1be580413c34475f0e3adce4b5d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 13 Dec 2024 16:22:27 +0100 Subject: [PATCH 032/161] Update docs workflow with additional steps to install requirements for sphinx (rtd theme). --- .github/workflows/docs.yml | 15 ++++++++++++++- docs/requirements.txt | 4 +++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6ba05364..f76b7603 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -8,20 +8,33 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: write steps: - uses: actions/checkout@v4 with: persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r docs/requirements.txt + - name: Build HTML uses: ammaraskar/sphinx-action@master + - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: html-docs path: docs/build/html/ + - name: Deploy uses: peaceiris/actions-gh-pages@v3 if: github.ref == 'refs/heads/master' diff --git a/docs/requirements.txt b/docs/requirements.txt index 32464913..1e056423 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,3 @@ -furo==2021.11.16 \ No newline at end of file +furo==2021.11.16 +Sphinx==7.4.7 +sphinx-rtd-theme==3.0.2 \ No newline at end of file From 49f8520cd6f1b93c391b3b7a6cf7b0eb1fc520f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 13 Dec 2024 16:24:37 +0100 Subject: [PATCH 033/161] Remove furo from requirements. --- docs/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 1e056423..97bea5f0 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,2 @@ -furo==2021.11.16 Sphinx==7.4.7 sphinx-rtd-theme==3.0.2 \ No newline at end of file From fb2ce64cd26ed695dff7873c519d3467e23d2ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 13 Dec 2024 16:30:19 +0100 Subject: [PATCH 034/161] Fix requirements for docs. --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 97bea5f0..02188c13 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -Sphinx==7.4.7 +sphinx==7.4.7 sphinx-rtd-theme==3.0.2 \ No newline at end of file From 5e3da7f2b7ee0da46f9d265cf239166650b4a1af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 13 Dec 2024 16:38:38 +0100 Subject: [PATCH 035/161] Update docs workflow and requirements. --- .github/workflows/docs.yml | 12 +----------- docs/requirements.txt | 1 - 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f76b7603..1d5909d6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,18 +16,8 @@ jobs: with: persist-credentials: false - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.12' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r docs/requirements.txt - - name: Build HTML - uses: ammaraskar/sphinx-action@master + uses: ammaraskar/sphinx-action@7.0.0 - name: Upload artifacts uses: actions/upload-artifact@v4 diff --git a/docs/requirements.txt b/docs/requirements.txt index 02188c13..3ffe4e33 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1 @@ -sphinx==7.4.7 sphinx-rtd-theme==3.0.2 \ No newline at end of file From 4e43bfad5b96065554123356e1adde179bcf0b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 13 Dec 2024 21:48:43 +0100 Subject: [PATCH 036/161] Initial commit for api and submissions module docs. --- docs/source/api_index.rst | 20 ++++++++++++++++++++ docs/source/conf.py | 25 ++++++++++++++++++++++++- docs/source/index.rst | 8 ++------ 3 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 docs/source/api_index.rst diff --git a/docs/source/api_index.rst b/docs/source/api_index.rst new file mode 100644 index 00000000..7c3641d7 --- /dev/null +++ b/docs/source/api_index.rst @@ -0,0 +1,20 @@ +API +=== + +.. toctree:: + :maxdepth: 2 + :caption: API + +API +--- + +.. automodule:: judge0.api + :members: + :undoc-members: + +Submission +---------- + +.. automodule:: judge0.submission + :members: + :undoc-members: \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index fe56503c..817dc328 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,6 +6,10 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +import os +import sys + project = "Judge0 Python SDK" copyright = "2024, Judge0" author = "Judge0" @@ -14,7 +18,7 @@ # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = ["sphinx_rtd_theme"] +extensions = ["sphinx.ext.autodoc"] templates_path = ["_templates"] exclude_patterns = [] @@ -25,3 +29,22 @@ html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] + +sys.path.insert(0, os.path.abspath("../src/judge0")) # Adjust as needed + +html_theme_options = { + # Toc options + "collapse_navigation": True, + "sticky_navigation": True, + "navigation_depth": 4, + "includehidden": True, + "titles_only": False, +} + +autodoc_default_options = { + "members": True, + "undoc-members": True, + "private-members": False, + "special-members": False, + "inherited-members": False, +} diff --git a/docs/source/index.rst b/docs/source/index.rst index 7b56c952..b61592e3 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,12 +6,8 @@ Judge0 Python SDK documentation =============================== -Add your content using ``reStructuredText`` syntax. See the -`reStructuredText `_ -documentation for details. - - .. toctree:: :maxdepth: 2 - :caption: Contents: + :caption: Contents + api_index \ No newline at end of file From 86fe15c07df9dee85fd5d676be644307a8b93eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 13 Dec 2024 21:54:55 +0100 Subject: [PATCH 037/161] Update lib path in conf for docs. --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 817dc328..b7503af0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -30,7 +30,7 @@ html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] -sys.path.insert(0, os.path.abspath("../src/judge0")) # Adjust as needed +sys.path.insert(0, os.path.abspath("../src/")) # Adjust as needed html_theme_options = { # Toc options From 353916830b5f831185d223c52effa1c5823230bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 13 Dec 2024 21:57:57 +0100 Subject: [PATCH 038/161] Update path to judge0 lib in conf for docs. --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index b7503af0..f6678296 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -30,7 +30,7 @@ html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] -sys.path.insert(0, os.path.abspath("../src/")) # Adjust as needed +sys.path.insert(0, os.path.abspath("../../src/")) # Adjust as needed html_theme_options = { # Toc options From 58a2be793e589aed0836032ab0e75436e3455df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 14 Dec 2024 00:36:06 +0100 Subject: [PATCH 039/161] Move contributing docs to sphinx. --- CONTRIBUTING.md | 25 +------------------------ docs/source/contributing.rst | 28 ++++++++++++++++++++++++++++ docs/source/index.rst | 8 ++------ 3 files changed, 31 insertions(+), 30 deletions(-) create mode 100644 docs/source/contributing.rst diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e85b3388..346d24de 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,26 +1,3 @@ # How to contribute -## Preparing the development setup - -1. Install Python 3.9 - -```bash -sudo add-apt-repository ppa:deadsnakes/ppa -sudo apt update -sudo apt install python3.9 python3.9-venv -``` - -2. Clone the repo, create and activate a new virtual environment - -```bash -cd judge0-python -python3.9 -m venv venv -source venv/bin/activate -``` - -3. Install the library and development dependencies - -```bash -pip install -e .[test] -pre-commit install -``` +See [docs](https://judge0.github.io/judge0-python/contributing.html). diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst new file mode 100644 index 00000000..2a19fb5b --- /dev/null +++ b/docs/source/contributing.rst @@ -0,0 +1,28 @@ +Contributing +============ + +Preparing the development setup +------------------------------- + +1. Install Python 3.9 + +.. code-block:: console + + $ sudo add-apt-repository ppa:deadsnakes/ppa + $ sudo apt update + $ sudo apt install python3.9 python3.9-venv + +2. Clone the repo, create and activate a new virtual environment + +.. code-block:: console + + $ cd judge0-python + $ python3.9 -m venv venv + $ . venv/bin/activate + +3. Install the library and development dependencies + +.. code-block:: console + + $ pip install -e .[test] + $ pre-commit install diff --git a/docs/source/index.rst b/docs/source/index.rst index b61592e3..3ae70c51 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,8 +1,3 @@ -.. Judge0 Python SDK documentation master file, created by - sphinx-quickstart on Thu Dec 12 19:59:23 2024. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - Judge0 Python SDK documentation =============================== @@ -10,4 +5,5 @@ Judge0 Python SDK documentation :maxdepth: 2 :caption: Contents - api_index \ No newline at end of file + api_index + contributing \ No newline at end of file From 026b9b177dc12d6d0455e14586946928d0126519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 14 Dec 2024 01:02:54 +0100 Subject: [PATCH 040/161] Remove tables from docstrings. Render as numpy-based docs using napoleon extension. --- docs/source/api_index.rst | 13 ++++++------- docs/source/conf.py | 8 +++++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/source/api_index.rst b/docs/source/api_index.rst index 7c3641d7..b846e92b 100644 --- a/docs/source/api_index.rst +++ b/docs/source/api_index.rst @@ -1,19 +1,18 @@ API === -.. toctree:: - :maxdepth: 2 - :caption: API +.. autosummary:: + :toctree: generated -API ---- +API Module +---------- .. automodule:: judge0.api :members: :undoc-members: -Submission ----------- +Submission Module +----------------- .. automodule:: judge0.submission :members: diff --git a/docs/source/conf.py b/docs/source/conf.py index f6678296..28ddaaf9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,7 +18,11 @@ # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = ["sphinx.ext.autodoc"] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.autosummary", +] templates_path = ["_templates"] exclude_patterns = [] @@ -48,3 +52,5 @@ "special-members": False, "inherited-members": False, } + +napoleon_google_docstring = False From dbfd71605a3e5de0dc2c1b3bacaa15ad7873c2cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 14 Dec 2024 18:14:57 +0100 Subject: [PATCH 041/161] Add mocked imports for requests and pydantic to avoid installation in docs env. Remove tables from docstrings. --- docs/requirements.txt | 3 ++- docs/source/api_index.rst | 9 ++++++- docs/source/conf.py | 2 ++ docs/source/index.rst | 4 +++ src/judge0/api.py | 52 ++++++++++++++++----------------------- 5 files changed, 37 insertions(+), 33 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 3ffe4e33..db23d3d1 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,2 @@ -sphinx-rtd-theme==3.0.2 \ No newline at end of file +sphinx-rtd-theme==3.0.2 +sphinx-autodoc-typehints==2.3.0 \ No newline at end of file diff --git a/docs/source/api_index.rst b/docs/source/api_index.rst index b846e92b..5495d02e 100644 --- a/docs/source/api_index.rst +++ b/docs/source/api_index.rst @@ -16,4 +16,11 @@ Submission Module .. automodule:: judge0.submission :members: - :undoc-members: \ No newline at end of file + :member-order: groupwise + +Clients Module +----------------- + +.. automodule:: judge0.clients + :members: + :member-order: groupwise diff --git a/docs/source/conf.py b/docs/source/conf.py index 28ddaaf9..e8ac76ae 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -22,6 +22,7 @@ "sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx.ext.autosummary", + "sphinx_autodoc_typehints", ] templates_path = ["_templates"] @@ -52,5 +53,6 @@ "special-members": False, "inherited-members": False, } +autodoc_mock_imports = ["requests", "pydantic"] napoleon_google_docstring = False diff --git a/docs/source/index.rst b/docs/source/index.rst index 3ae70c51..087fd91c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,6 +1,10 @@ Judge0 Python SDK documentation =============================== +.. note:: + + This project is under active development. + .. toctree:: :maxdepth: 2 :caption: Contents diff --git a/src/judge0/api.py b/src/judge0/api.py index fabcd4ff..7b2fb22d 100644 --- a/src/judge0/api.py +++ b/src/judge0/api.py @@ -188,19 +188,25 @@ def wait( def create_submissions_from_test_cases( submissions: Union[Submission, Submissions], test_cases: Optional[Union[TestCaseType, TestCases]] = None, -): +) -> Union[Submission, list[Submission]]: """Create submissions from the (submission, test_case) pairs. - The following table contains the return type based on the types of - `submissions` and `test_cases` arguments: + This function always returns a deep copy so make sure you are using the + returned submission(s). - | submissions | test_cases | returns | - |:------------|:-----------|:------------| - | Submission | TestCase | Submission | - | Submission | TestCases | Submissions | - | Submissions | TestCase | Submissions | - | Submissions | TestCases | Submissions | + Parameters + ---------- + submissions : Submission or Submissions + Base submission(s) that need to be expanded with test cases. + test_cases: TestCaseType or TestCases + Test cases. + Returns + ------- + Submissions or Submissions + A single submission if submissions arguments is of type Submission or + source_code argument is provided, and test_cases argument is of type + TestCase. Otherwise returns a list of submissions. """ if isinstance(submissions, Submission): submissions_list = [submissions] @@ -275,16 +281,6 @@ def async_execute( Aliases: `async_run`. - The following table contains the return type based on the types of - `submissions` (or `source_code`) and `test_cases` arguments: - - | submissions | test_cases | returns | - |:------------|:-----------|:------------| - | Submission | TestCase | Submission | - | Submission | TestCases | Submissions | - | Submissions | TestCase | Submissions | - | Submissions | TestCases | Submissions | - Parameters ---------- client : Client or Flavor, optional @@ -300,7 +296,9 @@ def async_execute( Returns ------- Submission or Submissions - A single submission or a list of submissions. + A single submission if submissions arguments is of type Submission or + source_code argument is provided, and test_cases argument is of type + TestCase. Otherwise returns a list of submissions. Raises ------ @@ -331,16 +329,6 @@ def sync_execute( Aliases: `execute`, `run`, `sync_run`. - The following table contains the return type based on the types of - `submissions` (or `source_code`) and `test_cases` arguments: - - | submissions | test_cases | returns | - |:------------|:-----------|:------------| - | Submission | TestCase | Submission | - | Submission | TestCases | Submissions | - | Submissions | TestCase | Submissions | - | Submissions | TestCases | Submissions | - Parameters ---------- client : Client or Flavor, optional @@ -356,7 +344,9 @@ def sync_execute( Returns ------- Submission or Submissions - A single submission or a list of submissions. + A single submission if submissions arguments is of type Submission or + source_code argument is provided, and test_cases argument is of type + TestCase. Otherwise returns a list of submissions. Raises ------ From 0842184f377c54f31cd1e4bcf4ccd75214a46f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sat, 14 Dec 2024 20:48:05 +0100 Subject: [PATCH 042/161] Add html_show_sphinx --- docs/requirements.txt | 2 +- docs/source/conf.py | 1 + docs/source/index.rst | 6 +----- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index db23d3d1..cf4f60f6 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ sphinx-rtd-theme==3.0.2 -sphinx-autodoc-typehints==2.3.0 \ No newline at end of file +sphinx-autodoc-typehints==2.3.0 diff --git a/docs/source/conf.py b/docs/source/conf.py index e8ac76ae..97bcdf69 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -34,6 +34,7 @@ html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] +html_show_sphinx = False sys.path.insert(0, os.path.abspath("../../src/")) # Adjust as needed diff --git a/docs/source/index.rst b/docs/source/index.rst index 087fd91c..1f189fde 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,13 +1,9 @@ Judge0 Python SDK documentation =============================== -.. note:: - - This project is under active development. - .. toctree:: :maxdepth: 2 :caption: Contents api_index - contributing \ No newline at end of file + contributing From 22d4ae6aaa4022ec722549dda8732fadc2f892c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 14 Dec 2024 21:16:45 +0100 Subject: [PATCH 043/161] Switch to sphinxawesome-theme. --- docs/requirements.txt | 2 +- docs/source/conf.py | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index cf4f60f6..8e76ca0b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx-rtd-theme==3.0.2 +sphinxawesome-theme==5.3.2 sphinx-autodoc-typehints==2.3.0 diff --git a/docs/source/conf.py b/docs/source/conf.py index 97bcdf69..ff7cb8df 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -32,20 +32,11 @@ # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = "sphinx_rtd_theme" -html_static_path = ["_static"] +html_theme = "sphinxawesome_theme" html_show_sphinx = False sys.path.insert(0, os.path.abspath("../../src/")) # Adjust as needed -html_theme_options = { - # Toc options - "collapse_navigation": True, - "sticky_navigation": True, - "navigation_depth": 4, - "includehidden": True, - "titles_only": False, -} autodoc_default_options = { "members": True, From 1b5898f41d57a9884e7feaa3207d0a9bd2b67856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 14 Dec 2024 22:38:56 +0100 Subject: [PATCH 044/161] Initial setup of doc structure. --- docs/source/api/api.rst | 6 ++ docs/source/api/clients.rst | 6 ++ docs/source/api/index.rst | 6 ++ docs/source/api/submission.rst | 6 ++ docs/source/api_index.rst | 26 ----- docs/source/conf.py | 2 + .../{ => contributors_guide}/contributing.rst | 0 docs/source/contributors_guide/index.rst | 5 + .../contributors_guide/release_notes.rst | 4 + docs/source/index.rst | 53 +++++++++- src/judge0/clients.py | 100 +++++++++++------- 11 files changed, 145 insertions(+), 69 deletions(-) create mode 100644 docs/source/api/api.rst create mode 100644 docs/source/api/clients.rst create mode 100644 docs/source/api/index.rst create mode 100644 docs/source/api/submission.rst delete mode 100644 docs/source/api_index.rst rename docs/source/{ => contributors_guide}/contributing.rst (100%) create mode 100644 docs/source/contributors_guide/index.rst create mode 100644 docs/source/contributors_guide/release_notes.rst diff --git a/docs/source/api/api.rst b/docs/source/api/api.rst new file mode 100644 index 00000000..08b5d0e3 --- /dev/null +++ b/docs/source/api/api.rst @@ -0,0 +1,6 @@ +API Module +========== + +.. automodule:: judge0.api + :members: + :undoc-members: diff --git a/docs/source/api/clients.rst b/docs/source/api/clients.rst new file mode 100644 index 00000000..52e7e4e8 --- /dev/null +++ b/docs/source/api/clients.rst @@ -0,0 +1,6 @@ +Clients Module +============== + +.. automodule:: judge0.clients + :members: + :member-order: groupwise diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst new file mode 100644 index 00000000..ae975c4d --- /dev/null +++ b/docs/source/api/index.rst @@ -0,0 +1,6 @@ +.. toctree:: + :maxdepth: 2 + + api + submission + clients \ No newline at end of file diff --git a/docs/source/api/submission.rst b/docs/source/api/submission.rst new file mode 100644 index 00000000..e42a6aa0 --- /dev/null +++ b/docs/source/api/submission.rst @@ -0,0 +1,6 @@ +Submission Module +================= + +.. automodule:: judge0.submission + :members: + :member-order: groupwise diff --git a/docs/source/api_index.rst b/docs/source/api_index.rst deleted file mode 100644 index 5495d02e..00000000 --- a/docs/source/api_index.rst +++ /dev/null @@ -1,26 +0,0 @@ -API -=== - -.. autosummary:: - :toctree: generated - -API Module ----------- - -.. automodule:: judge0.api - :members: - :undoc-members: - -Submission Module ------------------ - -.. automodule:: judge0.submission - :members: - :member-order: groupwise - -Clients Module ------------------ - -.. automodule:: judge0.clients - :members: - :member-order: groupwise diff --git a/docs/source/conf.py b/docs/source/conf.py index ff7cb8df..77420a5a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -29,6 +29,8 @@ exclude_patterns = [] +# add_module_names = False + # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output diff --git a/docs/source/contributing.rst b/docs/source/contributors_guide/contributing.rst similarity index 100% rename from docs/source/contributing.rst rename to docs/source/contributors_guide/contributing.rst diff --git a/docs/source/contributors_guide/index.rst b/docs/source/contributors_guide/index.rst new file mode 100644 index 00000000..312258bb --- /dev/null +++ b/docs/source/contributors_guide/index.rst @@ -0,0 +1,5 @@ +.. toctree:: + :maxdepth: 2 + + contributing + release_notes diff --git a/docs/source/contributors_guide/release_notes.rst b/docs/source/contributors_guide/release_notes.rst new file mode 100644 index 00000000..0b6251f7 --- /dev/null +++ b/docs/source/contributors_guide/release_notes.rst @@ -0,0 +1,4 @@ +How to create a release candidate +================================= + +TODO \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index 1f189fde..5df6c218 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,9 +1,54 @@ +=============================== Judge0 Python SDK documentation =============================== +Getting Started +=============== + +You can run minimal Hello World example in three easy steps: + +1. Install Judge0 Python SDK: + +.. code-block:: bash + + pip install judge0 + +2. Create a minimal script: + +.. code-block:: Python + + import judge0 + + submission = judge.run(source_code="print('Hello Judge0!')") + print(submission.stdout) + +3. Run the script. + +Want to learn more +------------------ + + +To learn what is happening behind the scenes and how to best use Judge0 Python +SDK to facilitate the development of your own product see In Depth guide and +Examples. + +Getting Involved +---------------- + +TODO + +.. toctree:: + :caption: Getting Involved + :glob: + :titlesonly: + :hidden: + + contributors_guide/index + .. toctree:: - :maxdepth: 2 - :caption: Contents + :caption: API + :glob: + :titlesonly: + :hidden: - api_index - contributing + api/index \ No newline at end of file diff --git a/src/judge0/clients.py b/src/judge0/clients.py index 29b1ce79..ff8e989e 100644 --- a/src/judge0/clients.py +++ b/src/judge0/clients.py @@ -1,4 +1,4 @@ -from typing import Optional, Union +from typing import ClassVar, Optional, Union import requests @@ -10,7 +10,7 @@ class Client: - API_KEY_ENV = None + API_KEY_ENV: ClassVar[str] = None def __init__( self, @@ -280,7 +280,7 @@ def get_submissions( class ATD(Client): """Base class for all AllThingsDev clients.""" - API_KEY_ENV = "JUDGE0_ATD_API_KEY" + API_KEY_ENV: ClassVar[str] = "JUDGE0_ATD_API_KEY" def __init__(self, endpoint, host_header_value, api_key, **kwargs): self.api_key = api_key @@ -300,21 +300,31 @@ def _update_endpoint_header(self, header_value): class ATDJudge0CE(ATD): """AllThingsDev client for CE flavor.""" - DEFAULT_ENDPOINT: str = "https://judge0-ce.proxy-production.allthingsdev.co" - DEFAULT_HOST: str = "Judge0-CE.allthingsdev.co" - HOME_URL: str = ( + DEFAULT_ENDPOINT: ClassVar[str] = ( + "https://judge0-ce.proxy-production.allthingsdev.co" + ) + DEFAULT_HOST: ClassVar[str] = "Judge0-CE.allthingsdev.co" + HOME_URL: ClassVar[str] = ( "https://www.allthingsdev.co/apimarketplace/judge0-ce/66b683c8b7b7ad054eb6ff8f" ) - DEFAULT_ABOUT_ENDPOINT: str = "01fc1c98-ceee-4f49-8614-f2214703e25f" - DEFAULT_CONFIG_INFO_ENDPOINT: str = "b7aab45d-5eb0-4519-b092-89e5af4fc4f3" - DEFAULT_LANGUAGE_ENDPOINT: str = "a50ae6b1-23c1-40eb-b34c-88bc8cf2c764" - DEFAULT_LANGUAGES_ENDPOINT: str = "03824deb-bd18-4456-8849-69d78e1383cc" - DEFAULT_STATUSES_ENDPOINT: str = "c37b603f-6f99-4e31-a361-7154c734f19b" - DEFAULT_CREATE_SUBMISSION_ENDPOINT: str = "6e65686d-40b0-4bf7-a12f-1f6d033c4473" - DEFAULT_GET_SUBMISSION_ENDPOINT: str = "b7032b8b-86da-40b4-b9d3-b1f5e2b4ee1e" - DEFAULT_CREATE_SUBMISSIONS_ENDPOINT: str = "402b857c-1126-4450-bfd8-22e1f2cbff2f" - DEFAULT_GET_SUBMISSIONS_ENDPOINT: str = "e42f2a26-5b02-472a-80c9-61c4bdae32ec" + DEFAULT_ABOUT_ENDPOINT: ClassVar[str] = "01fc1c98-ceee-4f49-8614-f2214703e25f" + DEFAULT_CONFIG_INFO_ENDPOINT: ClassVar[str] = "b7aab45d-5eb0-4519-b092-89e5af4fc4f3" + DEFAULT_LANGUAGE_ENDPOINT: ClassVar[str] = "a50ae6b1-23c1-40eb-b34c-88bc8cf2c764" + DEFAULT_LANGUAGES_ENDPOINT: ClassVar[str] = "03824deb-bd18-4456-8849-69d78e1383cc" + DEFAULT_STATUSES_ENDPOINT: ClassVar[str] = "c37b603f-6f99-4e31-a361-7154c734f19b" + DEFAULT_CREATE_SUBMISSION_ENDPOINT: ClassVar[str] = ( + "6e65686d-40b0-4bf7-a12f-1f6d033c4473" + ) + DEFAULT_GET_SUBMISSION_ENDPOINT: ClassVar[str] = ( + "b7032b8b-86da-40b4-b9d3-b1f5e2b4ee1e" + ) + DEFAULT_CREATE_SUBMISSIONS_ENDPOINT: ClassVar[str] = ( + "402b857c-1126-4450-bfd8-22e1f2cbff2f" + ) + DEFAULT_GET_SUBMISSIONS_ENDPOINT: ClassVar[str] = ( + "e42f2a26-5b02-472a-80c9-61c4bdae32ec" + ) def __init__(self, api_key, **kwargs): super().__init__( @@ -374,22 +384,32 @@ def get_submissions( class ATDJudge0ExtraCE(ATD): """AllThingsDev client for Extra CE flavor.""" - DEFAULT_ENDPOINT: str = "https://judge0-extra-ce.proxy-production.allthingsdev.co" - DEFAULT_HOST: str = "Judge0-Extra-CE.allthingsdev.co" - HOME_URL: str = ( + DEFAULT_ENDPOINT: ClassVar[str] = ( + "https://judge0-extra-ce.proxy-production.allthingsdev.co" + ) + DEFAULT_HOST: ClassVar[str] = "Judge0-Extra-CE.allthingsdev.co" + HOME_URL: ClassVar[str] = ( "https://www.allthingsdev.co/apimarketplace/judge0-extra-ce/" "66b68838b7b7ad054eb70690" ) - DEFAULT_ABOUT_ENDPOINT: str = "1fd631a1-be6a-47d6-bf4c-987e357e3096" - DEFAULT_CONFIG_INFO_ENDPOINT: str = "46e05354-2a43-436a-9458-5d111456f0ff" - DEFAULT_LANGUAGE_ENDPOINT: str = "10465a84-2a2c-4213-845f-45e3c04a5867" - DEFAULT_LANGUAGES_ENDPOINT: str = "774ecece-1200-41f7-a992-38f186c90803" - DEFAULT_STATUSES_ENDPOINT: str = "a2843b3c-673d-4966-9a14-2e7d76dcd0cb" - DEFAULT_CREATE_SUBMISSION_ENDPOINT: str = "be2d195e-dd58-4770-9f3c-d6c0fbc2b6e5" - DEFAULT_GET_SUBMISSION_ENDPOINT: str = "c3a457cd-37a6-4106-97a8-9e60a223abbc" - DEFAULT_CREATE_SUBMISSIONS_ENDPOINT: str = "c64df5d3-edfd-4b08-8687-561af2f80d2f" - DEFAULT_GET_SUBMISSIONS_ENDPOINT: str = "5d173718-8e6a-4cf5-9d8c-db5e6386d037" + DEFAULT_ABOUT_ENDPOINT: ClassVar[str] = "1fd631a1-be6a-47d6-bf4c-987e357e3096" + DEFAULT_CONFIG_INFO_ENDPOINT: ClassVar[str] = "46e05354-2a43-436a-9458-5d111456f0ff" + DEFAULT_LANGUAGE_ENDPOINT: ClassVar[str] = "10465a84-2a2c-4213-845f-45e3c04a5867" + DEFAULT_LANGUAGES_ENDPOINT: ClassVar[str] = "774ecece-1200-41f7-a992-38f186c90803" + DEFAULT_STATUSES_ENDPOINT: ClassVar[str] = "a2843b3c-673d-4966-9a14-2e7d76dcd0cb" + DEFAULT_CREATE_SUBMISSION_ENDPOINT: ClassVar[str] = ( + "be2d195e-dd58-4770-9f3c-d6c0fbc2b6e5" + ) + DEFAULT_GET_SUBMISSION_ENDPOINT: ClassVar[str] = ( + "c3a457cd-37a6-4106-97a8-9e60a223abbc" + ) + DEFAULT_CREATE_SUBMISSIONS_ENDPOINT: ClassVar[str] = ( + "c64df5d3-edfd-4b08-8687-561af2f80d2f" + ) + DEFAULT_GET_SUBMISSIONS_ENDPOINT: ClassVar[str] = ( + "5d173718-8e6a-4cf5-9d8c-db5e6386d037" + ) def __init__(self, api_key, **kwargs): super().__init__( @@ -449,7 +469,7 @@ def get_submissions( class Rapid(Client): """Base class for all RapidAPI clients.""" - API_KEY_ENV = "JUDGE0_RAPID_API_KEY" + API_KEY_ENV: ClassVar[str] = "JUDGE0_RAPID_API_KEY" def __init__(self, endpoint, host_header_value, api_key, **kwargs): self.api_key = api_key @@ -466,9 +486,9 @@ def __init__(self, endpoint, host_header_value, api_key, **kwargs): class RapidJudge0CE(Rapid): """RapidAPI client for CE flavor.""" - DEFAULT_ENDPOINT: str = "https://judge0-ce.p.rapidapi.com" - DEFAULT_HOST: str = "judge0-ce.p.rapidapi.com" - HOME_URL: str = "https://rapidapi.com/judge0-official/api/judge0-ce" + DEFAULT_ENDPOINT: ClassVar[str] = "https://judge0-ce.p.rapidapi.com" + DEFAULT_HOST: ClassVar[str] = "judge0-ce.p.rapidapi.com" + HOME_URL: ClassVar[str] = "https://rapidapi.com/judge0-official/api/judge0-ce" def __init__(self, api_key, **kwargs): super().__init__( @@ -482,9 +502,9 @@ def __init__(self, api_key, **kwargs): class RapidJudge0ExtraCE(Rapid): """RapidAPI client for Extra CE flavor.""" - DEFAULT_ENDPOINT: str = "https://judge0-extra-ce.p.rapidapi.com" - DEFAULT_HOST: str = "judge0-extra-ce.p.rapidapi.com" - HOME_URL: str = "https://rapidapi.com/judge0-official/api/judge0-extra-ce" + DEFAULT_ENDPOINT: ClassVar[str] = "https://judge0-extra-ce.p.rapidapi.com" + DEFAULT_HOST: ClassVar[str] = "judge0-extra-ce.p.rapidapi.com" + HOME_URL: ClassVar[str] = "https://rapidapi.com/judge0-official/api/judge0-extra-ce" def __init__(self, api_key, **kwargs): super().__init__( @@ -498,7 +518,7 @@ def __init__(self, api_key, **kwargs): class Sulu(Client): """Base class for all Sulu clients.""" - API_KEY_ENV = "JUDGE0_SULU_API_KEY" + API_KEY_ENV: ClassVar[str] = "JUDGE0_SULU_API_KEY" def __init__(self, endpoint, api_key=None, **kwargs): self.api_key = api_key @@ -512,8 +532,8 @@ def __init__(self, endpoint, api_key=None, **kwargs): class SuluJudge0CE(Sulu): """Sulu client for CE flavor.""" - DEFAULT_ENDPOINT: str = "https://judge0-ce.p.sulu.sh" - HOME_URL: str = "https://sparkhub.sulu.sh/apis/judge0/judge0-ce/readme" + DEFAULT_ENDPOINT: ClassVar[str] = "https://judge0-ce.p.sulu.sh" + HOME_URL: ClassVar[str] = "https://sparkhub.sulu.sh/apis/judge0/judge0-ce/readme" def __init__(self, api_key=None, **kwargs): super().__init__( @@ -526,8 +546,10 @@ def __init__(self, api_key=None, **kwargs): class SuluJudge0ExtraCE(Sulu): """Sulu client for Extra CE flavor.""" - DEFAULT_ENDPOINT: str = "https://judge0-extra-ce.p.sulu.sh" - HOME_URL: str = "https://sparkhub.sulu.sh/apis/judge0/judge0-extra-ce/readme" + DEFAULT_ENDPOINT: ClassVar[str] = "https://judge0-extra-ce.p.sulu.sh" + HOME_URL: ClassVar[str] = ( + "https://sparkhub.sulu.sh/apis/judge0/judge0-extra-ce/readme" + ) def __init__(self, api_key=None, **kwargs): super().__init__(self.DEFAULT_ENDPOINT, api_key, **kwargs) From e3270b0189e9247c2d7f075faa57503ac34880eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Mon, 16 Dec 2024 18:22:32 +0100 Subject: [PATCH 045/161] Update docs for api module. --- docs/source/api/index.rst | 3 +- docs/source/api/types.rst | 6 +++ docs/source/index.rst | 8 ++-- src/judge0/api.py | 85 ++++++++++++++++++++++++++++++--------- 4 files changed, 77 insertions(+), 25 deletions(-) create mode 100644 docs/source/api/types.rst diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst index ae975c4d..eb4ed678 100644 --- a/docs/source/api/index.rst +++ b/docs/source/api/index.rst @@ -3,4 +3,5 @@ api submission - clients \ No newline at end of file + clients + types \ No newline at end of file diff --git a/docs/source/api/types.rst b/docs/source/api/types.rst new file mode 100644 index 00000000..2b415b39 --- /dev/null +++ b/docs/source/api/types.rst @@ -0,0 +1,6 @@ +Types Module +============ + +.. automodule:: judge0.base_types + :members: + :undoc-members: diff --git a/docs/source/index.rst b/docs/source/index.rst index 5df6c218..6c202aad 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -38,17 +38,17 @@ Getting Involved TODO .. toctree:: - :caption: Getting Involved + :caption: API :glob: :titlesonly: :hidden: - contributors_guide/index + api/index .. toctree:: - :caption: API + :caption: Getting Involved :glob: :titlesonly: :hidden: - api/index \ No newline at end of file + contributors_guide/index \ No newline at end of file diff --git a/src/judge0/api.py b/src/judge0/api.py index 7b2fb22d..e6d60e2a 100644 --- a/src/judge0/api.py +++ b/src/judge0/api.py @@ -9,6 +9,18 @@ def get_client(flavor: Flavor = Flavor.CE) -> Client: + """Resolve client from API keys from environment or default to preview client. + + Parameters + ---------- + flavor : Flavor + Flavor of Judge0 Client. + + Returns + ------- + Client + An object of base type Client and the specified flavor. + """ from . import _get_implicit_client if isinstance(flavor, Flavor): @@ -26,15 +38,32 @@ def _resolve_client( ) -> Client: """Resolve a client from flavor or submission(s) arguments. + Parameters + ---------- + client : Client or Flavor, optional + A Client object or flavor of client. Returns the client if not None. + submissions: Submission or Submissions, optional + Submission(s) used to determine the suitable client. + + Returns + ------- + Client + An object of base type Client. + Raises ------ ClientResolutionError - Raised if client resolution fails. + If there is no implemented client that supports all the languages specified + in the submissions. """ # User explicitly passed a client. if isinstance(client, Client): return client + # NOTE: At the moment, we do not support the option to check if explicit + # flavor of a client supports the submissions, i.e. submissions argument is + # ignored if flavor argument is provided. + if isinstance(client, Flavor): return get_client(client) @@ -42,7 +71,7 @@ def _resolve_client( raise ValueError("Client cannot be determined from empty submissions.") # client is None and we have to determine a flavor of the client from the - # submissions and the languages. + # the submission's languages. if isinstance(submissions, Submission): submissions = [submissions] @@ -65,18 +94,17 @@ def _resolve_client( def create_submissions( *, - client: Optional[Client] = None, + client: Optional[Union[Client, Flavor]] = None, submissions: Optional[Union[Submission, Submissions]] = None, ) -> Union[Submission, Submissions]: - """Create submissions to a client. + """Universal function for creating submissions to the client. Parameters ---------- - client : Client, optional - A Client where submissions should be created. If None, will try to - be automatically resolved. - submissions: Submission, Submissions - A submission or submissions to create. + client : Client or Flavor, optional + A client or client flavor where submissions should be created. + submissions: Submission or Submissions, optional + Submission(s) to create. Raises ------ @@ -102,19 +130,20 @@ def create_submissions( def get_submissions( *, - client: Optional[Client] = None, + client: Optional[Union[Client, Flavor]] = None, submissions: Optional[Union[Submission, Submissions]] = None, fields: Optional[Union[str, Iterable[str]]] = None, ) -> Union[Submission, Submissions]: - """Create submissions to a client. + """Get submission (status) from a client. Parameters ---------- - client : Client, optional - A Client where submissions should be created. If None, will try to - be automatically resolved. - submissions: Submission, Submissions - A submission or submissions to create. + client : Client or Flavor, optional + A client or client flavor where submissions should be checked. + submissions : Submission or Submissions, optional + Submission(s) to update. + fields : str or sequence of str, optional + Submission attributes that need to be updated. Defaults to all attributes. Raises ------ @@ -144,10 +173,26 @@ def get_submissions( def wait( *, - client: Optional[Client] = None, + client: Optional[Union[Client, Flavor]] = None, submissions: Optional[Union[Submission, Submissions]] = None, retry_strategy: Optional[RetryStrategy] = None, ) -> Union[Submission, Submissions]: + """Wait for all the submissions to finish. + + Parameters + ---------- + client : Client or Flavor, optional + A client or client flavor where submissions should be checked. + submissions : Submission or Submissions + Submission(s) to wait for. + retry_strategy : RetryStrategy, optional + A retry strategy. + + Raises + ------ + ClientResolutionError + Raised if client resolution fails. + """ client = _resolve_client(client, submissions) if retry_strategy is None: @@ -189,9 +234,9 @@ def create_submissions_from_test_cases( submissions: Union[Submission, Submissions], test_cases: Optional[Union[TestCaseType, TestCases]] = None, ) -> Union[Submission, list[Submission]]: - """Create submissions from the (submission, test_case) pairs. + """Create submissions from the submission and test case pairs. - This function always returns a deep copy so make sure you are using the + Function always returns a deep copy so make sure you are using the returned submission(s). Parameters @@ -335,7 +380,7 @@ def sync_execute( A client where submissions should be created. If None, will try to be resolved. submissions : Submission or Submissions, optional - Submission or submissions for execution. + Submission(s) for execution. source_code: str, optional A source code of a program. test_cases: TestCaseType or TestCases, optional From dd1ae42d74a1a06611bf6d4b4c34f891d96eff9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 17 Dec 2024 20:27:43 +0100 Subject: [PATCH 046/161] Fix language change after run. (#11) * Fix language change after run. * Add unit test. --- src/judge0/submission.py | 1 + tests/test_submission.py | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/judge0/submission.py b/src/judge0/submission.py index 069733c5..8e5d1cb9 100644 --- a/src/judge0/submission.py +++ b/src/judge0/submission.py @@ -271,6 +271,7 @@ def pre_execution_copy(self) -> "Submission": new_submission = Submission() for attr in REQUEST_FIELDS: setattr(new_submission, attr, copy.deepcopy(getattr(self, attr))) + new_submission.language = self.language return new_submission def __iter__(self): diff --git a/tests/test_submission.py b/tests/test_submission.py index 98903ed0..fb1bf737 100644 --- a/tests/test_submission.py +++ b/tests/test_submission.py @@ -1,4 +1,5 @@ -from judge0 import Status, Submission, wait +from judge0 import run, Status, Submission, wait +from judge0.base_types import LanguageAlias def test_from_json(): @@ -71,3 +72,23 @@ def test_is_done(request): wait(client=client, submissions=submission) assert submission.is_done() + + +def test_language_before_and_after_execution(request): + client = request.getfixturevalue("judge0_ce_client") + code = """\ + public class Main { + public static void main(String[] args) { + System.out.println("Hello World"); + } + } + """ + + submission = Submission( + source_code=code, + language=LanguageAlias.JAVA, + ) + + assert submission.language == LanguageAlias.JAVA + submission = run(client=client, submissions=submission) + assert submission.language == LanguageAlias.JAVA From 2861d3335fd7d6d9f4632ed1fb9b27e52a46ff37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 17 Dec 2024 21:23:44 +0100 Subject: [PATCH 047/161] Add tests for TestCase.from_record method. Switch the TestCase.from_record method from static to class method. --- src/judge0/base_types.py | 14 +++++++---- tests/test_api_test_cases.py | 47 +++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/judge0/base_types.py b/src/judge0/base_types.py index 48480e8b..125dc540 100644 --- a/src/judge0/base_types.py +++ b/src/judge0/base_types.py @@ -1,3 +1,5 @@ +import copy + from dataclasses import dataclass from enum import IntEnum from typing import Optional, Protocol, runtime_checkable, Sequence, Union @@ -15,8 +17,8 @@ class TestCase: input: Optional[str] = None expected_output: Optional[str] = None - @staticmethod - def from_record(test_case: Optional[TestCaseType] = None) -> "TestCase": + @classmethod + def from_record(cls, test_case: TestCaseType) -> "TestCase": """Create a TestCase from built-in types.""" if isinstance(test_case, (tuple, list)): test_case = { @@ -24,12 +26,14 @@ def from_record(test_case: Optional[TestCaseType] = None) -> "TestCase": for field, value in zip(("input", "expected_output"), test_case) } if isinstance(test_case, dict): - return TestCase( + return cls( input=test_case.get("input", None), expected_output=test_case.get("expected_output", None), ) - if isinstance(test_case, TestCase) or test_case is None: - return test_case + if isinstance(test_case, cls): + return copy.deepcopy(test_case) + if test_case is None: + return cls() raise ValueError( f"Cannot create TestCase object from object of type {type(test_case)}." ) diff --git a/tests/test_api_test_cases.py b/tests/test_api_test_cases.py index f3952795..82ec8704 100644 --- a/tests/test_api_test_cases.py +++ b/tests/test_api_test_cases.py @@ -6,6 +6,51 @@ from judge0.api import create_submissions_from_test_cases +@pytest.mark.parametrize( + "test_case,expected_output", + [ + [ + TestCase(input="input_1", expected_output="output_1"), + TestCase(input="input_1", expected_output="output_1"), + ], + [ + tuple([]), + TestCase(input=None, expected_output=None), + ], + [ + ("input_tuple",), + TestCase(input="input_tuple", expected_output=None), + ], + [ + ("input_tuple", "output_tuple"), + TestCase(input="input_tuple", expected_output="output_tuple"), + ], + [ + [], + TestCase(input=None, expected_output=None), + ], + [ + ["input_list"], + TestCase(input="input_list", expected_output=None), + ], + [ + ["input_list", "output_list"], + TestCase(input="input_list", expected_output="output_list"), + ], + [ + {"input": "input_dict", "expected_output": "output_dict"}, + TestCase(input="input_dict", expected_output="output_dict"), + ], + [ + None, + TestCase(), + ], + ], +) +def test_test_case_from_record(test_case, expected_output): + assert TestCase.from_record(test_case) == expected_output + + @pytest.mark.parametrize( "submissions,test_cases,expected_type", [ @@ -19,7 +64,7 @@ def test_create_submissions_from_test_cases_return_type( submissions, test_cases, expected_type ): output = create_submissions_from_test_cases(submissions, test_cases) - assert type(output) == expected_type + assert type(output) is expected_type @pytest.mark.parametrize( From e8b05b2a79b2616dc63ba69b22e16940148b80e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 17 Dec 2024 21:51:42 +0100 Subject: [PATCH 048/161] Make test cases work with all possible variations. --- src/judge0/api.py | 27 ++++++++--- tests/test_api_test_cases.py | 90 +++++++++++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 7 deletions(-) diff --git a/src/judge0/api.py b/src/judge0/api.py index e6d60e2a..e254dd9f 100644 --- a/src/judge0/api.py +++ b/src/judge0/api.py @@ -260,10 +260,27 @@ def create_submissions_from_test_cases( if isinstance(test_cases, TestCase) or test_cases is None: test_cases_list = [test_cases] + multiple_test_cases = False else: - test_cases_list = test_cases - - test_cases_list = [TestCase.from_record(tc) for tc in test_cases_list] + try: + # Let's assume that we are dealing with multiple test_cases that + # can be created from test_cases argument. If this fails, i.e. + # raises a ValueError, we know we are dealing with a test_cases=dict, + # or test_cases=["in", "out"], or test_cases=tuple("in", "out"). + test_cases_list = [TestCase.from_record(tc) for tc in test_cases] + + # It is possible to send test_cases={}, or test_cases=[], or + # test_cases=tuple([]). In this case, we are treating that as None. + if len(test_cases) > 0: + multiple_test_cases = True + else: + multiple_test_cases = False + test_cases_list = [None] + except ValueError: + test_cases_list = [test_cases] + multiple_test_cases = False + + test_cases_list = [TestCase.from_record(test_case=tc) for tc in test_cases_list] all_submissions = [] for submission in submissions_list: @@ -274,9 +291,7 @@ def create_submissions_from_test_cases( submission_copy.expected_output = test_case.expected_output all_submissions.append(submission_copy) - if isinstance(submissions, Submission) and ( - isinstance(test_cases, TestCase) or test_cases is None - ): + if isinstance(submissions, Submission) and (not multiple_test_cases): return all_submissions[0] else: return all_submissions diff --git a/tests/test_api_test_cases.py b/tests/test_api_test_cases.py index 82ec8704..0dcb1294 100644 --- a/tests/test_api_test_cases.py +++ b/tests/test_api_test_cases.py @@ -1,4 +1,4 @@ -"""Separate file containg tests related to test case functionality.""" +"""Separate file containing tests related to test case functionality.""" import judge0 import pytest @@ -67,6 +67,94 @@ def test_create_submissions_from_test_cases_return_type( assert type(output) is expected_type +class TestCreateSubmissionsFromTestCases: + @pytest.mark.parametrize( + "test_case,stdin,expected_output", + [ + [TestCase(), None, None], + [[], None, None], + [{}, None, None], + [tuple([]), None, None], + ], + ) + def test_empty_test_case(self, test_case, stdin, expected_output): + submission = create_submissions_from_test_cases( + Submission(), test_cases=test_case + ) + + assert ( + submission.stdin == stdin and submission.expected_output == expected_output + ) + + @pytest.mark.parametrize( + "test_case,stdin,expected_output", + [ + [TestCase(), None, None], + [TestCase(input="input"), "input", None], + [TestCase(expected_output="output"), None, "output"], + [["input_list"], "input_list", None], + [["input_list", "output_list"], "input_list", "output_list"], + [{"input": "input_dict"}, "input_dict", None], + [ + {"input": "input_dict", "expected_output": "output_dict"}, + "input_dict", + "output_dict", + ], + [("input_tuple",), "input_tuple", None], + [("input_tuple", "output_tuple"), "input_tuple", "output_tuple"], + ], + ) + def test_single_test_case(self, test_case, stdin, expected_output): + submission = create_submissions_from_test_cases( + Submission(), test_cases=test_case + ) + + assert ( + submission.stdin == stdin and submission.expected_output == expected_output + ) + + @pytest.mark.parametrize( + "test_cases,stdin,expected_output", + [ + [[TestCase()], None, None], + [[TestCase(input="input")], "input", None], + [[TestCase(expected_output="output")], None, "output"], + [(["input_list"],), "input_list", None], + [(["input_list", "output_list"],), "input_list", "output_list"], + [({"input": "input_dict"},), "input_dict", None], + [ + ({"input": "input_dict", "expected_output": "output_dict"},), + "input_dict", + "output_dict", + ], + [ + [ + ("input_tuple",), + ], + "input_tuple", + None, + ], + [ + [ + ("input_tuple", "output_tuple"), + ], + "input_tuple", + "output_tuple", + ], + ], + ) + def test_single_test_case_in_iterable(self, test_cases, stdin, expected_output): + submissions = create_submissions_from_test_cases( + Submission(), test_cases=test_cases + ) + + for submission in submissions: + assert ( + submission.stdin == stdin + and submission.expected_output == expected_output + ) + + @pytest.mark.parametrize( "source_code_or_submissions,test_cases,expected_status", [ From 5a2bd1fe38a6fe493c2a140e5ede95bad9a24bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 17 Dec 2024 22:13:57 +0100 Subject: [PATCH 049/161] Fix filesystem example. --- examples/0005_filesystem.py | 16 ++++++++-------- src/judge0/submission.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/0005_filesystem.py b/examples/0005_filesystem.py index c75a1b46..dc79eb6a 100644 --- a/examples/0005_filesystem.py +++ b/examples/0005_filesystem.py @@ -3,7 +3,7 @@ print("Subexample 1") result = judge0.run(source_code="print('hello, world')") -fs = Filesystem(result.post_execution_filesystem) +fs = Filesystem(content=result.post_execution_filesystem) for f in fs: print(f.name) print(f) @@ -11,19 +11,19 @@ print("Subexample 2") -fs = Filesystem(File("my_file.txt", "hello, world")) +fs = Filesystem(content=File(name="my_file.txt", content="hello, world")) result = judge0.run( source_code="print(open('my_file.txt').read())", additional_files=fs ) print(result.stdout) -for f in Filesystem(result.post_execution_filesystem): +for f in Filesystem(content=result.post_execution_filesystem): print(f.name) print(f) print() print("Subexample 3") -fs = Filesystem(File("my_file.txt", "hello, world")) +fs = Filesystem(content=File(name="my_file.txt", content="hello, world")) result = judge0.run( source_code="print(open('my_file.txt').read())", additional_files=fs ) @@ -35,14 +35,14 @@ print("Subexample 4") fs = Filesystem( - [ - File("my_file.txt", "hello, world"), - File("./dir1/dir2/dir3/my_file2.txt", "hello, world2"), + content=[ + File(name="my_file.txt", content="hello, world"), + File(name="./dir1/dir2/dir3/my_file2.txt", content="hello, world2"), ] ) result = judge0.run(source_code="find .", additional_files=fs, language=46) print(result.stdout) -for f in Filesystem(result.post_execution_filesystem): +for f in Filesystem(content=result.post_execution_filesystem): print(f.name) print(f) print() diff --git a/src/judge0/submission.py b/src/judge0/submission.py index 8e5d1cb9..b9d474cb 100644 --- a/src/judge0/submission.py +++ b/src/judge0/submission.py @@ -131,7 +131,7 @@ class Submission(BaseModel): default=LanguageAlias.PYTHON, repr=True, ) - additional_files: Optional[str] = Field(default=None, repr=True) + additional_files: Optional[Union[str, Filesystem]] = Field(default=None, repr=True) compiler_options: Optional[str] = Field(default=None, repr=True) command_line_arguments: Optional[str] = Field(default=None, repr=True) stdin: Optional[str] = Field(default=None, repr=True) From d4b3a2d05a84d2266e8d3c6014de0a6fbba865b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 17 Dec 2024 22:19:10 +0100 Subject: [PATCH 050/161] Return None in from_record if None is passed. --- src/judge0/base_types.py | 6 ++++-- tests/test_api_test_cases.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/judge0/base_types.py b/src/judge0/base_types.py index 125dc540..05a7a646 100644 --- a/src/judge0/base_types.py +++ b/src/judge0/base_types.py @@ -18,7 +18,9 @@ class TestCase: expected_output: Optional[str] = None @classmethod - def from_record(cls, test_case: TestCaseType) -> "TestCase": + def from_record( + cls, test_case: Union[TestCaseType, None] + ) -> Union["TestCase", None]: """Create a TestCase from built-in types.""" if isinstance(test_case, (tuple, list)): test_case = { @@ -33,7 +35,7 @@ def from_record(cls, test_case: TestCaseType) -> "TestCase": if isinstance(test_case, cls): return copy.deepcopy(test_case) if test_case is None: - return cls() + return None raise ValueError( f"Cannot create TestCase object from object of type {type(test_case)}." ) diff --git a/tests/test_api_test_cases.py b/tests/test_api_test_cases.py index 0dcb1294..0d08f5f8 100644 --- a/tests/test_api_test_cases.py +++ b/tests/test_api_test_cases.py @@ -43,7 +43,7 @@ ], [ None, - TestCase(), + None, ], ], ) From f63c19a89b8f35d3fb52bbb4200da1b42b15bf6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 17 Dec 2024 22:20:53 +0100 Subject: [PATCH 051/161] Update pyproject.toml to prepare release candidate 0.0.2. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b53813e8..97193261 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.0.2dev" +version = "0.0.2rc1" description = "The official Python library for Judge0." readme = "README.md" requires-python = ">=3.9" From fd00c399726f11b576fb89323e00a6edefbeee2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 17 Dec 2024 22:23:11 +0100 Subject: [PATCH 052/161] Set version to 0.0.2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 97193261..3568054f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.0.2rc1" +version = "0.0.2" description = "The official Python library for Judge0." readme = "README.md" requires-python = ">=3.9" From 4f054cabc1a148d75cbc1102456d9df86a4c0c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 20 Dec 2024 18:03:03 +0100 Subject: [PATCH 053/161] Change the title of release notes doc. --- docs/source/contributors_guide/release_notes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/contributors_guide/release_notes.rst b/docs/source/contributors_guide/release_notes.rst index 0b6251f7..ec66b1c6 100644 --- a/docs/source/contributors_guide/release_notes.rst +++ b/docs/source/contributors_guide/release_notes.rst @@ -1,4 +1,4 @@ -How to create a release candidate -================================= +How to create a release +======================= TODO \ No newline at end of file From 11a4f784a80070a3b1e120c854c2f3be93084acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 20 Dec 2024 22:57:15 +0100 Subject: [PATCH 054/161] Initial commit of working docs versioning. --- .github/workflows/docs.yml | 4 +++- docs/requirements.txt | 1 + docs/source/_templates/versioning.html | 8 ++++++++ docs/source/conf.py | 27 ++++++++++++++++++++++---- docs/source/index.rst | 2 +- 5 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 docs/source/_templates/versioning.html diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1d5909d6..efd3954b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,6 +18,8 @@ jobs: - name: Build HTML uses: ammaraskar/sphinx-action@7.0.0 + with: + build-command: "sphinx-multiversion source build/html" - name: Upload artifacts uses: actions/upload-artifact@v4 @@ -30,4 +32,4 @@ jobs: if: github.ref == 'refs/heads/master' with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: docs/build/html \ No newline at end of file + publish_dir: docs/build/html/master \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt index 8e76ca0b..ae1043dd 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ sphinxawesome-theme==5.3.2 sphinx-autodoc-typehints==2.3.0 +sphinx-multiversion==0.2.4 \ No newline at end of file diff --git a/docs/source/_templates/versioning.html b/docs/source/_templates/versioning.html new file mode 100644 index 00000000..ef74ed44 --- /dev/null +++ b/docs/source/_templates/versioning.html @@ -0,0 +1,8 @@ +{% if versions %} +

{{ _('Versions') }}

+ +{% endif %} \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 77420a5a..9312dcda 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -13,7 +13,7 @@ project = "Judge0 Python SDK" copyright = "2024, Judge0" author = "Judge0" -release = "0.1" +release = "" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration @@ -23,19 +23,24 @@ "sphinx.ext.napoleon", "sphinx.ext.autosummary", "sphinx_autodoc_typehints", + "sphinx_multiversion", ] templates_path = ["_templates"] exclude_patterns = [] - -# add_module_names = False - # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "sphinxawesome_theme" html_show_sphinx = False +html_sidebars = { + "**": [ + "sidebar_main_nav_links.html", + "sidebar_toc.html", + "versioning.html", + ], +} sys.path.insert(0, os.path.abspath("../../src/")) # Adjust as needed @@ -50,3 +55,17 @@ autodoc_mock_imports = ["requests", "pydantic"] napoleon_google_docstring = False + +# Whitelist pattern for tags (set to None to ignore all tags) +smv_tag_whitelist = r"^.*$" +# Whitelist pattern for branches (set to None to ignore all branches) +smv_branch_whitelist = r"^master$" +# Whitelist pattern for remotes (set to None to use local branches only) +smv_remote_whitelist = None +# Pattern for released versions +smv_released_pattern = "" # r"^tags/.*$" +# Format for versioned output directories inside the build directory +smv_outputdir_format = "{ref.name}" +# Determines whether remote or local git branches/tags are preferred if their +# output dirs conflict +smv_prefer_remote_refs = False diff --git a/docs/source/index.rst b/docs/source/index.rst index 6c202aad..b8ebcd9f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,7 +15,7 @@ You can run minimal Hello World example in three easy steps: 2. Create a minimal script: -.. code-block:: Python +.. code-block:: python import judge0 From 6ae246f7744160e45e9cbd2dd7301d9e64894d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 20 Dec 2024 23:30:20 +0100 Subject: [PATCH 055/161] Update docs.yml workflow. --- .github/workflows/docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index efd3954b..81ac601b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -19,7 +19,7 @@ jobs: - name: Build HTML uses: ammaraskar/sphinx-action@7.0.0 with: - build-command: "sphinx-multiversion source build/html" + build-command: "sphinx-multiversion source build/html -e" - name: Upload artifacts uses: actions/upload-artifact@v4 @@ -32,4 +32,4 @@ jobs: if: github.ref == 'refs/heads/master' with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: docs/build/html/master \ No newline at end of file + publish_dir: docs/build/html/master From 013a96ca064adad1e827fd46804f2f8b07599d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 20 Dec 2024 23:35:40 +0100 Subject: [PATCH 056/161] Update docs.yml workflow with redirection to sphinx-log.txt --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 81ac601b..bdea8f09 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -19,7 +19,7 @@ jobs: - name: Build HTML uses: ammaraskar/sphinx-action@7.0.0 with: - build-command: "sphinx-multiversion source build/html -e" + build-command: "sphinx-multiversion source build/html --keep-going --no-color > sphinx-log.txt" - name: Upload artifacts uses: actions/upload-artifact@v4 From 60fbef35ebafc3f8e948dc183da9670db184c361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 20 Dec 2024 23:48:48 +0100 Subject: [PATCH 057/161] Update docs.yml with explicit creation of sphinx-log --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bdea8f09..f30f4976 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -19,7 +19,7 @@ jobs: - name: Build HTML uses: ammaraskar/sphinx-action@7.0.0 with: - build-command: "sphinx-multiversion source build/html --keep-going --no-color > sphinx-log.txt" + build-command: "touch /tmp/sphinx-log && sphinx-multiversion source build/html" - name: Upload artifacts uses: actions/upload-artifact@v4 From aef4e8ed08e022a7b915f283ec59c4c9e2135fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 20 Dec 2024 23:56:55 +0100 Subject: [PATCH 058/161] Update docs.yml --- .github/workflows/docs.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f30f4976..9c3b4128 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,10 +16,15 @@ jobs: with: persist-credentials: false + # Temporary solution since sphinx-multiversion does + # not have -w option. + - name: Pre-create log file + run: touch /tmp/sphinx-log + - name: Build HTML uses: ammaraskar/sphinx-action@7.0.0 with: - build-command: "touch /tmp/sphinx-log && sphinx-multiversion source build/html" + build-command: "sphinx-multiversion source build/html" - name: Upload artifacts uses: actions/upload-artifact@v4 From de2ee1ef477b4e8a8be8bf8320adbd5d9df1a1f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 21 Dec 2024 00:07:38 +0100 Subject: [PATCH 059/161] Update to remove ammaraskar/sphinx-action@7.0.0 --- .github/workflows/docs.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9c3b4128..5a05350c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,15 +16,18 @@ jobs: with: persist-credentials: false - # Temporary solution since sphinx-multiversion does - # not have -w option. - - name: Pre-create log file - run: touch /tmp/sphinx-log - - - name: Build HTML - uses: ammaraskar/sphinx-action@7.0.0 + - name: Set up Python + uses: actions/setup-python@v4 with: - build-command: "sphinx-multiversion source build/html" + python-version: "3.9" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install sphinx==7.4.7 sphinxawesome-theme==5.3.2 sphinx-autodoc-typehints==2.3.0 sphinx-multiversion==0.2.4 + + - name: Build documentation + run: sphinx-multiversion docs/source docs/build/html --keep-going --no-color" - name: Upload artifacts uses: actions/upload-artifact@v4 From 4cecba931bf0d34f3d73f5b6f7777375df642a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 21 Dec 2024 00:09:00 +0100 Subject: [PATCH 060/161] Fix error in docs.yml --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5a05350c..3a864c7b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -27,7 +27,7 @@ jobs: pip install sphinx==7.4.7 sphinxawesome-theme==5.3.2 sphinx-autodoc-typehints==2.3.0 sphinx-multiversion==0.2.4 - name: Build documentation - run: sphinx-multiversion docs/source docs/build/html --keep-going --no-color" + run: sphinx-multiversion docs/source docs/build/html --keep-going --no-color - name: Upload artifacts uses: actions/upload-artifact@v4 From bc12fad8fae9c6e4f7dd565c0adc9fa5f642c4fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 21 Dec 2024 00:11:36 +0100 Subject: [PATCH 061/161] Use docs/requirements in docs.yml workflow. --- .github/workflows/docs.yml | 2 +- docs/requirements.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3a864c7b..af15327a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,7 +24,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install sphinx==7.4.7 sphinxawesome-theme==5.3.2 sphinx-autodoc-typehints==2.3.0 sphinx-multiversion==0.2.4 + pip install -r docs/requirements.txt - name: Build documentation run: sphinx-multiversion docs/source docs/build/html --keep-going --no-color diff --git a/docs/requirements.txt b/docs/requirements.txt index ae1043dd..cd3bc0f8 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,4 @@ +sphinx==7.4.7 sphinxawesome-theme==5.3.2 sphinx-autodoc-typehints==2.3.0 sphinx-multiversion==0.2.4 \ No newline at end of file From 88d8dc26895de7505f5a7d76a0d30f47da06279a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 21 Dec 2024 00:14:28 +0100 Subject: [PATCH 062/161] Enable release tags in conf.py for docs. --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 9312dcda..3d715eb1 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -63,7 +63,7 @@ # Whitelist pattern for remotes (set to None to use local branches only) smv_remote_whitelist = None # Pattern for released versions -smv_released_pattern = "" # r"^tags/.*$" +smv_released_pattern = r"^tags/.*$" # Format for versioned output directories inside the build directory smv_outputdir_format = "{ref.name}" # Determines whether remote or local git branches/tags are preferred if their From c68197e56f24f56afc63d1e49fbd4447a994306b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 21 Dec 2024 00:21:45 +0100 Subject: [PATCH 063/161] Fetch all tags of repo for building docs. --- .github/workflows/docs.yml | 7 ++++++- docs/source/conf.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index af15327a..7fe6d471 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -15,7 +15,12 @@ jobs: - uses: actions/checkout@v4 with: persist-credentials: false - + fetch-depth: 0 # Fetch the full history + ref: ${{ github.ref }} # Check out the current branch or tag + + - name: Fetch tags only + run: git fetch --tags --no-recurse-submodules + - name: Set up Python uses: actions/setup-python@v4 with: diff --git a/docs/source/conf.py b/docs/source/conf.py index 3d715eb1..9312dcda 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -63,7 +63,7 @@ # Whitelist pattern for remotes (set to None to use local branches only) smv_remote_whitelist = None # Pattern for released versions -smv_released_pattern = r"^tags/.*$" +smv_released_pattern = "" # r"^tags/.*$" # Format for versioned output directories inside the build directory smv_outputdir_format = "{ref.name}" # Determines whether remote or local git branches/tags are preferred if their From ca0d721962c634cf906b47b56c7c6312d022c118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 21 Dec 2024 00:24:59 +0100 Subject: [PATCH 064/161] Update publish_dir in docs workflow. --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 7fe6d471..42c1b0c3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -45,4 +45,4 @@ jobs: if: github.ref == 'refs/heads/master' with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: docs/build/html/master + publish_dir: docs/build/html From 00b1ce6f66d99e1b4cecc5e9f1263ef60f941825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 21 Dec 2024 00:37:49 +0100 Subject: [PATCH 065/161] Add redirect file. --- .github/workflows/docs.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 42c1b0c3..d5c9a0d7 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,6 +34,18 @@ jobs: - name: Build documentation run: sphinx-multiversion docs/source docs/build/html --keep-going --no-color + - name: Generate index.html for judge0.github.io/judge0-python. + run: | + echo ' + + + + + +

Redirecting to master/index.html

+ + ' > docs/build/html/index.html + - name: Upload artifacts uses: actions/upload-artifact@v4 with: From eb4fbffbd4ef9a906d14fefd7c8ac3e79afc6a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 21 Dec 2024 00:41:19 +0100 Subject: [PATCH 066/161] Remove body from generated index.html --- .github/workflows/docs.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d5c9a0d7..4b2aa464 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -41,9 +41,6 @@ jobs: - -

Redirecting to master/index.html

- ' > docs/build/html/index.html - name: Upload artifacts From 0f63e3ed94c012b7e5c60d88a21a06ca6e97793c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 24 Dec 2024 09:17:12 +0100 Subject: [PATCH 067/161] Update Client docs. Simplify docs structure. --- docs/source/api/clients.rst | 46 ++++++++- docs/source/api/index.rst | 7 -- docs/source/api/types.rst | 2 +- docs/source/conf.py | 4 +- docs/source/contributors_guide/index.rst | 5 - docs/source/index.rst | 13 ++- src/judge0/clients.py | 115 +++++++++++++++++++++-- 7 files changed, 160 insertions(+), 32 deletions(-) delete mode 100644 docs/source/api/index.rst delete mode 100644 docs/source/contributors_guide/index.rst diff --git a/docs/source/api/clients.rst b/docs/source/api/clients.rst index 52e7e4e8..b4d15c94 100644 --- a/docs/source/api/clients.rst +++ b/docs/source/api/clients.rst @@ -1,6 +1,46 @@ Clients Module ============== -.. automodule:: judge0.clients - :members: - :member-order: groupwise + +.. autoclass:: judge0.clients.Client + :exclude-members: API_KEY_ENV + +.. autoclass:: judge0.clients.ATD + :show-inheritance: + +.. autoclass:: judge0.clients.ATDJudge0CE + :show-inheritance: + :exclude-members: DEFAULT_ENDPOINT, DEFAULT_HOST, HOME_URL, DEFAULT_ABOUT_ENDPOINT, + DEFAULT_CONFIG_INFO_ENDPOINT, DEFAULT_LANGUAGE_ENDPOINT, DEFAULT_LANGUAGES_ENDPOINT, + DEFAULT_STATUSES_ENDPOINT, DEFAULT_CREATE_SUBMISSION_ENDPOINT, DEFAULT_GET_SUBMISSION_ENDPOINT, + DEFAULT_CREATE_SUBMISSIONS_ENDPOINT, DEFAULT_GET_SUBMISSIONS_ENDPOINT, get_about, + get_config_info, get_language, get_languages, get_statuses, create_submission, get_submission, + create_submissions, get_submissions + +.. autoclass:: judge0.clients.ATDJudge0ExtraCE + :show-inheritance: + :exclude-members: DEFAULT_ENDPOINT, DEFAULT_HOST, HOME_URL, DEFAULT_ABOUT_ENDPOINT, + DEFAULT_CONFIG_INFO_ENDPOINT, DEFAULT_LANGUAGE_ENDPOINT, DEFAULT_LANGUAGES_ENDPOINT, + DEFAULT_STATUSES_ENDPOINT, DEFAULT_CREATE_SUBMISSION_ENDPOINT, DEFAULT_GET_SUBMISSION_ENDPOINT, + DEFAULT_CREATE_SUBMISSIONS_ENDPOINT, DEFAULT_GET_SUBMISSIONS_ENDPOINT, get_about, + get_config_info, get_language, get_languages, get_statuses, create_submission, get_submission, + create_submissions, get_submissions + + +.. autoclass:: judge0.clients.Rapid + :show-inheritance: + +.. autoclass:: judge0.clients.RapidJudge0CE + :show-inheritance: + +.. autoclass:: judge0.clients.RapidJudge0ExtraCE + :show-inheritance: + +.. autoclass:: judge0.clients.Sulu + :show-inheritance: + +.. autoclass:: judge0.clients.SuluJudge0CE + :show-inheritance: + +.. autoclass:: judge0.clients.SuluJudge0ExtraCE + :show-inheritance: \ No newline at end of file diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst deleted file mode 100644 index eb4ed678..00000000 --- a/docs/source/api/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -.. toctree:: - :maxdepth: 2 - - api - submission - clients - types \ No newline at end of file diff --git a/docs/source/api/types.rst b/docs/source/api/types.rst index 2b415b39..219d7ed0 100644 --- a/docs/source/api/types.rst +++ b/docs/source/api/types.rst @@ -3,4 +3,4 @@ Types Module .. automodule:: judge0.base_types :members: - :undoc-members: + diff --git a/docs/source/conf.py b/docs/source/conf.py index 9312dcda..a9ae07cb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -21,7 +21,7 @@ extensions = [ "sphinx.ext.autodoc", "sphinx.ext.napoleon", - "sphinx.ext.autosummary", + # "sphinx.ext.autosummary", "sphinx_autodoc_typehints", "sphinx_multiversion", ] @@ -47,7 +47,7 @@ autodoc_default_options = { "members": True, - "undoc-members": True, + "undoc-members": False, "private-members": False, "special-members": False, "inherited-members": False, diff --git a/docs/source/contributors_guide/index.rst b/docs/source/contributors_guide/index.rst deleted file mode 100644 index 312258bb..00000000 --- a/docs/source/contributors_guide/index.rst +++ /dev/null @@ -1,5 +0,0 @@ -.. toctree:: - :maxdepth: 2 - - contributing - release_notes diff --git a/docs/source/index.rst b/docs/source/index.rst index b8ebcd9f..3510578a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -25,15 +25,14 @@ You can run minimal Hello World example in three easy steps: 3. Run the script. Want to learn more ------------------- - +================== To learn what is happening behind the scenes and how to best use Judge0 Python SDK to facilitate the development of your own product see In Depth guide and Examples. Getting Involved ----------------- +================ TODO @@ -43,7 +42,10 @@ TODO :titlesonly: :hidden: - api/index + api/api + api/submission + api/clients + api/types .. toctree:: :caption: Getting Involved @@ -51,4 +53,5 @@ TODO :titlesonly: :hidden: - contributors_guide/index \ No newline at end of file + contributors_guide/contributing + contributors_guide/release_notes \ No newline at end of file diff --git a/src/judge0/clients.py b/src/judge0/clients.py index ff8e989e..311d26bd 100644 --- a/src/judge0/clients.py +++ b/src/judge0/clients.py @@ -10,6 +10,24 @@ class Client: + """Base class for all clients. + + Parameters + ---------- + endpoint : str + Client's default endpoint. + auth_headers : dict + Request authentication headers. + + Attributes + ---------- + API_KEY_ENV : str + Environment variable where judge0-python should look for API key for + the client. Set to default values for ATD, RapidAPI, and Sulu clients. + """ + + # Environment variable where judge0-python should look for API key for + # the client. Set to default values for ATD, RapidAPI, and Sulu clients. API_KEY_ENV: ClassVar[str] = None def __init__( @@ -24,7 +42,6 @@ def __init__( self.retry_strategy = retry_strategy self.session = requests.Session() - # TODO: Should be handled differently. try: self.languages = self.get_languages() self.config = self.get_config_info() @@ -39,6 +56,13 @@ def __del__(self): @handle_too_many_requests_error_for_preview_client def get_about(self) -> dict: + """Get general information about judge0. + + Returns + ------- + dict + General information about judge0. + """ response = self.session.get( f"{self.endpoint}/about", headers=self.auth_headers, @@ -48,6 +72,13 @@ def get_about(self) -> dict: @handle_too_many_requests_error_for_preview_client def get_config_info(self) -> Config: + """Get information about client's configuration. + + Returns + ------- + Config + Client's configuration. + """ response = self.session.get( f"{self.endpoint}/config_info", headers=self.auth_headers, @@ -57,6 +88,18 @@ def get_config_info(self) -> Config: @handle_too_many_requests_error_for_preview_client def get_language(self, language_id: int) -> Language: + """Get language corresponding to the id. + + Parameters + ---------- + language_id : int + Language id. + + Returns + ------- + Language + Language corresponding to the passed id. + """ request_url = f"{self.endpoint}/languages/{language_id}" response = self.session.get(request_url, headers=self.auth_headers) response.raise_for_status() @@ -64,6 +107,13 @@ def get_language(self, language_id: int) -> Language: @handle_too_many_requests_error_for_preview_client def get_languages(self) -> list[Language]: + """Get a list of supported languages. + + Returns + ------- + list of language + A list of supported languages. + """ request_url = f"{self.endpoint}/languages" response = self.session.get(request_url, headers=self.auth_headers) response.raise_for_status() @@ -71,6 +121,13 @@ def get_languages(self) -> list[Language]: @handle_too_many_requests_error_for_preview_client def get_statuses(self) -> list[dict]: + """Get a list of possible submission statuses. + + Returns + ------- + list of dict + A list of possible submission statues. + """ response = self.session.get( f"{self.endpoint}/statuses", headers=self.auth_headers, @@ -80,20 +137,43 @@ def get_statuses(self) -> list[dict]: @property def version(self): + """Property corresponding to the current client's version.""" if not hasattr(self, "_version"): _version = self.get_about()["version"] setattr(self, "_version", _version) return self._version def get_language_id(self, language: Union[LanguageAlias, int]) -> int: - """Get language id corresponding to the language alias for the client.""" + """Get language id corresponding to the language alias for the client. + + Parameters + ---------- + language : LanguageAlias or int + Language alias or language id. + + Returns + ------- + Language id corresponding to the language alias. + """ if isinstance(language, LanguageAlias): supported_language_ids = LANGUAGE_TO_LANGUAGE_ID[self.version] language = supported_language_ids.get(language, -1) return language def is_language_supported(self, language: Union[LanguageAlias, int]) -> bool: - """Check if language is supported by the client.""" + """Check if language is supported by the client. + + Parameters + ---------- + language : LanguageAlias or int + Language alias or language id. + + Returns + ------- + bool + Return True if language is supported by the client, otherwise returns + False. + """ language_id = self.get_language_id(language) return any(language_id == lang.id for lang in self.languages) @@ -208,9 +288,6 @@ def create_submissions(self, submissions: Submissions) -> Submissions: f"{submission.language}!" ) - # TODO: Maybe raise an exception if the number of submissions is bigger - # than the batch size a client supports? - submissions_body = [submission.as_body(self) for submission in submissions] response = self.session.post( @@ -516,7 +593,15 @@ def __init__(self, api_key, **kwargs): class Sulu(Client): - """Base class for all Sulu clients.""" + """Base class for all Sulu clients. + + Parameters + ---------- + endpoint : str + Default request endpoint. + api_key : str, optional + Sulu API key. + """ API_KEY_ENV: ClassVar[str] = "JUDGE0_SULU_API_KEY" @@ -530,7 +615,13 @@ def __init__(self, endpoint, api_key=None, **kwargs): class SuluJudge0CE(Sulu): - """Sulu client for CE flavor.""" + """Sulu client for CE flavor. + + Parameters + ---------- + api_key : str, optional + Sulu API key. + """ DEFAULT_ENDPOINT: ClassVar[str] = "https://judge0-ce.p.sulu.sh" HOME_URL: ClassVar[str] = "https://sparkhub.sulu.sh/apis/judge0/judge0-ce/readme" @@ -544,7 +635,13 @@ def __init__(self, api_key=None, **kwargs): class SuluJudge0ExtraCE(Sulu): - """Sulu client for Extra CE flavor.""" + """Sulu client for Extra CE flavor. + + Parameters + ---------- + api_key : str + Sulu API key. + """ DEFAULT_ENDPOINT: ClassVar[str] = "https://judge0-extra-ce.p.sulu.sh" HOME_URL: ClassVar[str] = ( From 73fc1f52f8d91d7d8ad97366390477e21f09e3de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 24 Dec 2024 09:26:59 +0100 Subject: [PATCH 068/161] Update submission and api docs. --- docs/source/api/api.rst | 2 +- docs/source/api/submission.rst | 2 ++ src/judge0/api.py | 5 +++++ src/judge0/submission.py | 4 ++-- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/source/api/api.rst b/docs/source/api/api.rst index 08b5d0e3..e5b41b51 100644 --- a/docs/source/api/api.rst +++ b/docs/source/api/api.rst @@ -3,4 +3,4 @@ API Module .. automodule:: judge0.api :members: - :undoc-members: + :exclude-members: sync_run, async_run diff --git a/docs/source/api/submission.rst b/docs/source/api/submission.rst index e42a6aa0..4f9977af 100644 --- a/docs/source/api/submission.rst +++ b/docs/source/api/submission.rst @@ -3,4 +3,6 @@ Submission Module .. automodule:: judge0.submission :members: + :exclude-members: process_encoded_fields, process_language, process_post_execution_filesystem, + process_status :member-order: groupwise diff --git a/src/judge0/api.py b/src/judge0/api.py index e254dd9f..92b91b12 100644 --- a/src/judge0/api.py +++ b/src/judge0/api.py @@ -188,6 +188,11 @@ def wait( retry_strategy : RetryStrategy, optional A retry strategy. + Returns + ------- + Submission or Submissions + A single submission or a list of submissions. + Raises ------ ClientResolutionError diff --git a/src/judge0/submission.py b/src/judge0/submission.py index b9d474cb..10863c8a 100644 --- a/src/judge0/submission.py +++ b/src/judge0/submission.py @@ -208,7 +208,7 @@ def process_language( return value def set_attributes(self, attributes: dict[str, Any]) -> None: - """Set Submissions attributes while taking into account different + """Set submissions attributes while taking into account different attribute's types. Parameters @@ -236,7 +236,7 @@ def set_attributes(self, attributes: dict[str, Any]) -> None: def as_body(self, client: "Client") -> dict: """Prepare Submission as a dictionary while taking into account - the `client`'s restrictions. + the client's restrictions. """ body = { "source_code": encode(self.source_code), From f0b5d2b49dcde8371a1271960f77a2ab22c6eded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 24 Dec 2024 09:37:28 +0100 Subject: [PATCH 069/161] Update test workflow to be included for PR's as well. --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 38c14e80..3c33777d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,9 @@ on: push: branches: ["master"] paths: ["src/**", "tests/**"] + pull_request: + branches: ["master"] + paths: ["src/**", "tests/**"] permissions: contents: read From 78277eaf9dc90ae41af3ade1b42de8efb104289e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Tue, 24 Dec 2024 10:08:51 +0100 Subject: [PATCH 070/161] Add all LanguageAliases. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add all language aliases * Add all language alliases to dunder init. --------- Co-authored-by: Filip Karlo Došilović --- pyproject.toml | 12 ++- src/judge0/__init__.py | 69 +++++++++++++++-- src/judge0/base_types.py | 76 +++++++++++++++++-- src/judge0/data.py | 156 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 288 insertions(+), 25 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3568054f..257ab800 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,5 +49,15 @@ docs = ["sphinx==7.4.7"] [tool.flake8] docstring-convention = "numpy" -extend-ignore = ["D205", "D400", "D105", "D100", "D101", "D102", "D103", "F821"] +extend-ignore = [ + 'D100', + 'D101', + 'D102', + 'D103', + 'D104', + 'D105', + 'D205', + 'D400', + 'F821', +] max-line-length = 88 diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index 5ccf40bb..df0f78ac 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -113,11 +113,70 @@ def _get_implicit_client(flavor: Flavor) -> Client: CE = Flavor.CE EXTRA_CE = Flavor.EXTRA_CE -# TODO: Let's use getattr and setattr for this language ALIASES and raise an -# exception if a value already exists. -PYTHON = LanguageAlias.PYTHON +ASSEMBLY = LanguageAlias.ASSEMBLY +BASH = LanguageAlias.BASH +BASIC = LanguageAlias.BASIC +BOSQUE = LanguageAlias.BOSQUE +C = LanguageAlias.C +C3 = LanguageAlias.C3 +CLOJURE = LanguageAlias.CLOJURE +COBOL = LanguageAlias.COBOL +COMMON_LISP = LanguageAlias.COMMON_LISP CPP = LanguageAlias.CPP -JAVA = LanguageAlias.JAVA -CPP_GCC = LanguageAlias.CPP_GCC CPP_CLANG = LanguageAlias.CPP_CLANG +CPP_GCC = LanguageAlias.CPP_GCC +CPP_TEST = LanguageAlias.CPP_TEST +CPP_TEST_CLANG = LanguageAlias.CPP_TEST_CLANG +CPP_TEST_GCC = LanguageAlias.CPP_TEST_GCC +CSHARP = LanguageAlias.CSHARP +CSHARP_DOTNET = LanguageAlias.CSHARP_DOTNET +CSHARP_MONO = LanguageAlias.CSHARP_MONO +CSHARP_TEST = LanguageAlias.CSHARP_TEST +C_CLANG = LanguageAlias.C_CLANG +C_GCC = LanguageAlias.C_GCC +D = LanguageAlias.D +DART = LanguageAlias.DART +ELIXIR = LanguageAlias.ELIXIR +ERLANG = LanguageAlias.ERLANG +EXECUTABLE = LanguageAlias.EXECUTABLE +FORTRAN = LanguageAlias.FORTRAN +FSHARP = LanguageAlias.FSHARP +GO = LanguageAlias.GO +GROOVY = LanguageAlias.GROOVY +HASKELL = LanguageAlias.HASKELL +JAVA = LanguageAlias.JAVA +JAVAFX = LanguageAlias.JAVAFX +JAVASCRIPT = LanguageAlias.JAVASCRIPT +JAVA_JDK = LanguageAlias.JAVA_JDK +JAVA_OPENJDK = LanguageAlias.JAVA_OPENJDK +JAVA_TEST = LanguageAlias.JAVA_TEST +KOTLIN = LanguageAlias.KOTLIN +LUA = LanguageAlias.LUA +MPI_C = LanguageAlias.MPI_C +MPI_CPP = LanguageAlias.MPI_CPP +MPI_PYTHON = LanguageAlias.MPI_PYTHON +MULTI_FILE = LanguageAlias.MULTI_FILE +NIM = LanguageAlias.NIM +OBJECTIVE_C = LanguageAlias.OBJECTIVE_C +OCAML = LanguageAlias.OCAML +OCTAVE = LanguageAlias.OCTAVE +PASCAL = LanguageAlias.PASCAL +PERL = LanguageAlias.PERL +PHP = LanguageAlias.PHP +PLAIN_TEXT = LanguageAlias.PLAIN_TEXT +PROLOG = LanguageAlias.PROLOG +PYTHON = LanguageAlias.PYTHON +PYTHON2 = LanguageAlias.PYTHON2 +PYTHON2_PYPY = LanguageAlias.PYTHON2_PYPY +PYTHON3 = LanguageAlias.PYTHON3 +PYTHON3_PYPY = LanguageAlias.PYTHON3_PYPY PYTHON_FOR_ML = LanguageAlias.PYTHON_FOR_ML +PYTHON_PYPY = LanguageAlias.PYTHON_PYPY +R = LanguageAlias.R +RUBY = LanguageAlias.RUBY +RUST = LanguageAlias.RUST +SCALA = LanguageAlias.SCALA +SQLITE = LanguageAlias.SQLITE +SWIFT = LanguageAlias.SWIFT +TYPESCRIPT = LanguageAlias.TYPESCRIPT +VISUAL_BASIC = LanguageAlias.VISUAL_BASIC diff --git a/src/judge0/base_types.py b/src/judge0/base_types.py index 05a7a646..8b892bad 100644 --- a/src/judge0/base_types.py +++ b/src/judge0/base_types.py @@ -1,7 +1,7 @@ import copy from dataclasses import dataclass -from enum import IntEnum +from enum import IntEnum, auto from typing import Optional, Protocol, runtime_checkable, Sequence, Union from pydantic import BaseModel @@ -59,13 +59,73 @@ class Language(BaseModel): class LanguageAlias(IntEnum): """Language enumeration.""" - - PYTHON = 0 - CPP = 1 - JAVA = 2 - CPP_GCC = 3 - CPP_CLANG = 4 - PYTHON_FOR_ML = 5 + ASSEMBLY = auto() + BASH = auto() + BASIC = auto() + BOSQUE = auto() + C = auto() + C3 = auto() + CLOJURE = auto() + COBOL = auto() + COMMON_LISP = auto() + CPP = auto() + CPP_CLANG = auto() + CPP_GCC = auto() + CPP_TEST = auto() + CPP_TEST_CLANG = auto() + CPP_TEST_GCC = auto() + CSHARP = auto() + CSHARP_DOTNET = auto() + CSHARP_MONO = auto() + CSHARP_TEST = auto() + C_CLANG = auto() + C_GCC = auto() + D = auto() + DART = auto() + ELIXIR = auto() + ERLANG = auto() + EXECUTABLE = auto() + FORTRAN = auto() + FSHARP = auto() + GO = auto() + GROOVY = auto() + HASKELL = auto() + JAVA = auto() + JAVAFX = auto() + JAVASCRIPT = auto() + JAVA_JDK = auto() + JAVA_OPENJDK = auto() + JAVA_TEST = auto() + KOTLIN = auto() + LUA = auto() + MPI_C = auto() + MPI_CPP = auto() + MPI_PYTHON = auto() + MULTI_FILE = auto() + NIM = auto() + OBJECTIVE_C = auto() + OCAML = auto() + OCTAVE = auto() + PASCAL = auto() + PERL = auto() + PHP = auto() + PLAIN_TEXT = auto() + PROLOG = auto() + PYTHON = auto() + PYTHON2 = auto() + PYTHON2_PYPY = auto() + PYTHON3 = auto() + PYTHON3_PYPY = auto() + PYTHON_FOR_ML = auto() + PYTHON_PYPY = auto() + R = auto() + RUBY = auto() + RUST = auto() + SCALA = auto() + SQLITE = auto() + SWIFT = auto() + TYPESCRIPT = auto() + VISUAL_BASIC = auto() class Flavor(IntEnum): diff --git a/src/judge0/data.py b/src/judge0/data.py index 1e759c26..39ad1b32 100644 --- a/src/judge0/data.py +++ b/src/judge0/data.py @@ -2,31 +2,165 @@ LANGUAGE_TO_LANGUAGE_ID = { "1.13.1": { - LanguageAlias.PYTHON: 71, - LanguageAlias.CPP: 54, - LanguageAlias.JAVA: 62, - LanguageAlias.CPP_GCC: 54, + LanguageAlias.ASSEMBLY: 45, + LanguageAlias.BASH: 46, + LanguageAlias.BASIC: 47, + LanguageAlias.C: 50, + LanguageAlias.CLOJURE: 86, + LanguageAlias.COBOL: 77, + LanguageAlias.COMMON_LISP: 55, + LanguageAlias.CPP: 52, LanguageAlias.CPP_CLANG: 76, + LanguageAlias.CPP_GCC: 52, + LanguageAlias.CSHARP: 51, + LanguageAlias.CSHARP_MONO: 51, + LanguageAlias.C_CLANG: 75, + LanguageAlias.C_GCC: 50, + LanguageAlias.D: 56, + LanguageAlias.ELIXIR: 57, + LanguageAlias.ERLANG: 58, + LanguageAlias.EXECUTABLE: 44, + LanguageAlias.FORTRAN: 59, + LanguageAlias.FSHARP: 87, + LanguageAlias.GO: 60, + LanguageAlias.GROOVY: 88, + LanguageAlias.HASKELL: 61, + LanguageAlias.JAVA: 62, + LanguageAlias.JAVASCRIPT: 63, + LanguageAlias.JAVA_OPENJDK: 62, + LanguageAlias.KOTLIN: 78, + LanguageAlias.LUA: 64, + LanguageAlias.MULTI_FILE: 89, + LanguageAlias.OBJECTIVE_C: 79, + LanguageAlias.OCAML: 65, + LanguageAlias.OCTAVE: 66, + LanguageAlias.PASCAL: 67, + LanguageAlias.PERL: 85, + LanguageAlias.PHP: 68, + LanguageAlias.PLAIN_TEXT: 43, + LanguageAlias.PROLOG: 69, + LanguageAlias.PYTHON: 71, + LanguageAlias.PYTHON2: 70, + LanguageAlias.PYTHON3: 71, + LanguageAlias.R: 80, + LanguageAlias.RUBY: 72, + LanguageAlias.RUST: 73, + LanguageAlias.SCALA: 81, + LanguageAlias.SQLITE: 82, + LanguageAlias.SWIFT: 83, + LanguageAlias.TYPESCRIPT: 74, + LanguageAlias.VISUAL_BASIC: 84, }, "1.13.1-extra": { - LanguageAlias.PYTHON: 10, + LanguageAlias.BOSQUE: 11, + LanguageAlias.C: 1, + LanguageAlias.C3: 3, LanguageAlias.CPP: 2, - LanguageAlias.JAVA: 4, LanguageAlias.CPP_CLANG: 2, + LanguageAlias.CPP_TEST: 12, + LanguageAlias.CPP_TEST_CLANG: 15, + LanguageAlias.CPP_TEST_GCC: 12, + LanguageAlias.CSHARP: 22, + LanguageAlias.CSHARP_MONO: 22, + LanguageAlias.CSHARP_DOTNET: 21, + LanguageAlias.CSHARP_TEST: 23, + LanguageAlias.C_CLANG: 1, + LanguageAlias.FSHARP: 24, + LanguageAlias.JAVA: 4, + LanguageAlias.JAVA_OPENJDK: 4, + LanguageAlias.JAVA_TEST: 5, + LanguageAlias.MPI_C: 6, + LanguageAlias.MPI_CPP: 7, + LanguageAlias.MPI_PYTHON: 8, + LanguageAlias.MULTI_FILE: 89, + LanguageAlias.NIM: 9, + LanguageAlias.PYTHON: 10, + LanguageAlias.PYTHON3: 10, LanguageAlias.PYTHON_FOR_ML: 10, + LanguageAlias.VISUAL_BASIC: 20, }, "1.14.0": { - LanguageAlias.PYTHON: 100, + LanguageAlias.ASSEMBLY: 45, + LanguageAlias.BASH: 46, + LanguageAlias.BASIC: 47, + LanguageAlias.C: 103, + LanguageAlias.CLOJURE: 86, + LanguageAlias.COBOL: 77, + LanguageAlias.COMMON_LISP: 55, LanguageAlias.CPP: 105, - LanguageAlias.JAVA: 91, - LanguageAlias.CPP_GCC: 105, LanguageAlias.CPP_CLANG: 76, + LanguageAlias.CPP_GCC: 105, + LanguageAlias.CSHARP: 51, + LanguageAlias.CSHARP_MONO: 51, + LanguageAlias.C_CLANG: 104, + LanguageAlias.C_GCC: 103, + LanguageAlias.D: 56, + LanguageAlias.DART: 90, + LanguageAlias.ELIXIR: 57, + LanguageAlias.ERLANG: 58, + LanguageAlias.EXECUTABLE: 44, + LanguageAlias.FORTRAN: 59, + LanguageAlias.FSHARP: 87, + LanguageAlias.GO: 95, + LanguageAlias.GROOVY: 88, + LanguageAlias.HASKELL: 61, + LanguageAlias.JAVA: 62, + LanguageAlias.JAVAFX: 96, + LanguageAlias.JAVASCRIPT: 102, + LanguageAlias.JAVA_JDK: 91, + LanguageAlias.JAVA_OPENJDK: 62, + LanguageAlias.KOTLIN: 78, + LanguageAlias.LUA: 64, + LanguageAlias.MULTI_FILE: 89, + LanguageAlias.OBJECTIVE_C: 79, + LanguageAlias.OCAML: 65, + LanguageAlias.OCTAVE: 66, + LanguageAlias.PASCAL: 67, + LanguageAlias.PERL: 85, + LanguageAlias.PHP: 98, + LanguageAlias.PLAIN_TEXT: 43, + LanguageAlias.PROLOG: 69, + LanguageAlias.PYTHON: 100, + LanguageAlias.PYTHON2: 70, + LanguageAlias.PYTHON3: 100, + LanguageAlias.R: 99, + LanguageAlias.RUBY: 72, + LanguageAlias.RUST: 73, + LanguageAlias.SCALA: 81, + LanguageAlias.SQLITE: 82, + LanguageAlias.SWIFT: 83, + LanguageAlias.TYPESCRIPT: 101, + LanguageAlias.VISUAL_BASIC: 84, }, "1.14.0-extra": { - LanguageAlias.PYTHON: 25, + LanguageAlias.BOSQUE: 11, + LanguageAlias.C: 1, + LanguageAlias.C3: 3, LanguageAlias.CPP: 2, - LanguageAlias.JAVA: 4, LanguageAlias.CPP_CLANG: 2, + LanguageAlias.CPP_TEST: 12, + LanguageAlias.CPP_TEST_CLANG: 15, + LanguageAlias.CPP_TEST_GCC: 12, + LanguageAlias.CSHARP: 29, + LanguageAlias.CSHARP_MONO: 22, + LanguageAlias.CSHARP_DOTNET: 29, + LanguageAlias.CSHARP_TEST: 23, + LanguageAlias.C_CLANG: 1, + LanguageAlias.FSHARP: 24, + LanguageAlias.JAVA: 4, + LanguageAlias.JAVA_OPENJDK: 4, + LanguageAlias.JAVA_TEST: 5, + LanguageAlias.MPI_C: 6, + LanguageAlias.MPI_CPP: 7, + LanguageAlias.MPI_PYTHON: 8, + LanguageAlias.MULTI_FILE: 89, + LanguageAlias.NIM: 9, + LanguageAlias.PYTHON: 25, + LanguageAlias.PYTHON2: 26, + LanguageAlias.PYTHON2_PYPY: 26, + LanguageAlias.PYTHON3: 25, + LanguageAlias.PYTHON3_PYPY: 28, LanguageAlias.PYTHON_FOR_ML: 25, + LanguageAlias.VISUAL_BASIC: 20, }, } From b28c1d64d983e23f71d9ca3a6dc4e9a23f3daff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Tue, 24 Dec 2024 15:16:25 +0100 Subject: [PATCH 071/161] Add source_code as bytes --- examples/0006_exe.py | 9 +++++++++ src/judge0/submission.py | 2 +- tests/test_submission.py | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 examples/0006_exe.py diff --git a/examples/0006_exe.py b/examples/0006_exe.py new file mode 100644 index 00000000..81d4ada7 --- /dev/null +++ b/examples/0006_exe.py @@ -0,0 +1,9 @@ +from base64 import b64decode + +import judge0 + +source_code = b64decode( + "f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAAABAAAAAAABAAAAAAAAAAEAQAAAAAAAAAAAAAEAAOAABAEAABAADAAEAAAAFAAAAABAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAJQAAAAAAAAAlAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHAjVANsAG+GABAAInHDwUx/41HPA8FAGhlbGxvLCB3b3JsZAoALnNoc3RydGFiAC50ZXh0AC5yb2RhdGEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAEAAAAGAAAAAAAAAAAAQAAAAAAAABAAAAAAAAAXAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABEAAAABAAAAAgAAAAAAAAAYAEAAAAAAABgQAAAAAAAADQAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAABAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAlEAAAAAAAABkAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA" # noqa: E501 +) +result = judge0.run(source_code=source_code, language=judge0.EXECUTABLE) +print(result.stdout) diff --git a/src/judge0/submission.py b/src/judge0/submission.py index 10863c8a..55b76602 100644 --- a/src/judge0/submission.py +++ b/src/judge0/submission.py @@ -126,7 +126,7 @@ class Submission(BaseModel): URL for a callback to report execution results or status. """ - source_code: Optional[str] = Field(default=None, repr=True) + source_code: Optional[Union[str, bytes]] = Field(default=None, repr=True) language: Union[LanguageAlias, int] = Field( default=LanguageAlias.PYTHON, repr=True, diff --git a/tests/test_submission.py b/tests/test_submission.py index fb1bf737..ddae1401 100644 --- a/tests/test_submission.py +++ b/tests/test_submission.py @@ -1,3 +1,5 @@ +from base64 import b64decode + from judge0 import run, Status, Submission, wait from judge0.base_types import LanguageAlias @@ -92,3 +94,19 @@ def test_language_before_and_after_execution(request): assert submission.language == LanguageAlias.JAVA submission = run(client=client, submissions=submission) assert submission.language == LanguageAlias.JAVA + + +def test_language_executable(request): + client = request.getfixturevalue("judge0_ce_client") + code = b64decode( + "f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAAABAAAAAAABAAAAAAAAAAEAQAAAAAAAAAAAAAEAAOAABAEAABAADAAEAAAAFAAAAABAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAJQAAAAAAAAAlAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHAjVANsAG+GABAAInHDwUx/41HPA8FAGhlbGxvLCB3b3JsZAoALnNoc3RydGFiAC50ZXh0AC5yb2RhdGEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAEAAAAGAAAAAAAAAAAAQAAAAAAAABAAAAAAAAAXAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABEAAAABAAAAAgAAAAAAAAAYAEAAAAAAABgQAAAAAAAADQAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAABAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAlEAAAAAAAABkAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA" # noqa: E501 + ) + submission = Submission( + source_code=code, + language=LanguageAlias.EXECUTABLE, + ) + + assert submission.language == LanguageAlias.EXECUTABLE + submission = run(client=client, submissions=submission) + assert submission.language == LanguageAlias.EXECUTABLE + assert submission.stdout == "hello, world\n" From aa1d8123822ecb41c9df864e61a94355325bf949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Tue, 24 Dec 2024 15:21:44 +0100 Subject: [PATCH 072/161] Prepare 0.0.3 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 257ab800..212569cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.0.2" +version = "0.0.3" description = "The official Python library for Judge0." readme = "README.md" requires-python = ">=3.9" From 1e1af05530ad89f1f9e20b3d499cdc361b684f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Tue, 24 Dec 2024 15:29:25 +0100 Subject: [PATCH 073/161] Update pyproject description --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 212569cb..18592ef9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "judge0" -version = "0.0.3" -description = "The official Python library for Judge0." +version = "0.0.4-dev" +description = "The official Python SDK for Judge0." readme = "README.md" requires-python = ">=3.9" authors = [{ name = "Judge0", email = "contact@judge0.com" }] From 79aac51bd56aec693206a4c630b1441a214ffb33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Thu, 26 Dec 2024 00:04:34 +0100 Subject: [PATCH 074/161] Add .python-version to gitignore. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 82f92755..13480660 100644 --- a/.gitignore +++ b/.gitignore @@ -85,7 +85,7 @@ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: -# .python-version +.python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. From 49f61a8d85224f5b7f68dfc4979379844a3ed54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 28 Dec 2024 18:47:43 +0100 Subject: [PATCH 075/161] Add pygments style to docs conf. --- docs/source/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index a9ae07cb..49c7ab8a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -41,6 +41,7 @@ "versioning.html", ], } +pygments_style = "sphinx" sys.path.insert(0, os.path.abspath("../../src/")) # Adjust as needed From f87358f99b40aa258a0926d7952ff0099cba802d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 28 Dec 2024 18:50:58 +0100 Subject: [PATCH 076/161] Change permalinks in docs headers. --- docs/source/conf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 49c7ab8a..83e54e45 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,6 +10,8 @@ import os import sys +from sphinxawesome_theme.postprocess import Icons + project = "Judge0 Python SDK" copyright = "2024, Judge0" author = "Judge0" @@ -45,6 +47,8 @@ sys.path.insert(0, os.path.abspath("../../src/")) # Adjust as needed +# -- Awesome theme config -- +html_permalinks_icon = Icons.permalinks_icon autodoc_default_options = { "members": True, From dac233da696304b9125dd1811e308fea618a8d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 28 Dec 2024 19:16:04 +0100 Subject: [PATCH 077/161] Add reverse version sorting. --- docs/source/_templates/versioning.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/source/_templates/versioning.html b/docs/source/_templates/versioning.html index ef74ed44..1b8de300 100644 --- a/docs/source/_templates/versioning.html +++ b/docs/source/_templates/versioning.html @@ -1,7 +1,10 @@ {% if versions %} +{% set master_version = versions | selectattr('name', 'equalto', 'master') | list %} +{% set other_versions = versions | rejectattr('name', 'equalto', 'master') | sort(attribute='name', reverse=true) %} +{% set sorted_versions = master_version + other_versions %}

{{ _('Versions') }}

    - {%- for item in versions %} + {%- for item in sorted_versions %}
  • {{ item.name }}
  • {%- endfor %}
From 97aa8e30337bcee1ebccde395b5fc447c1a21b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 28 Dec 2024 19:41:50 +0100 Subject: [PATCH 078/161] Update workflow to point the index to latest release. --- .github/workflows/docs.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4b2aa464..6a6a8e9a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,12 +34,22 @@ jobs: - name: Build documentation run: sphinx-multiversion docs/source docs/build/html --keep-going --no-color + - name: Get the latest tag + run: | + # Fetch all tags + git fetch --tags + # Get the latest tag + latest_tag=$(git tag --sort=-creatordate | head -n 1) + # Remove the 'v' prefix if it exists + latest_tag_no_v=${latest_tag#v} + echo "LATEST_RELEASE=$latest_tag_no_v" >> $GITHUB_ENV + - name: Generate index.html for judge0.github.io/judge0-python. run: | echo ' - + ' > docs/build/html/index.html From dfaf7d55efe3cf6cc6a66cefdad9bcc703cc9e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 28 Dec 2024 19:48:14 +0100 Subject: [PATCH 079/161] Update with explicitly set env variable in the step. --- .github/workflows/docs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6a6a8e9a..11f7c0cb 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -49,9 +49,11 @@ jobs: echo ' - + ' > docs/build/html/index.html + env: + latest_release: ${{ env.LATEST_RELEASE }} - name: Upload artifacts uses: actions/upload-artifact@v4 From 6c3b8b77a15fcefa8cfecf1712e850892a5ed89c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 28 Dec 2024 19:49:37 +0100 Subject: [PATCH 080/161] Fix unexpected EOF. --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 11f7c0cb..1c954670 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -49,7 +49,7 @@ jobs: echo ' - + ' > docs/build/html/index.html env: From 335ee1ba91f167eb4b6748dfa65d495d7d0ee2ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 28 Dec 2024 19:58:03 +0100 Subject: [PATCH 081/161] Don't remove the v from the tag when building index for docs. --- .github/workflows/docs.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1c954670..4a70709c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -40,9 +40,7 @@ jobs: git fetch --tags # Get the latest tag latest_tag=$(git tag --sort=-creatordate | head -n 1) - # Remove the 'v' prefix if it exists - latest_tag_no_v=${latest_tag#v} - echo "LATEST_RELEASE=$latest_tag_no_v" >> $GITHUB_ENV + echo "LATEST_RELEASE=$latest_tag" >> $GITHUB_ENV - name: Generate index.html for judge0.github.io/judge0-python. run: | From 9a7519817d176dba6f5b49458d1ffa9395153d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Mon, 30 Dec 2024 12:06:46 +0100 Subject: [PATCH 082/161] Add text to Getting Involved section on index page. --- docs/source/index.rst | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 3510578a..257efdaf 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -34,7 +34,20 @@ Examples. Getting Involved ================ -TODO +Getting involved in any open-source project is simple and rewarding, with +multiple ways to contribute to its growth and success. You can help by: + +1. `reporting bugs `_ by + creating a detailed issue describing the problem, along with any relevant code or + steps to reproduce it, so it can be addressed effectively, +2. creating a `pull request `_ for + an existing issue; we welcome improvements, fixes, and new features that align + with the project's goals, and +3. you can show support by starring the `repository `_, + letting us know that we’re doing a good job and helping us gain visibility within + the open-source community. + +Every contribution, big or small, is valuable! .. toctree:: :caption: API From 334d6fa2d1e92b2dd7f6309e78ce50a0d86feb85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Mon, 30 Dec 2024 12:44:10 +0100 Subject: [PATCH 083/161] Add retry module to docs. --- docs/source/api/retry.rst | 6 +++++ docs/source/index.rst | 1 + pyproject.toml | 4 +++- src/judge0/retry.py | 50 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 docs/source/api/retry.rst diff --git a/docs/source/api/retry.rst b/docs/source/api/retry.rst new file mode 100644 index 00000000..22977dc1 --- /dev/null +++ b/docs/source/api/retry.rst @@ -0,0 +1,6 @@ +Retry Module +============ + +.. automodule:: judge0.retry + :members: + :member-order: bysource \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index 257efdaf..2c50fdfb 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -59,6 +59,7 @@ Every contribution, big or small, is valuable! api/submission api/clients api/types + api/retry .. toctree:: :caption: Getting Involved diff --git a/pyproject.toml b/pyproject.toml index 18592ef9..edfcfdd7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,6 @@ test = [ docs = ["sphinx==7.4.7"] [tool.flake8] -docstring-convention = "numpy" extend-ignore = [ 'D100', 'D101', @@ -56,8 +55,11 @@ extend-ignore = [ 'D103', 'D104', 'D105', + 'D107', 'D205', + "D209", 'D400', 'F821', ] +docstring-convention = "numpy" max-line-length = 88 diff --git a/src/judge0/retry.py b/src/judge0/retry.py index 20b42ef7..f4bff5b1 100644 --- a/src/judge0/retry.py +++ b/src/judge0/retry.py @@ -3,62 +3,106 @@ class RetryStrategy(ABC): + """Abstract base class that defines the interface for any retry strategy. + + See :obj:`MaxRetries`, :obj:`MaxWaitTime`, and :obj:`RegularPeriodRetry` for + example implementations. + """ + @abstractmethod def is_done(self) -> bool: + """Check if the retry strategy has exhausted its retries.""" pass @abstractmethod def wait(self) -> None: + """Delay implementation before the next retry attempt.""" pass + @abstractmethod def step(self) -> None: + """Update internal attributes of the retry strategy.""" pass class MaxRetries(RetryStrategy): """Check for submissions status every 100 ms and retry a maximum of - `max_retries` times.""" + `max_retries` times. + + Parameters + ---------- + max_retries : int + Max number of retries. + """ def __init__(self, max_retries: int = 20): + if max_retries < 1: + raise ValueError("max_retries must be at least 1.") self.n_retries = 0 self.max_retries = max_retries def step(self): + """Increment the number of retries by one.""" self.n_retries += 1 def wait(self): + """Wait for 0.1 seconds between retries.""" time.sleep(0.1) def is_done(self) -> bool: + """Check if the number of retries is bigger or equal to specified + maximum number of retries.""" return self.n_retries >= self.max_retries class MaxWaitTime(RetryStrategy): """Check for submissions status every 100 ms and wait for all submissions - a maximum of `max_wait_time` (seconds).""" + a maximum of `max_wait_time` (seconds). + + Parameters + ---------- + max_wait_time_sec : float + Maximum waiting time (in seconds). + """ def __init__(self, max_wait_time_sec: float = 5 * 60): self.max_wait_time_sec = max_wait_time_sec self.total_wait_time = 0 def step(self): + """Add 0.1 seconds to total waiting time.""" self.total_wait_time += 0.1 def wait(self): + """Wait (sleep) for 0.1 seconds.""" time.sleep(0.1) def is_done(self): + """Check if the total waiting time is bigger or equal to the specified + maximum waiting time.""" return self.total_wait_time >= self.max_wait_time_sec class RegularPeriodRetry(RetryStrategy): - """Check for submissions status periodically for indefinite amount of time.""" + """Check for submissions status periodically for indefinite amount of time. + + Parameters + ---------- + wait_time_sec : float + Wait time between retries (in seconds). + """ def __init__(self, wait_time_sec: float = 0.1): self.wait_time_sec = wait_time_sec def wait(self): + """Wait for `wait_time_sec` seconds.""" time.sleep(self.wait_time_sec) def is_done(self) -> bool: + """Return False, as this retry strategy is indefinite.""" return False + + def step(self) -> None: + """Satisfy the interface with a dummy implementation.""" + pass From c498a23f7cf371fbe95e664e60191a3ad3b35687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Mon, 30 Dec 2024 12:49:20 +0100 Subject: [PATCH 084/161] Add errors module to docs. Add docstrings in common module. --- docs/source/api/errors.rst | 5 +++++ docs/source/index.rst | 1 + src/judge0/common.py | 2 ++ 3 files changed, 8 insertions(+) create mode 100644 docs/source/api/errors.rst diff --git a/docs/source/api/errors.rst b/docs/source/api/errors.rst new file mode 100644 index 00000000..b976cd16 --- /dev/null +++ b/docs/source/api/errors.rst @@ -0,0 +1,5 @@ +Errors Module +============= + +.. automodule:: judge0.errors + :members: diff --git a/docs/source/index.rst b/docs/source/index.rst index 2c50fdfb..6cb851f6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -60,6 +60,7 @@ Every contribution, big or small, is valuable! api/clients api/types api/retry + api/errors .. toctree:: :caption: Getting Involved diff --git a/src/judge0/common.py b/src/judge0/common.py index 57ad8387..c25ee549 100644 --- a/src/judge0/common.py +++ b/src/judge0/common.py @@ -6,6 +6,7 @@ def encode(content: Union[bytes, str, Encodeable]) -> str: + """Encode content to base64 string.""" if isinstance(content, bytes): return b64encode(content).decode() if isinstance(content, str): @@ -16,6 +17,7 @@ def encode(content: Union[bytes, str, Encodeable]) -> str: def decode(content: Union[bytes, str]) -> str: + """Decode base64 encoded content.""" if isinstance(content, bytes): return b64decode( content.decode(errors="backslashreplace"), validate=True From b97d17141c8fdf7abf595bb6194dd39c67d42a36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Mon, 30 Dec 2024 12:55:19 +0100 Subject: [PATCH 085/161] Add version to dunder init. Update version in pyproject toml. --- pyproject.toml | 2 +- src/judge0/__init__.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index edfcfdd7..4974a30a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.0.4-dev" +version = "0.1.0.dev0" description = "The official Python SDK for Judge0." readme = "README.md" requires-python = ">=3.9" diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index df0f78ac..391e926d 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -27,6 +27,8 @@ from .retry import MaxRetries, MaxWaitTime, RegularPeriodRetry from .submission import Submission +__version__ = "0.1.0.dev0" + __all__ = [ "ATD", "ATDJudge0CE", From 4b88795a071cd94c5e9fcb01a5a7304323d137c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Mon, 30 Dec 2024 13:18:34 +0100 Subject: [PATCH 086/161] Update base types docs. Add scroll to top button in docs. --- docs/source/api/types.rst | 39 ++++++++++++++++++++++++++++++++++++++- docs/source/conf.py | 3 +++ src/judge0/base_types.py | 20 +++++++++++++++++--- src/judge0/common.py | 6 +++--- 4 files changed, 61 insertions(+), 7 deletions(-) diff --git a/docs/source/api/types.rst b/docs/source/api/types.rst index 219d7ed0..bf0f62ab 100644 --- a/docs/source/api/types.rst +++ b/docs/source/api/types.rst @@ -1,6 +1,43 @@ Types Module ============ -.. automodule:: judge0.base_types +Types +----- + +.. autoclass:: judge0.base_types.Config + :members: + :member-order: bysource + +.. autoclass:: judge0.base_types.Encodable + :members: + +.. autoclass:: judge0.base_types.Flavor + :members: + :member-order: bysource + +.. autoclass:: judge0.base_types.Language + :members: + :member-order: bysource + +.. autoclass:: judge0.base_types.LanguageAlias :members: + :member-order: bysource +.. autoclass:: judge0.base_types.Status + :members: + :member-order: bysource + +.. autoclass:: judge0.base_types.TestCase + :members: + :member-order: bysource + +Type aliases +------------ + +.. autoclass:: judge0.base_types.TestCaseType + :members: + :member-order: bysource + +.. autoclass:: judge0.base_types.TestCases + :members: + :member-order: bysource \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 83e54e45..697f9f50 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -35,6 +35,9 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "sphinxawesome_theme" +html_theme_options = { + "show_scrolltop": True, +} html_show_sphinx = False html_sidebars = { "**": [ diff --git a/src/judge0/base_types.py b/src/judge0/base_types.py index 8b892bad..5e7a57c9 100644 --- a/src/judge0/base_types.py +++ b/src/judge0/base_types.py @@ -1,7 +1,7 @@ import copy from dataclasses import dataclass -from enum import IntEnum, auto +from enum import auto, IntEnum from typing import Optional, Protocol, runtime_checkable, Sequence, Union from pydantic import BaseModel @@ -14,6 +14,8 @@ @dataclass(frozen=True) class TestCase: + """Dataclass for test case.""" + input: Optional[str] = None expected_output: Optional[str] = None @@ -21,7 +23,18 @@ class TestCase: def from_record( cls, test_case: Union[TestCaseType, None] ) -> Union["TestCase", None]: - """Create a TestCase from built-in types.""" + """Create a TestCase from built-in types. + + Parameters + ---------- + test_case: :obj:`TestCaseType` or None + Test case data. + + Returns + ------- + TestCase or None + Created TestCase object or None if test_case is None. + """ if isinstance(test_case, (tuple, list)): test_case = { field: value @@ -42,7 +55,7 @@ def from_record( @runtime_checkable -class Encodeable(Protocol): +class Encodable(Protocol): def encode(self) -> bytes: """Serialize the object to bytes.""" ... @@ -59,6 +72,7 @@ class Language(BaseModel): class LanguageAlias(IntEnum): """Language enumeration.""" + ASSEMBLY = auto() BASH = auto() BASIC = auto() diff --git a/src/judge0/common.py b/src/judge0/common.py index c25ee549..e8ab58e2 100644 --- a/src/judge0/common.py +++ b/src/judge0/common.py @@ -2,16 +2,16 @@ from itertools import islice from typing import Union -from judge0.base_types import Encodeable +from judge0.base_types import Encodable -def encode(content: Union[bytes, str, Encodeable]) -> str: +def encode(content: Union[bytes, str, Encodable]) -> str: """Encode content to base64 string.""" if isinstance(content, bytes): return b64encode(content).decode() if isinstance(content, str): return b64encode(content.encode()).decode() - if isinstance(content, Encodeable): + if isinstance(content, Encodable): return b64encode(content.encode()).decode() raise ValueError(f"Unsupported type. Expected bytes or str, got {type(content)}!") From 58194f3011d7ae914745cb4fbd9c8a4c766116fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Mon, 30 Dec 2024 13:27:10 +0100 Subject: [PATCH 087/161] Add filesystem to docs. --- docs/source/api/filesystem.rst | 6 ++++++ docs/source/api/types.rst | 4 ++++ docs/source/index.rst | 7 ++++--- src/judge0/filesystem.py | 20 ++++++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 docs/source/api/filesystem.rst diff --git a/docs/source/api/filesystem.rst b/docs/source/api/filesystem.rst new file mode 100644 index 00000000..73eafb65 --- /dev/null +++ b/docs/source/api/filesystem.rst @@ -0,0 +1,6 @@ +Filesystem Module +================= + +.. automodule:: judge0.filesystem + :members: + :member-order: bysource diff --git a/docs/source/api/types.rst b/docs/source/api/types.rst index bf0f62ab..8cb94ccb 100644 --- a/docs/source/api/types.rst +++ b/docs/source/api/types.rst @@ -34,6 +34,10 @@ Types Type aliases ------------ +.. autoclass:: judge0.base_types.Iterable + :members: + :member-order: bysource + .. autoclass:: judge0.base_types.TestCaseType :members: :member-order: bysource diff --git a/docs/source/index.rst b/docs/source/index.rst index 6cb851f6..d31c6019 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -56,11 +56,12 @@ Every contribution, big or small, is valuable! :hidden: api/api - api/submission api/clients - api/types - api/retry api/errors + api/filesystem + api/retry + api/submission + api/types .. toctree:: :caption: Getting Involved diff --git a/src/judge0/filesystem.py b/src/judge0/filesystem.py index 6773680c..27fae223 100644 --- a/src/judge0/filesystem.py +++ b/src/judge0/filesystem.py @@ -11,6 +11,16 @@ class File(BaseModel): + """File object for storing file content. + + Parameters + ---------- + name : str + File name. + content : str or bytes, optional + File content. If str is provided, it will be encoded to bytes. + """ + name: str content: Optional[Union[str, bytes]] = None @@ -29,6 +39,15 @@ def __str__(self): class Filesystem(BaseModel): + """Filesystem object for storing multiple files. + + Parameters + ---------- + content : str or bytes or File or Iterable[File] or Filesystem, optional + Filesystem content. If str or bytes is provided, it will be decoded to + files. + """ + files: list[File] = [] def __init__(self, **data): @@ -66,6 +85,7 @@ def __repr__(self) -> str: return f"{self.__class__.__name__}(content={content_encoded!r})" def encode(self) -> bytes: + """Encode Filesystem object to bytes.""" zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, "w") as zip_file: for file in self.files: From 6491bc55721b82557e80f75ff15a7aaf097164f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Mon, 30 Dec 2024 13:37:49 +0100 Subject: [PATCH 088/161] Add link to examples. --- docs/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index d31c6019..0db57436 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -29,7 +29,7 @@ Want to learn more To learn what is happening behind the scenes and how to best use Judge0 Python SDK to facilitate the development of your own product see In Depth guide and -Examples. +`examples `_. Getting Involved ================ From a2c5e45569a148210e15a4951cb8c21521110559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 31 Dec 2024 11:07:07 +0100 Subject: [PATCH 089/161] Use None for base client HOME_URL. --- src/judge0/clients.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/judge0/clients.py b/src/judge0/clients.py index 311d26bd..ac5af89f 100644 --- a/src/judge0/clients.py +++ b/src/judge0/clients.py @@ -46,8 +46,9 @@ def __init__( self.languages = self.get_languages() self.config = self.get_config_info() except Exception as e: + home_url = getattr(self, "HOME_URL", None) raise RuntimeError( - f"Authentication failed. Visit {self.HOME_URL} to get or " + f"Authentication failed. Visit {home_url} to get or " "review your authentication credentials." ) from e From 8338f218fe7fa7b1266a60e3f74f7f93837a615b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 31 Dec 2024 11:25:58 +0100 Subject: [PATCH 090/161] Update docs in base types module. --- src/judge0/base_types.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/judge0/base_types.py b/src/judge0/base_types.py index 5e7a57c9..dc18bdd7 100644 --- a/src/judge0/base_types.py +++ b/src/judge0/base_types.py @@ -14,7 +14,7 @@ @dataclass(frozen=True) class TestCase: - """Dataclass for test case.""" + """Test case data model.""" input: Optional[str] = None expected_output: Optional[str] = None @@ -62,6 +62,11 @@ def encode(self) -> bytes: class Language(BaseModel): + """Language data model. + + Stores information about a language supported by Judge0. + """ + id: int name: str is_archived: Optional[bool] = None @@ -71,7 +76,12 @@ class Language(BaseModel): class LanguageAlias(IntEnum): - """Language enumeration.""" + """Language alias enumeration. + + Enumerates the programming languages supported by Judge0 client. Language + alias is resolved to the latest version of the language supported by the + selected client. + """ ASSEMBLY = auto() BASH = auto() @@ -143,14 +153,20 @@ class LanguageAlias(IntEnum): class Flavor(IntEnum): - """Judge0 flavor enumeration.""" + """Flavor enumeration. + + Enumerates the flavors supported by Judge0 client. + """ CE = 0 EXTRA_CE = 1 class Status(IntEnum): - """Status enumeration.""" + """Status enumeration. + + Enumerates possible status codes of a submission. + """ IN_QUEUE = 1 PROCESSING = 2 @@ -172,7 +188,10 @@ def __str__(self): class Config(BaseModel): - """Client config data.""" + """Client config data model. + + Stores configuration data for the Judge0 client. + """ allow_enable_network: bool allow_enable_per_process_and_thread_memory_limit: bool From 86d935a2788ff4b09d1d3b677261a31c34afea13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 31 Dec 2024 11:36:45 +0100 Subject: [PATCH 091/161] Minor docs update. --- src/judge0/api.py | 8 +++-- src/judge0/clients.py | 74 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 74 insertions(+), 8 deletions(-) diff --git a/src/judge0/api.py b/src/judge0/api.py index 92b91b12..6c084713 100644 --- a/src/judge0/api.py +++ b/src/judge0/api.py @@ -353,10 +353,12 @@ def async_execute( resolved. submissions : Submission or Submissions, optional Submission or submissions for execution. - source_code: str, optional + source_code : str, optional A source code of a program. - test_cases: TestCaseType or TestCases, optional + test_cases : TestCaseType or TestCases, optional A single test or a list of test cases + **kwargs : dict + Additional keyword arguments to pass to the Submission constructor. Returns ------- @@ -405,6 +407,8 @@ def sync_execute( A source code of a program. test_cases: TestCaseType or TestCases, optional A single test or a list of test cases + **kwargs : dict + Additional keyword arguments to pass to the Submission constructor. Returns ------- diff --git a/src/judge0/clients.py b/src/judge0/clients.py index ac5af89f..621b8447 100644 --- a/src/judge0/clients.py +++ b/src/judge0/clients.py @@ -356,7 +356,19 @@ def get_submissions( class ATD(Client): - """Base class for all AllThingsDev clients.""" + """Base class for all AllThingsDev clients. + + Parameters + ---------- + endpoint : str + Default request endpoint. + host_header_value : str + Value for the x-apihub-host header. + api_key : str + AllThingsDev API key. + **kwargs : dict + Additional keyword arguments for the base Client. + """ API_KEY_ENV: ClassVar[str] = "JUDGE0_ATD_API_KEY" @@ -376,7 +388,15 @@ def _update_endpoint_header(self, header_value): class ATDJudge0CE(ATD): - """AllThingsDev client for CE flavor.""" + """AllThingsDev client for CE flavor. + + Parameters + ---------- + api_key : str + AllThingsDev API key. + **kwargs : dict + Additional keyword arguments for the base Client. + """ DEFAULT_ENDPOINT: ClassVar[str] = ( "https://judge0-ce.proxy-production.allthingsdev.co" @@ -460,7 +480,15 @@ def get_submissions( class ATDJudge0ExtraCE(ATD): - """AllThingsDev client for Extra CE flavor.""" + """AllThingsDev client for Extra CE flavor. + + Parameters + ---------- + api_key : str + AllThingsDev API key. + **kwargs : dict + Additional keyword arguments for the base Client. + """ DEFAULT_ENDPOINT: ClassVar[str] = ( "https://judge0-extra-ce.proxy-production.allthingsdev.co" @@ -545,7 +573,19 @@ def get_submissions( class Rapid(Client): - """Base class for all RapidAPI clients.""" + """Base class for all RapidAPI clients. + + Parameters + ---------- + endpoint : str + Default request endpoint. + host_header_value : str + Value for the x-rapidapi-host header. + api_key : str + RapidAPI API key. + **kwargs : dict + Additional keyword arguments for the base Client. + """ API_KEY_ENV: ClassVar[str] = "JUDGE0_RAPID_API_KEY" @@ -562,7 +602,15 @@ def __init__(self, endpoint, host_header_value, api_key, **kwargs): class RapidJudge0CE(Rapid): - """RapidAPI client for CE flavor.""" + """RapidAPI client for CE flavor. + + Parameters + ---------- + api_key : str + RapidAPI API key. + **kwargs : dict + Additional keyword arguments for the base Client. + """ DEFAULT_ENDPOINT: ClassVar[str] = "https://judge0-ce.p.rapidapi.com" DEFAULT_HOST: ClassVar[str] = "judge0-ce.p.rapidapi.com" @@ -578,7 +626,15 @@ def __init__(self, api_key, **kwargs): class RapidJudge0ExtraCE(Rapid): - """RapidAPI client for Extra CE flavor.""" + """RapidAPI client for Extra CE flavor. + + Parameters + ---------- + api_key : str + RapidAPI API key. + **kwargs : dict + Additional keyword arguments for the base Client. + """ DEFAULT_ENDPOINT: ClassVar[str] = "https://judge0-extra-ce.p.rapidapi.com" DEFAULT_HOST: ClassVar[str] = "judge0-extra-ce.p.rapidapi.com" @@ -602,6 +658,8 @@ class Sulu(Client): Default request endpoint. api_key : str, optional Sulu API key. + **kwargs : dict + Additional keyword arguments for the base Client. """ API_KEY_ENV: ClassVar[str] = "JUDGE0_SULU_API_KEY" @@ -622,6 +680,8 @@ class SuluJudge0CE(Sulu): ---------- api_key : str, optional Sulu API key. + **kwargs : dict + Additional keyword arguments for the base Client. """ DEFAULT_ENDPOINT: ClassVar[str] = "https://judge0-ce.p.sulu.sh" @@ -642,6 +702,8 @@ class SuluJudge0ExtraCE(Sulu): ---------- api_key : str Sulu API key. + **kwargs : dict + Additional keyword arguments for the base Client. """ DEFAULT_ENDPOINT: ClassVar[str] = "https://judge0-extra-ce.p.sulu.sh" From 3c2fb61ff51057988b98701dea68bf23c440735c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 31 Dec 2024 12:32:02 +0100 Subject: [PATCH 092/161] Update contributing guide. --- .../contributors_guide/contributing.rst | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/docs/source/contributors_guide/contributing.rst b/docs/source/contributors_guide/contributing.rst index 2a19fb5b..5cee988e 100644 --- a/docs/source/contributors_guide/contributing.rst +++ b/docs/source/contributors_guide/contributing.rst @@ -25,4 +25,46 @@ Preparing the development setup .. code-block:: console $ pip install -e .[test] + $ pip install -r docs/requirements.txt # needed for building the docs $ pre-commit install + +Building documentation +---------------------- + +Documentation is built using Sphinx. To build the documentation, run the + +.. code-block:: console + + $ cd docs + $ make html + +You should inspect the changes in the documentation by opening the +``docs/build/html/index.html`` file in your browser. + +You'll see a different output since the documentation is build with +`sphinx-multiversion `_ extension. + +Testing +------- + +If you implemented a feature or fixed a bug, please add tests for it. + +Unfortunately, at the moment you cannot run full test suite because it requires +access to API keys for all implemented API hubs (ATD, Sulu, and RapidAPI) and +a private Judge0 instance. To partially address this situation, you can run and +test your implemented feature and tests locally and use the GitHub CI pipeline +to run the full test suite. + +To run the tests locally, you can use the following command: + +.. code-block:: console + + $ pytest -svv tests -k '' + +To make the test compatible with the CI pipeline, you should use one of the +client fixtures: + +.. code-block:: python + + def test_my_test(request): + client = request.getfixturevalue("judge0_ce_client") # or judge0_extra_ce_client From ae10b877dff737f5c6ed63fd3e2b9632a0e1b4b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Tue, 31 Dec 2024 13:17:14 +0100 Subject: [PATCH 093/161] Change client order --- src/judge0/clients.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/judge0/clients.py b/src/judge0/clients.py index 621b8447..2d8366a9 100644 --- a/src/judge0/clients.py +++ b/src/judge0/clients.py @@ -715,5 +715,5 @@ def __init__(self, api_key=None, **kwargs): super().__init__(self.DEFAULT_ENDPOINT, api_key, **kwargs) -CE = (RapidJudge0CE, SuluJudge0CE, ATDJudge0CE) -EXTRA_CE = (RapidJudge0ExtraCE, SuluJudge0ExtraCE, ATDJudge0ExtraCE) +CE = (SuluJudge0CE, RapidJudge0CE, ATDJudge0CE) +EXTRA_CE = (SuluJudge0ExtraCE, RapidJudge0ExtraCE, ATDJudge0ExtraCE) From 50947518275817cbfba0cbe8ddf16922bdf5b6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 31 Dec 2024 14:05:50 +0100 Subject: [PATCH 094/161] Add logo --- docs/assets/logo.png | Bin 0 -> 31233 bytes docs/source/conf.py | 32 ++++++++++++++++++++++++++++++++ docs/source/index.rst | 6 +++--- 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 docs/assets/logo.png diff --git a/docs/assets/logo.png b/docs/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9661bbb3b66cba7024393224b4afefdedb102674 GIT binary patch literal 31233 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

VJca6nosg~zu%#x_>VE4K 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 literal 0 HcmV?d00001 diff --git a/docs/source/conf.py b/docs/source/conf.py index 697f9f50..0807b9f0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -34,9 +34,39 @@ # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output +html_title = project html_theme = "sphinxawesome_theme" html_theme_options = { "show_scrolltop": True, + "extra_header_link_icons": { + "repository on GitHub": { + "link": "https://github.com/judge0/judge0-python", + "icon": ( + '' + '' + ), + }, + }, } html_show_sphinx = False html_sidebars = { @@ -46,6 +76,8 @@ "versioning.html", ], } +html_logo = "../assets/logo.png" +html_favicon = html_logo pygments_style = "sphinx" sys.path.insert(0, os.path.abspath("../../src/")) # Adjust as needed diff --git a/docs/source/index.rst b/docs/source/index.rst index 0db57436..a4345356 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,6 +1,6 @@ -=============================== -Judge0 Python SDK documentation -=============================== +================= +Judge0 Python SDK +================= Getting Started =============== From fa0c502daff57d9617d1370d9b83dfe6b4a33bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 31 Dec 2024 14:27:21 +0100 Subject: [PATCH 095/161] Update release notes guide. --- .../contributors_guide/release_notes.rst | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/source/contributors_guide/release_notes.rst b/docs/source/contributors_guide/release_notes.rst index ec66b1c6..da50b25b 100644 --- a/docs/source/contributors_guide/release_notes.rst +++ b/docs/source/contributors_guide/release_notes.rst @@ -1,4 +1,32 @@ How to create a release ======================= -TODO \ No newline at end of file +Creating a release is a simple process that involves a few steps: + +#. **Prepare the release**: + #. Create a separate branch for the release. Name the branch ``release-x.y.z`` + where ``x.y.z`` is the version number. + #. Update the version number in ``judge0/__init__.py``. + #. Update the version number in ``judge0/pyproject.toml``. + #. Sync the branch with any changes from the master branch. + #. Create a pull request for the release branch. Make sure that all tests pass. + #. Merge the pull request. + #. Pull the changes to your local repository and tag the commit (``git tag vX.Y.Z``) with the version number. + #. Push the tags to the remote repository (``git push origin master --tags``). +#. **Create release (notes) on GitHub**. + #. Go to the `releases page `_ on GitHub. + #. Release title should be ``Judge0 Python SDK vX.Y.Z``. + #. Release notes should include a changes from the previous release to the newest release. + #. Use the `template `_ from the repo to organize the changes. + #. Create the release. ("Set as a pre-release" should NOT be checked.) +#. **Release on PyPI**: + #. Use the `GitHub Actions workflow `_ to create a release on PyPI. + #. Select `Run workflow` and as `Target repository` select `pypi`. + #. Click the `Run workflow` button. + +After the release is successfully published on PyPI, create a new pull request +that updates the working version in ``judge0/__init__.py`` and ``judge0/pyproject.toml`` +to the minor version. Merge the pull request and you're done! For example, if the +new release was ``1.2.2``, the working version should be updated to ``1.3.0.dev0``. + +You've successfully created a release! Congratulations! 🎉 \ No newline at end of file From 9fc2353a41ec571128cb6540d633c3ea6f3a79ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Tue, 31 Dec 2024 15:23:29 +0100 Subject: [PATCH 096/161] Update contributing guide. Update tests to use default client fixtures. --- .../contributors_guide/contributing.rst | 47 +++++++++++---- tests/conftest.py | 60 +++++++++++++++---- tests/test_api_test_cases.py | 6 +- tests/test_submission.py | 8 +-- 4 files changed, 92 insertions(+), 29 deletions(-) diff --git a/docs/source/contributors_guide/contributing.rst b/docs/source/contributors_guide/contributing.rst index 5cee988e..61cc2279 100644 --- a/docs/source/contributors_guide/contributing.rst +++ b/docs/source/contributors_guide/contributing.rst @@ -47,24 +47,47 @@ You'll see a different output since the documentation is build with Testing ------- -If you implemented a feature or fixed a bug, please add tests for it. +.. warning:: + If you are implementing features or fixing bugs, you are expected to have + all of the three API keys (ATD, Sulu, and RapidAPI) setup and set in you + environment variables - ``JUDGE0_SULU_API_KEY``, ``JUDGE0_RAPID_API_KEY``, + and ``JUDGE0_ATD_API_KEY``. -Unfortunately, at the moment you cannot run full test suite because it requires -access to API keys for all implemented API hubs (ATD, Sulu, and RapidAPI) and -a private Judge0 instance. To partially address this situation, you can run and -test your implemented feature and tests locally and use the GitHub CI pipeline -to run the full test suite. +Every bug fix or new feature should have tests for it. The tests are located in +the ``tests`` directory and are written using `pytest `_. -To run the tests locally, you can use the following command: +While working with the tests, you should use the following fixtures: -.. code-block:: console +* ``default_ce_client`` - a client, chosen based on the environment variables set, that uses the CE flavor of the client. +* ``default_extra_ce_client`` - a client, chosen based on the environment variables set, that uses the Extra CE flavor of the client. - $ pytest -svv tests -k '' +The ``default_ce_client`` and ``default_extra_ce_client`` are fixtures that +return a client based on the environment variables set. This enables you to +run the full test suite locally, but also to run the tests on the CI pipeline +without changing the tests. -To make the test compatible with the CI pipeline, you should use one of the -client fixtures: +You can use the fixtures in your tests like this: .. code-block:: python def test_my_test(request): - client = request.getfixturevalue("judge0_ce_client") # or judge0_extra_ce_client + client = request.getfixturevalue("default_ce_client") # or default_extra_ce_client + +To run the tests locally, you can use the following command: + +.. code-block:: console + + $ pytest -svv tests -k '' + +This will enable you to run a single test, without incurring the cost of +running the full test suite. If you want to run the full test suite, you can +use the following command: + +.. code-block:: console + + $ pytest -svv tests + +or you can create a draft PR and let the CI pipeline run the tests for you. +The CI pipeline will run the tests on every PR, using a private instance +of Judge0, so you can be sure that your changes are not breaking the existing +functionality. diff --git a/tests/conftest.py b/tests/conftest.py index b1bd6120..0ceddb23 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,11 +13,15 @@ def judge0_ce_client(): api_key = os.getenv("JUDGE0_TEST_API_KEY") api_key_header = os.getenv("JUDGE0_TEST_API_KEY_HEADER") endpoint = os.getenv("JUDGE0_TEST_CE_ENDPOINT") - client = clients.Client( - endpoint=endpoint, - auth_headers={api_key_header: api_key}, - ) - return client + + if api_key is None or api_key_header is None or endpoint is None: + return None + else: + client = clients.Client( + endpoint=endpoint, + auth_headers={api_key_header: api_key}, + ) + return client @pytest.fixture(scope="session") @@ -25,11 +29,15 @@ def judge0_extra_ce_client(): api_key = os.getenv("JUDGE0_TEST_API_KEY") api_key_header = os.getenv("JUDGE0_TEST_API_KEY_HEADER") endpoint = os.getenv("JUDGE0_TEST_EXTRA_CE_ENDPOINT") - client = clients.Client( - endpoint=endpoint, - auth_headers={api_key_header: api_key}, - ) - return client + + if api_key is None or api_key_header is None or endpoint is None: + return None + else: + client = clients.Client( + endpoint=endpoint, + auth_headers={api_key_header: api_key}, + ) + return client @pytest.fixture(scope="session") @@ -63,6 +71,12 @@ def rapid_extra_ce_client(): @pytest.fixture(scope="session") def sulu_ce_client(): api_key = os.getenv("JUDGE0_SULU_API_KEY") + if api_key is None: + pytest.fail( + "Sulu API key is not available for testing. Make sure to have " + "JUDGE0_SULU_API_KEY in your environment variables." + ) + client = clients.SuluJudge0CE(api_key) return client @@ -70,5 +84,31 @@ def sulu_ce_client(): @pytest.fixture(scope="session") def sulu_extra_ce_client(): api_key = os.getenv("JUDGE0_SULU_API_KEY") + if api_key is None: + pytest.fail( + "Sulu API key is not available for testing. Make sure to have " + "JUDGE0_SULU_API_KEY in your environment variables." + ) + client = clients.SuluJudge0ExtraCE(api_key) return client + + +@pytest.fixture(scope="session") +def default_ce_client(judge0_ce_client, sulu_ce_client): + if judge0_ce_client is not None: + return judge0_ce_client + if sulu_ce_client is not None: + return sulu_ce_client + + pytest.fail("No default CE client available for testing.") + + +@pytest.fixture(scope="session") +def default_extra_ce_client(judge0_extra_ce_client, sulu_extra_ce_client): + if judge0_extra_ce_client is not None: + return judge0_extra_ce_client + if sulu_extra_ce_client is not None: + return sulu_extra_ce_client + + pytest.fail("No default Extra CE client available for testing.") diff --git a/tests/test_api_test_cases.py b/tests/test_api_test_cases.py index 0d08f5f8..c4963e9e 100644 --- a/tests/test_api_test_cases.py +++ b/tests/test_api_test_cases.py @@ -207,7 +207,7 @@ def test_single_test_case_in_iterable(self, test_cases, stdin, expected_output): def test_test_cases_from_run( source_code_or_submissions, test_cases, expected_status, request ): - client = request.getfixturevalue("judge0_ce_client") + client = request.getfixturevalue("default_ce_client") if isinstance(source_code_or_submissions, str): submissions = judge0.run( @@ -254,7 +254,7 @@ def test_test_cases_from_run( ], ) def test_no_test_cases(submissions, expected_status, request): - client = request.getfixturevalue("judge0_ce_client") + client = request.getfixturevalue("default_ce_client") submissions = judge0.run( client=client, @@ -269,7 +269,7 @@ def test_no_test_cases(submissions, expected_status, request): @pytest.mark.parametrize("n_submissions", [42, 84]) def test_batched_test_cases(n_submissions, request): - client = request.getfixturevalue("judge0_ce_client") + client = request.getfixturevalue("default_ce_client") submissions = [ Submission(source_code=f"print({i})", expected_output=f"{i}") for i in range(n_submissions) diff --git a/tests/test_submission.py b/tests/test_submission.py index ddae1401..ec1885b2 100644 --- a/tests/test_submission.py +++ b/tests/test_submission.py @@ -52,7 +52,7 @@ def test_from_json(): def test_status_before_and_after_submission(request): - client = request.getfixturevalue("judge0_ce_client") + client = request.getfixturevalue("default_ce_client") submission = Submission(source_code='print("Hello World!")') assert submission.status is None @@ -65,7 +65,7 @@ def test_status_before_and_after_submission(request): def test_is_done(request): - client = request.getfixturevalue("judge0_ce_client") + client = request.getfixturevalue("default_ce_client") submission = Submission(source_code='print("Hello World!")') assert submission.status is None @@ -77,7 +77,7 @@ def test_is_done(request): def test_language_before_and_after_execution(request): - client = request.getfixturevalue("judge0_ce_client") + client = request.getfixturevalue("default_ce_client") code = """\ public class Main { public static void main(String[] args) { @@ -97,7 +97,7 @@ def test_language_before_and_after_execution(request): def test_language_executable(request): - client = request.getfixturevalue("judge0_ce_client") + client = request.getfixturevalue("default_ce_client") code = b64decode( "f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAAABAAAAAAABAAAAAAAAAAEAQAAAAAAAAAAAAAEAAOAABAEAABAADAAEAAAAFAAAAABAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAJQAAAAAAAAAlAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHAjVANsAG+GABAAInHDwUx/41HPA8FAGhlbGxvLCB3b3JsZAoALnNoc3RydGFiAC50ZXh0AC5yb2RhdGEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAEAAAAGAAAAAAAAAAAAQAAAAAAAABAAAAAAAAAXAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABEAAAABAAAAAgAAAAAAAAAYAEAAAAAAABgQAAAAAAAADQAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAABAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAlEAAAAAAAABkAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA" # noqa: E501 ) From ad9f6e96852287c2814ead9a3992e94c21f6be92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 5 Jan 2025 19:24:25 +0100 Subject: [PATCH 097/161] Add placeholder for client resolution docs. --- docs/source/contributors_guide/contributing.rst | 4 ++++ docs/source/in_depth/client_resolution.rst | 4 ++++ docs/source/index.rst | 12 +++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 docs/source/in_depth/client_resolution.rst diff --git a/docs/source/contributors_guide/contributing.rst b/docs/source/contributors_guide/contributing.rst index 61cc2279..546c6cfe 100644 --- a/docs/source/contributors_guide/contributing.rst +++ b/docs/source/contributors_guide/contributing.rst @@ -41,6 +41,10 @@ Documentation is built using Sphinx. To build the documentation, run the You should inspect the changes in the documentation by opening the ``docs/build/html/index.html`` file in your browser. +.. note:: + If you are having trouble with the documentation and are seeing unexpected + output, delete the ``docs/build`` directory and rerun the ``make html`` command. + You'll see a different output since the documentation is build with `sphinx-multiversion `_ extension. diff --git a/docs/source/in_depth/client_resolution.rst b/docs/source/in_depth/client_resolution.rst new file mode 100644 index 00000000..dfdd5321 --- /dev/null +++ b/docs/source/in_depth/client_resolution.rst @@ -0,0 +1,4 @@ +Client Resolution +================= + +TODO: Describe the approach to client resolution. See `_get_implicit_client`. \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index a4345356..0e3027d0 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -63,6 +63,16 @@ Every contribution, big or small, is valuable! api/submission api/types + +.. toctree:: + :caption: In Depth + :glob: + :titlesonly: + :hidden: + + in_depth/client_resolution + + .. toctree:: :caption: Getting Involved :glob: @@ -70,4 +80,4 @@ Every contribution, big or small, is valuable! :hidden: contributors_guide/contributing - contributors_guide/release_notes \ No newline at end of file + contributors_guide/release_notes From df50bfa9c06c9c9fc90624516c2015737d85b06e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 5 Jan 2025 19:27:31 +0100 Subject: [PATCH 098/161] Add placeholder for API overview. --- docs/source/in_depth/overview.rst | 8 ++++++++ docs/source/index.rst | 1 + 2 files changed, 9 insertions(+) create mode 100644 docs/source/in_depth/overview.rst diff --git a/docs/source/in_depth/overview.rst b/docs/source/in_depth/overview.rst new file mode 100644 index 00000000..d7dd136b --- /dev/null +++ b/docs/source/in_depth/overview.rst @@ -0,0 +1,8 @@ +Overview +======== + +TODO: + +* add a brief overview of the most important classes (Client, Submission, etc.) +* add a brief overview of the most important functions (create_submission, get_submission, etc.) +* write about the difference between high-level api and low-level api \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index 0e3027d0..4f983252 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -70,6 +70,7 @@ Every contribution, big or small, is valuable! :titlesonly: :hidden: + in_depth/overview in_depth/client_resolution From 896ce80b0d44753ff18dd605ef80e9aa583fdaf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 5 Jan 2025 19:41:54 +0100 Subject: [PATCH 099/161] Add main nav links. Add indication of external links. --- docs/source/conf.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 0807b9f0..ea153536 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -67,6 +67,11 @@ ), }, }, + "awesome_external_links": True, + "main_nav_links": { + "Home": "https://judge0.github.io/judge0-python/", + "Judge0": "https://judge0.com/", + }, } html_show_sphinx = False html_sidebars = { From 711b778e39674936b526a5805de133d08ae4c269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 5 Jan 2025 20:40:51 +0100 Subject: [PATCH 100/161] Refactor _get_implict_client. Add ability to load custom clients from get_client. Refactor env variable to read auth headers as dict. --- .github/workflows/test.yml | 8 ++--- src/judge0/__init__.py | 74 +++++++++++++++++++++++++++++--------- tests/conftest.py | 26 +++++--------- 3 files changed, 70 insertions(+), 38 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3c33777d..d68acfc4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,10 +32,10 @@ jobs: JUDGE0_ATD_API_KEY: ${{ secrets.JUDGE0_ATD_API_KEY }} JUDGE0_RAPID_API_KEY: ${{ secrets.JUDGE0_RAPID_API_KEY }} JUDGE0_SULU_API_KEY: ${{ secrets.JUDGE0_SULU_API_KEY }} - JUDGE0_TEST_API_KEY: ${{ secrets.JUDGE0_TEST_API_KEY }} - JUDGE0_TEST_API_KEY_HEADER: ${{ secrets.JUDGE0_TEST_API_KEY_HEADER }} - JUDGE0_TEST_CE_ENDPOINT: ${{ secrets.JUDGE0_TEST_CE_ENDPOINT }} - JUDGE0_TEST_EXTRA_CE_ENDPOINT: ${{ secrets.JUDGE0_TEST_EXTRA_CE_ENDPOINT }} + JUDGE0_CE_AUTH_HEADERS: ${{ secrets.JUDGE0_CE_AUTH_HEADERS }} + JUDGE0_EXTRA_CE_AUTH_HEADERS: ${{ secrets.JUDGE0_EXTRA_CE_AUTH_HEADERS }} + JUDGE0_CE_ENDPOINT: ${{ secrets.JUDGE0_CE_ENDPOINT }} + JUDGE0_EXTRA_CE_ENDPOINT: ${{ secrets.JUDGE0_EXTRA_CE_ENDPOINT }} run: | source venv/bin/activate pytest -vv tests/ diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index 391e926d..0fb78d9f 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -1,5 +1,7 @@ import os +from typing import Union + from .api import ( async_execute, async_run, @@ -73,8 +75,6 @@ def _get_implicit_client(flavor: Flavor) -> Client: if flavor == Flavor.EXTRA_CE and JUDGE0_IMPLICIT_EXTRA_CE_CLIENT is not None: return JUDGE0_IMPLICIT_EXTRA_CE_CLIENT - from .clients import CE, EXTRA_CE - try: from dotenv import load_dotenv @@ -82,27 +82,18 @@ def _get_implicit_client(flavor: Flavor) -> Client: except: # noqa: E722 pass - if flavor == Flavor.CE: - client_classes = CE - else: - client_classes = EXTRA_CE + # Let's check if we can find a self-hosted client. + client = _get_custom_client(flavor) # Try to find one of the predefined keys JUDGE0_{SULU,RAPID,ATD}_API_KEY # in environment variables. - client = None - for client_class in client_classes: - api_key = os.getenv(client_class.API_KEY_ENV) - if api_key is not None: - client = client_class(api_key) - break + if client is None: + client = _get_predefined_client(flavor) # If we didn't find any of the possible predefined keys, initialize # the preview Sulu client based on the flavor. if client is None: - if flavor == Flavor.CE: - client = SuluJudge0CE(retry_strategy=RegularPeriodRetry(0.5)) - else: - client = SuluJudge0ExtraCE(retry_strategy=RegularPeriodRetry(0.5)) + client = _get_preview_client(flavor) if flavor == Flavor.CE: JUDGE0_IMPLICIT_CE_CLIENT = client @@ -112,6 +103,57 @@ def _get_implicit_client(flavor: Flavor) -> Client: return client +def _get_preview_client(flavor: Flavor) -> Union[SuluJudge0CE, SuluJudge0ExtraCE]: + if flavor == Flavor.CE: + return SuluJudge0CE(retry_strategy=RegularPeriodRetry(0.5)) + else: + return SuluJudge0ExtraCE(retry_strategy=RegularPeriodRetry(0.5)) + + +def _get_custom_client(flavor: Flavor) -> Union[Client, None]: + ce_endpoint = os.getenv("JUDGE0_CE_ENDPOINT") + ce_auth_header = os.getenv("JUDGE0_CE_AUTH_HEADERS") + extra_ce_endpoint = os.getenv("JUDGE0_EXTRA_CE_ENDPOINT") + extra_ce_auth_header = os.getenv("JUDGE0_EXTRA_CE_AUTH_HEADERS") + + if flavor == Flavor.CE and ce_endpoint is not None and ce_auth_header is not None: + return Client( + endpoint=ce_endpoint, + auth_headers=eval(ce_auth_header), + ) + + if ( + flavor == Flavor.EXTRA_CE + and extra_ce_endpoint is not None + and extra_ce_auth_header is not None + ): + return Client( + endpoint=extra_ce_endpoint, + auth_headers=eval(extra_ce_auth_header), + ) + + return None + + +def _get_predefined_client(flavor: Flavor) -> Union[Client, None]: + from .clients import CE, EXTRA_CE + + if flavor == Flavor.CE: + client_classes = CE + else: + client_classes = EXTRA_CE + + for client_class in client_classes: + api_key = os.getenv(client_class.API_KEY_ENV) + if api_key is not None: + client = client_class(api_key) + break + else: + client = None + + return client + + CE = Flavor.CE EXTRA_CE = Flavor.EXTRA_CE diff --git a/tests/conftest.py b/tests/conftest.py index 0ceddb23..d06dcb2b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,34 +10,24 @@ @pytest.fixture(scope="session") def judge0_ce_client(): - api_key = os.getenv("JUDGE0_TEST_API_KEY") - api_key_header = os.getenv("JUDGE0_TEST_API_KEY_HEADER") - endpoint = os.getenv("JUDGE0_TEST_CE_ENDPOINT") + endpoint = os.getenv("JUDGE0_CE_ENDPOINT") + auth_headers = os.getenv("JUDGE0_CE_AUTH_HEADERS") - if api_key is None or api_key_header is None or endpoint is None: + if endpoint is None or auth_headers is None: return None else: - client = clients.Client( - endpoint=endpoint, - auth_headers={api_key_header: api_key}, - ) - return client + return clients.Client(endpoint=endpoint, auth_headers=eval(auth_headers)) @pytest.fixture(scope="session") def judge0_extra_ce_client(): - api_key = os.getenv("JUDGE0_TEST_API_KEY") - api_key_header = os.getenv("JUDGE0_TEST_API_KEY_HEADER") - endpoint = os.getenv("JUDGE0_TEST_EXTRA_CE_ENDPOINT") + endpoint = os.getenv("JUDGE0_EXTRA_CE_ENDPOINT") + auth_headers = os.getenv("JUDGE0_EXTRA_CE_AUTH_HEADERS") - if api_key is None or api_key_header is None or endpoint is None: + if endpoint is None or auth_headers is None: return None else: - client = clients.Client( - endpoint=endpoint, - auth_headers={api_key_header: api_key}, - ) - return client + return clients.Client(endpoint=endpoint, auth_headers=eval(auth_headers)) @pytest.fixture(scope="session") From b893b18cabc65c1b18cf81b15284a6db5bfe362f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 5 Jan 2025 20:44:37 +0100 Subject: [PATCH 101/161] Add ability to run test CI from console. --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d68acfc4..991d2d19 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,7 @@ name: Test judge0-python on: + workflow_dispatch: push: branches: ["master"] paths: ["src/**", "tests/**"] From 340e0e01c72700dc7303bf34ae0c77ec1d3c76ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 5 Jan 2025 21:07:12 +0100 Subject: [PATCH 102/161] Use json.loads instead of eval. --- src/judge0/__init__.py | 6 ++++-- tests/conftest.py | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index 0fb78d9f..34de57d4 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -111,6 +111,8 @@ def _get_preview_client(flavor: Flavor) -> Union[SuluJudge0CE, SuluJudge0ExtraCE def _get_custom_client(flavor: Flavor) -> Union[Client, None]: + from json import loads + ce_endpoint = os.getenv("JUDGE0_CE_ENDPOINT") ce_auth_header = os.getenv("JUDGE0_CE_AUTH_HEADERS") extra_ce_endpoint = os.getenv("JUDGE0_EXTRA_CE_ENDPOINT") @@ -119,7 +121,7 @@ def _get_custom_client(flavor: Flavor) -> Union[Client, None]: if flavor == Flavor.CE and ce_endpoint is not None and ce_auth_header is not None: return Client( endpoint=ce_endpoint, - auth_headers=eval(ce_auth_header), + auth_headers=loads(ce_auth_header), ) if ( @@ -129,7 +131,7 @@ def _get_custom_client(flavor: Flavor) -> Union[Client, None]: ): return Client( endpoint=extra_ce_endpoint, - auth_headers=eval(extra_ce_auth_header), + auth_headers=loads(extra_ce_auth_header), ) return None diff --git a/tests/conftest.py b/tests/conftest.py index d06dcb2b..48aea373 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +import json import os import pytest @@ -16,7 +17,7 @@ def judge0_ce_client(): if endpoint is None or auth_headers is None: return None else: - return clients.Client(endpoint=endpoint, auth_headers=eval(auth_headers)) + return clients.Client(endpoint=endpoint, auth_headers=json.loads(auth_headers)) @pytest.fixture(scope="session") @@ -27,7 +28,7 @@ def judge0_extra_ce_client(): if endpoint is None or auth_headers is None: return None else: - return clients.Client(endpoint=endpoint, auth_headers=eval(auth_headers)) + return clients.Client(endpoint=endpoint, auth_headers=json.loads(auth_headers)) @pytest.fixture(scope="session") From bb29acc2325375d80dc29839a99d59b34ecf3f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 5 Jan 2025 21:21:54 +0100 Subject: [PATCH 103/161] Change default language to PYTHON_FOR_ML --- src/judge0/submission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/judge0/submission.py b/src/judge0/submission.py index 55b76602..78d14700 100644 --- a/src/judge0/submission.py +++ b/src/judge0/submission.py @@ -128,7 +128,7 @@ class Submission(BaseModel): source_code: Optional[Union[str, bytes]] = Field(default=None, repr=True) language: Union[LanguageAlias, int] = Field( - default=LanguageAlias.PYTHON, + default=LanguageAlias.PYTHON_FOR_ML, repr=True, ) additional_files: Optional[Union[str, Filesystem]] = Field(default=None, repr=True) From 56f695359025bc01d9a37709c6ee46060235c44f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 5 Jan 2025 21:45:59 +0100 Subject: [PATCH 104/161] Rename fixtures. Refactor tests to explicitly set Submission language. --- .../contributors_guide/contributing.rst | 8 ++-- src/judge0/__init__.py | 10 ++--- tests/conftest.py | 38 +++++++++++++----- tests/test_api_test_cases.py | 39 ++++++++++++++----- tests/test_submission.py | 16 +++++--- 5 files changed, 77 insertions(+), 34 deletions(-) diff --git a/docs/source/contributors_guide/contributing.rst b/docs/source/contributors_guide/contributing.rst index 546c6cfe..552471d5 100644 --- a/docs/source/contributors_guide/contributing.rst +++ b/docs/source/contributors_guide/contributing.rst @@ -62,10 +62,10 @@ the ``tests`` directory and are written using `pytest Client: # Let's check if we can find a self-hosted client. client = _get_custom_client(flavor) - # Try to find one of the predefined keys JUDGE0_{SULU,RAPID,ATD}_API_KEY - # in environment variables. + # Try to find one of the API keys JUDGE0_{SULU,RAPID,ATD}_API_KEY + # for hub clients. if client is None: - client = _get_predefined_client(flavor) + client = _get_hub_client(flavor) - # If we didn't find any of the possible predefined keys, initialize + # If we didn't find any of the possible keys, initialize # the preview Sulu client based on the flavor. if client is None: client = _get_preview_client(flavor) @@ -137,7 +137,7 @@ def _get_custom_client(flavor: Flavor) -> Union[Client, None]: return None -def _get_predefined_client(flavor: Flavor) -> Union[Client, None]: +def _get_hub_client(flavor: Flavor) -> Union[Client, None]: from .clients import CE, EXTRA_CE if flavor == Flavor.CE: diff --git a/tests/conftest.py b/tests/conftest.py index 48aea373..c1684352 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,7 @@ @pytest.fixture(scope="session") -def judge0_ce_client(): +def custom_ce_client(): endpoint = os.getenv("JUDGE0_CE_ENDPOINT") auth_headers = os.getenv("JUDGE0_CE_AUTH_HEADERS") @@ -21,7 +21,7 @@ def judge0_ce_client(): @pytest.fixture(scope="session") -def judge0_extra_ce_client(): +def custom_extra_ce_client(): endpoint = os.getenv("JUDGE0_EXTRA_CE_ENDPOINT") auth_headers = os.getenv("JUDGE0_EXTRA_CE_AUTH_HEADERS") @@ -86,20 +86,38 @@ def sulu_extra_ce_client(): @pytest.fixture(scope="session") -def default_ce_client(judge0_ce_client, sulu_ce_client): - if judge0_ce_client is not None: - return judge0_ce_client +def ce_client( + custom_ce_client, + sulu_ce_client, + rapid_ce_client, + atd_ce_client, +): + if custom_ce_client is not None: + return custom_ce_client if sulu_ce_client is not None: return sulu_ce_client + if rapid_ce_client is not None: + return rapid_ce_client + if atd_ce_client is not None: + return atd_ce_client - pytest.fail("No default CE client available for testing.") + pytest.fail("No CE client available for testing.") @pytest.fixture(scope="session") -def default_extra_ce_client(judge0_extra_ce_client, sulu_extra_ce_client): - if judge0_extra_ce_client is not None: - return judge0_extra_ce_client +def extra_ce_client( + custom_extra_ce_client, + sulu_extra_ce_client, + rapid_extra_ce_client, + atd_extra_ce_client, +): + if custom_extra_ce_client is not None: + return custom_extra_ce_client if sulu_extra_ce_client is not None: return sulu_extra_ce_client + if rapid_extra_ce_client is not None: + return rapid_extra_ce_client + if atd_extra_ce_client is not None: + return atd_extra_ce_client - pytest.fail("No default Extra CE client available for testing.") + pytest.fail("No Extra CE client available for testing.") diff --git a/tests/test_api_test_cases.py b/tests/test_api_test_cases.py index c4963e9e..8dea1e6a 100644 --- a/tests/test_api_test_cases.py +++ b/tests/test_api_test_cases.py @@ -2,8 +2,9 @@ import judge0 import pytest -from judge0 import Status, Submission, TestCase from judge0.api import create_submissions_from_test_cases +from judge0.base_types import LanguageAlias, Status, TestCase +from judge0.submission import Submission @pytest.mark.parametrize( @@ -172,14 +173,20 @@ def test_single_test_case_in_iterable(self, test_cases, stdin, expected_output): [Status.ACCEPTED, Status.ACCEPTED], ], [ - Submission(source_code="print(f'Hello, {input()}')"), + Submission( + source_code="print(f'Hello, {input()}')", + language=LanguageAlias.PYTHON, + ), [ TestCase("Judge0", "Hello, Judge0"), ], [Status.ACCEPTED], ], [ - Submission(source_code="print(f'Hello, {input()}')"), + Submission( + source_code="print(f'Hello, {input()}')", + language=LanguageAlias.PYTHON, + ), [ TestCase("Judge0", "Hello, Judge0"), TestCase("pytest", "Hi, pytest"), @@ -188,8 +195,14 @@ def test_single_test_case_in_iterable(self, test_cases, stdin, expected_output): ], [ [ - Submission(source_code="print(f'Hello, {input()}')"), - Submission(source_code="print(f'Hello, {input()}')"), + Submission( + source_code="print(f'Hello, {input()}')", + language=LanguageAlias.PYTHON, + ), + Submission( + source_code="print(f'Hello, {input()}')", + language=LanguageAlias.PYTHON, + ), ], [ TestCase("Judge0", "Hello, Judge0"), @@ -207,13 +220,14 @@ def test_single_test_case_in_iterable(self, test_cases, stdin, expected_output): def test_test_cases_from_run( source_code_or_submissions, test_cases, expected_status, request ): - client = request.getfixturevalue("default_ce_client") + client = request.getfixturevalue("ce_client") if isinstance(source_code_or_submissions, str): submissions = judge0.run( client=client, source_code=source_code_or_submissions, test_cases=test_cases, + language=LanguageAlias.PYTHON, ) else: submissions = judge0.run( @@ -231,6 +245,7 @@ def test_test_cases_from_run( [ Submission( source_code="print(f'Hello, {input()}')", + language=LanguageAlias.PYTHON, stdin="Judge0", expected_output="Hello, Judge0", ), @@ -240,11 +255,13 @@ def test_test_cases_from_run( [ Submission( source_code="print(f'Hello, {input()}')", + language=LanguageAlias.PYTHON, stdin="Judge0", expected_output="Hello, Judge0", ), Submission( source_code="print(f'Hello, {input()}')", + language=LanguageAlias.PYTHON, stdin="pytest", expected_output="Hello, pytest", ), @@ -254,7 +271,7 @@ def test_test_cases_from_run( ], ) def test_no_test_cases(submissions, expected_status, request): - client = request.getfixturevalue("default_ce_client") + client = request.getfixturevalue("ce_client") submissions = judge0.run( client=client, @@ -269,9 +286,13 @@ def test_no_test_cases(submissions, expected_status, request): @pytest.mark.parametrize("n_submissions", [42, 84]) def test_batched_test_cases(n_submissions, request): - client = request.getfixturevalue("default_ce_client") + client = request.getfixturevalue("ce_client") submissions = [ - Submission(source_code=f"print({i})", expected_output=f"{i}") + Submission( + source_code=f"print({i})", + language=LanguageAlias.PYTHON, + expected_output=f"{i}", + ) for i in range(n_submissions) ] diff --git a/tests/test_submission.py b/tests/test_submission.py index ec1885b2..86750344 100644 --- a/tests/test_submission.py +++ b/tests/test_submission.py @@ -52,8 +52,10 @@ def test_from_json(): def test_status_before_and_after_submission(request): - client = request.getfixturevalue("default_ce_client") - submission = Submission(source_code='print("Hello World!")') + client = request.getfixturevalue("ce_client") + submission = Submission( + source_code='print("Hello World!")', language=LanguageAlias.PYTHON + ) assert submission.status is None @@ -65,8 +67,10 @@ def test_status_before_and_after_submission(request): def test_is_done(request): - client = request.getfixturevalue("default_ce_client") - submission = Submission(source_code='print("Hello World!")') + client = request.getfixturevalue("ce_client") + submission = Submission( + source_code='print("Hello World!")', language=LanguageAlias.PYTHON + ) assert submission.status is None @@ -77,7 +81,7 @@ def test_is_done(request): def test_language_before_and_after_execution(request): - client = request.getfixturevalue("default_ce_client") + client = request.getfixturevalue("ce_client") code = """\ public class Main { public static void main(String[] args) { @@ -97,7 +101,7 @@ def test_language_before_and_after_execution(request): def test_language_executable(request): - client = request.getfixturevalue("default_ce_client") + client = request.getfixturevalue("ce_client") code = b64decode( "f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAAABAAAAAAABAAAAAAAAAAEAQAAAAAAAAAAAAAEAAOAABAEAABAADAAEAAAAFAAAAABAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAJQAAAAAAAAAlAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHAjVANsAG+GABAAInHDwUx/41HPA8FAGhlbGxvLCB3b3JsZAoALnNoc3RydGFiAC50ZXh0AC5yb2RhdGEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAEAAAAGAAAAAAAAAAAAQAAAAAAAABAAAAAAAAAXAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABEAAAABAAAAAgAAAAAAAAAYAEAAAAAAABgQAAAAAAAADQAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAABAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAlEAAAAAAAABkAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA" # noqa: E501 ) From 4a2e4f14341222df9bab335702a3a23452ad8a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 5 Jan 2025 22:14:35 +0100 Subject: [PATCH 105/161] Refactor fixtures. Add preview client as possible fixture for ce and extra ce client. --- tests/conftest.py | 72 +++++++++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c1684352..4e1547c5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ import pytest from dotenv import load_dotenv -from judge0 import clients +from judge0 import clients, RegularPeriodRetry load_dotenv() @@ -34,55 +34,71 @@ def custom_extra_ce_client(): @pytest.fixture(scope="session") def atd_ce_client(): api_key = os.getenv("JUDGE0_ATD_API_KEY") - client = clients.ATDJudge0CE(api_key) - return client + + if api_key is None: + return None + else: + return clients.ATDJudge0CE(api_key) @pytest.fixture(scope="session") def atd_extra_ce_client(): api_key = os.getenv("JUDGE0_ATD_API_KEY") - client = clients.ATDJudge0ExtraCE(api_key) - return client + + if api_key is None: + return None + else: + return clients.ATDJudge0ExtraCE(api_key) @pytest.fixture(scope="session") def rapid_ce_client(): api_key = os.getenv("JUDGE0_RAPID_API_KEY") - client = clients.RapidJudge0CE(api_key) - return client + + if api_key is None: + return None + else: + return clients.RapidJudge0CE(api_key) @pytest.fixture(scope="session") def rapid_extra_ce_client(): api_key = os.getenv("JUDGE0_RAPID_API_KEY") - client = clients.RapidJudge0ExtraCE(api_key) - return client + + if api_key is None: + return None + else: + return clients.RapidJudge0ExtraCE(api_key) @pytest.fixture(scope="session") def sulu_ce_client(): api_key = os.getenv("JUDGE0_SULU_API_KEY") - if api_key is None: - pytest.fail( - "Sulu API key is not available for testing. Make sure to have " - "JUDGE0_SULU_API_KEY in your environment variables." - ) - client = clients.SuluJudge0CE(api_key) - return client + if api_key is None: + return None + else: + return clients.SuluJudge0CE(api_key) @pytest.fixture(scope="session") def sulu_extra_ce_client(): api_key = os.getenv("JUDGE0_SULU_API_KEY") + if api_key is None: - pytest.fail( - "Sulu API key is not available for testing. Make sure to have " - "JUDGE0_SULU_API_KEY in your environment variables." - ) + return None + else: + return clients.SuluJudge0ExtraCE(api_key) - client = clients.SuluJudge0ExtraCE(api_key) - return client + +@pytest.fixture(scope="session") +def preview_ce_client() -> clients.SuluJudge0CE: + return clients.SuluJudge0CE(retry_strategy=RegularPeriodRetry(0.5)) + + +@pytest.fixture(scope="session") +def preview_extra_ce_client() -> clients.SuluJudge0ExtraCE: + return clients.SuluJudge0ExtraCE(retry_strategy=RegularPeriodRetry(0.5)) @pytest.fixture(scope="session") @@ -91,6 +107,7 @@ def ce_client( sulu_ce_client, rapid_ce_client, atd_ce_client, + preview_ce_client, ): if custom_ce_client is not None: return custom_ce_client @@ -100,8 +117,10 @@ def ce_client( return rapid_ce_client if atd_ce_client is not None: return atd_ce_client + if preview_ce_client is not None: + return preview_ce_client - pytest.fail("No CE client available for testing.") + pytest.fail("No CE client available for testing. This error should not happen!") @pytest.fixture(scope="session") @@ -110,6 +129,7 @@ def extra_ce_client( sulu_extra_ce_client, rapid_extra_ce_client, atd_extra_ce_client, + preview_extra_ce_client, ): if custom_extra_ce_client is not None: return custom_extra_ce_client @@ -119,5 +139,9 @@ def extra_ce_client( return rapid_extra_ce_client if atd_extra_ce_client is not None: return atd_extra_ce_client + if preview_extra_ce_client is not None: + return preview_extra_ce_client - pytest.fail("No Extra CE client available for testing.") + pytest.fail( + "No Extra CE client available for testing. This error should not happen!" + ) From 57050fab0b53ad082eea5eca7d9af9e4b8500b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 5 Jan 2025 22:31:46 +0100 Subject: [PATCH 106/161] Move docs requirements to pyproject.toml --- .github/workflows/docs.yml | 8 ++++---- docs/requirements.txt | 4 ---- docs/source/contributors_guide/contributing.rst | 5 ++--- pyproject.toml | 11 ++++++++++- 4 files changed, 16 insertions(+), 12 deletions(-) delete mode 100644 docs/requirements.txt diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4a70709c..df34c7f2 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,10 +17,10 @@ jobs: persist-credentials: false fetch-depth: 0 # Fetch the full history ref: ${{ github.ref }} # Check out the current branch or tag - + - name: Fetch tags only run: git fetch --tags --no-recurse-submodules - + - name: Set up Python uses: actions/setup-python@v4 with: @@ -29,7 +29,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r docs/requirements.txt + pip install -e [docs] - name: Build documentation run: sphinx-multiversion docs/source docs/build/html --keep-going --no-color @@ -41,7 +41,7 @@ jobs: # Get the latest tag latest_tag=$(git tag --sort=-creatordate | head -n 1) echo "LATEST_RELEASE=$latest_tag" >> $GITHUB_ENV - + - name: Generate index.html for judge0.github.io/judge0-python. run: | echo ' diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index cd3bc0f8..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -sphinx==7.4.7 -sphinxawesome-theme==5.3.2 -sphinx-autodoc-typehints==2.3.0 -sphinx-multiversion==0.2.4 \ No newline at end of file diff --git a/docs/source/contributors_guide/contributing.rst b/docs/source/contributors_guide/contributing.rst index 552471d5..194834cf 100644 --- a/docs/source/contributors_guide/contributing.rst +++ b/docs/source/contributors_guide/contributing.rst @@ -24,8 +24,7 @@ Preparing the development setup .. code-block:: console - $ pip install -e .[test] - $ pip install -r docs/requirements.txt # needed for building the docs + $ pip install -e [dev] $ pre-commit install Building documentation @@ -73,7 +72,7 @@ without changing the tests. You can use the fixtures in your tests like this: .. code-block:: python - + def test_my_test(request): client = request.getfixturevalue("ce_client") # or extra_ce_client diff --git a/pyproject.toml b/pyproject.toml index 4974a30a..1fc29ce9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,16 @@ test = [ "pytest-cov==6.0.0", "flake8-docstrings==1.7.0", ] -docs = ["sphinx==7.4.7"] +docs = [ + "sphinx==7.4.7", + "sphinxawesome-theme==5.3.2", + "sphinx-autodoc-typehints==2.3.0", + "sphinx-multiversion==0.2.4", +] +dev = [ + "judge0[test]", + "judge0[docs]", +] [tool.flake8] extend-ignore = [ From a06164c7bfd8060670e0182978465f97a24887cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 5 Jan 2025 22:37:07 +0100 Subject: [PATCH 107/161] Fix dependency installation in workflow and in the docs --- .github/workflows/docs.yml | 2 +- docs/source/contributors_guide/contributing.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index df34c7f2..11d9d8e3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -29,7 +29,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e [docs] + pip install -e .[docs] - name: Build documentation run: sphinx-multiversion docs/source docs/build/html --keep-going --no-color diff --git a/docs/source/contributors_guide/contributing.rst b/docs/source/contributors_guide/contributing.rst index 194834cf..094577c2 100644 --- a/docs/source/contributors_guide/contributing.rst +++ b/docs/source/contributors_guide/contributing.rst @@ -24,7 +24,7 @@ Preparing the development setup .. code-block:: console - $ pip install -e [dev] + $ pip install -e .[dev] $ pre-commit install Building documentation From 7974f90f5a17f676ee4eaf24457c21bc230ba93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 5 Jan 2025 22:39:33 +0100 Subject: [PATCH 108/161] Remove newline from workflow file --- .github/workflows/publish.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 35cd8dd4..0a4e3e38 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -46,4 +46,3 @@ jobs: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} run: twine upload dist/* - From 9f92d2b2fbda78100bce50bf2263de8495d46b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 5 Jan 2025 23:05:30 +0100 Subject: [PATCH 109/161] Add pytest ini_options. Fix skipped test. --- .github/workflows/test.yml | 2 +- docs/source/contributors_guide/contributing.rst | 4 ++-- pyproject.toml | 3 +++ src/judge0/api.py | 7 +++++-- src/judge0/base_types.py | 2 ++ tests/test_api.py | 1 - 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 991d2d19..2beadbd3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,4 +39,4 @@ jobs: JUDGE0_EXTRA_CE_ENDPOINT: ${{ secrets.JUDGE0_EXTRA_CE_ENDPOINT }} run: | source venv/bin/activate - pytest -vv tests/ + pytest tests diff --git a/docs/source/contributors_guide/contributing.rst b/docs/source/contributors_guide/contributing.rst index 094577c2..867f3f14 100644 --- a/docs/source/contributors_guide/contributing.rst +++ b/docs/source/contributors_guide/contributing.rst @@ -80,7 +80,7 @@ To run the tests locally, you can use the following command: .. code-block:: console - $ pytest -svv tests -k '' + $ pytest tests -k '' This will enable you to run a single test, without incurring the cost of running the full test suite. If you want to run the full test suite, you can @@ -88,7 +88,7 @@ use the following command: .. code-block:: console - $ pytest -svv tests + $ pytest tests or you can create a draft PR and let the CI pipeline run the tests for you. The CI pipeline will run the tests on every PR, using a private instance diff --git a/pyproject.toml b/pyproject.toml index 1fc29ce9..e8a4b3d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,3 +72,6 @@ extend-ignore = [ ] docstring-convention = "numpy" max-line-length = 88 + +[tool.pytest.ini_options] +addopts = "-vv" diff --git a/src/judge0/api.py b/src/judge0/api.py index 6c084713..e83ecf78 100644 --- a/src/judge0/api.py +++ b/src/judge0/api.py @@ -67,8 +67,11 @@ def _resolve_client( if isinstance(client, Flavor): return get_client(client) - if client is None and isinstance(submissions, Iterable) and len(submissions) == 0: - raise ValueError("Client cannot be determined from empty submissions.") + if client is None: + if ( + isinstance(submissions, Iterable) and len(submissions) == 0 + ) or submissions is None: + raise ValueError("Client cannot be determined from empty submissions.") # client is None and we have to determine a flavor of the client from the # the submission's languages. diff --git a/src/judge0/base_types.py b/src/judge0/base_types.py index dc18bdd7..94cedf83 100644 --- a/src/judge0/base_types.py +++ b/src/judge0/base_types.py @@ -16,6 +16,8 @@ class TestCase: """Test case data model.""" + __test__ = False # Needed to avoid pytest warning + input: Optional[str] = None expected_output: Optional[str] = None diff --git a/tests/test_api.py b/tests/test_api.py index 50a14644..3ba95267 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -48,7 +48,6 @@ def test_resolve_client_with_flavor( None, ], ) -@pytest.mark.skip def test_resolve_client_empty_submissions_argument(submissions): with pytest.raises(ValueError): _resolve_client(submissions=submissions) From ad8f8e04ee49949df67047d23166e75067d71ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 21 Jul 2025 16:31:40 +0200 Subject: [PATCH 110/161] Update README with Quick Start --- README.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/README.md b/README.md index de2703eb..4b0b8936 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,73 @@ # Judge0 Python SDK The official Python SDK for Judge0. +```python +>>> import judge0 +>>> result = judge0.run(source_code="print('hello, world')") +>>> result.stdout +'hello, world\n' +>>> result.time +0.987 +>>> result.memory +52440 +>>> for f in result: +... f.name +... f.content +... +'script.py' +b"print('hello, world')" +``` + +## Installation + +```bash +pip install judge0 +``` + +### Requirements + +- Python 3.9+ + +## Quick Start + +### Getting The API Key + +Get your API key from [Sulu](https://platform.sulu.sh/apis/judge0), [Rapid](https://rapidapi.com/organization/judge0), or [ATD](https://www.allthingsdev.co/publisher/profile/Herman%20Zvonimir%20Do%C5%A1ilovi%C4%87). + +#### Notes + +* [**Recommended**] Choose Sulu if you need pay-as-you-go (pey-per-use) pricing. +* Choose Rapid or ATD if you need a quota-based (volume-based) plan. +* Judge0 has two flavors: Judge0 CE and Judge0 Extra CE, and their difference is just in the languages they support. When choosing Sulu, your API key will work for both flavors, but for Rapid and ATD you will need to explicitly subscribe to both flavors if you want to use both. + +### Using Your API Key + +#### Option 1: Explicit Client Object + +Explicitly create a client object with your API key and pass it to Judge0 Python SDK functions. + +```python +import judge0 +client = judge0.SuluJudge0CE(api_key="xxx") +result = judge0.run(client=client, source_code="print('hello, world')") +print(result.stdout) +``` + +Other options include: +- `judge0.RapidJudge0CE` +- `judge0.ATDJudge0CE` +- `judge0.SuluJudge0ExtraCE` +- `judge0.RapidJudge0ExtraCE` +- `judge0.ATDJudge0ExtraCE` + +#### Option 2: Implicit Client Object + +Put your API key in one of the following environment variables, respectable to the provider that issued you the API key: `JUDGE0_SULU_API_KEY`, `JUDGE0_RAPID_API_KEY`, or `JUDGE0_ATD_API_KEY`. + +Judge0 Python SDK will automatically detect the environment variable and use it to create a client object that will be used for all API calls if you do not explicitly pass a client object. + +```python +import judge0 +result = judge0.run(source_code="print('hello, world')") +print(result.stdout) +``` From 0be30800f0d002b102a8977a3aba0cf38216e779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 21 Jul 2025 16:49:17 +0200 Subject: [PATCH 111/161] Add Examples --- README.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/README.md b/README.md index 4b0b8936..a9f17468 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,93 @@ import judge0 result = judge0.run(source_code="print('hello, world')") print(result.stdout) ``` + +## Examples + +### Running C Programming Language + +```python +import judge0 + +source_code = """ +#include + +int main() { + printf("hello, world\\n"); + return 0; +} +""" + +result = judge0.run(source_code=source_code, language=judge0.C) +print(result.stdout) +``` + +### Running Java Programming Language + +```python +import judge0 + +source_code = """ +public class Main { + public static void main(String[] args) { + System.out.println("hello, world"); + } +} +""" + +result = judge0.run(source_code=source_code, language=judge0.JAVA) +print(result.stdout) +``` + +### Reading From Standard Input + +```python +import judge0 + +source_code = """ +#include + +int main() { + int a, b; + scanf("%d %d", &a, &b); + printf("%d\\n", a + b); + + char name[10]; + scanf("%s", name); + printf("Hello, %s!\\n", name); + + return 0; +} +""" + +stdin = """ +3 5 +Bob +""" + +result = judge0.run(source_code=source_code, stdin=stdin, language=judge0.C) +print(result.stdout) +``` + +### Test Cases + +```python +import judge0 + +results = judge0.run( + source_code="print(f'Hello, {input()}!')", + test_cases=[ + ("Bob", "Hello, Bob!"), # Test Case #1. Tuple with first value as standard input, second value as expected output. + { # Test Case #2. Dictionary with "input" and "expected_output" keys. + "input": "Alice", + "expected_output": "Hello, Alice!" + }, + ["Charlie", "Hello, Charlie!"], # Test Case #3. List with first value as standard input and second value as expected output. + ], +) + +for i, result in enumerate(results): + print(f"--- Test Case #{i + 1} ---") + print(result.stdout) + print(result.status) +``` From d41a05e88fc53a628c102a1530f3eabf822d3c5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 21 Jul 2025 17:49:30 +0200 Subject: [PATCH 112/161] Test Cases And Multiple Languages --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index a9f17468..8c2ef390 100644 --- a/README.md +++ b/README.md @@ -161,3 +161,47 @@ for i, result in enumerate(results): print(result.stdout) print(result.status) ``` + +### Test Cases And Multiple Languages + +```python +import judge0 + +submissions = [ + judge0.Submission( + source_code="print(f'Hello, {input()}!')", + language=judge0.PYTHON, + ), + judge0.Submission( + source_code=""" +#include + +int main() { + char name[10]; + scanf("%s", name); + printf("Hello, %s!\\n", name); + return 0; +} +""", + language=judge0.C, + ), +] + +test_cases=[ + ("Bob", "Hello, Bob!"), + ("Alice", "Hello, Alice!"), + ("Charlie", "Hello, Charlie!"), +] + +results = judge0.run(submissions=submissions, test_cases=test_cases) + +for i in range(len(submissions)): + print(f"--- Submission #{i + 1} ---") + + for j in range(len(test_cases)): + result = results[i * len(test_cases) + j] + + print(f"--- Test Case #{j + 1} ---") + print(result.stdout) + print(result.status) +``` From 428b01c0457465f43516a8b5007fb3e815d6401d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 21 Jul 2025 17:55:37 +0200 Subject: [PATCH 113/161] Add example for Asynchronous Execution --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 8c2ef390..2905bcc2 100644 --- a/README.md +++ b/README.md @@ -205,3 +205,16 @@ for i in range(len(submissions)): print(result.stdout) print(result.status) ``` + +### Asynchronous Execution + +```python +import judge0 + +submission = judge0.async_run(source_code="print('hello, world')") +print(submission.stdout) # Prints 'None' + +judge0.wait(submissions=submission) # Wait for the submission to finish. + +print(submission.stdout) # Prints 'hello, world' +``` From 8a0563093a0205707d504432cf8100350c733242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 21 Jul 2025 17:58:42 +0200 Subject: [PATCH 114/161] Add Get Languages example --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 2905bcc2..722a0c55 100644 --- a/README.md +++ b/README.md @@ -218,3 +218,11 @@ judge0.wait(submissions=submission) # Wait for the submission to finish. print(submission.stdout) # Prints 'hello, world' ``` + +### Get Languages + +```python +import judge0 +client = judge0.get_client() +print(client.get_languages()) +``` From cd0680608c407a4d81cdbe6b6ac054c8743981f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sat, 26 Jul 2025 16:51:49 +0200 Subject: [PATCH 115/161] Update latest languages for Judge0 CE v1.14.0 --- src/judge0/data.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/judge0/data.py b/src/judge0/data.py index 39ad1b32..4e1aeec8 100644 --- a/src/judge0/data.py +++ b/src/judge0/data.py @@ -92,7 +92,7 @@ LanguageAlias.CPP_GCC: 105, LanguageAlias.CSHARP: 51, LanguageAlias.CSHARP_MONO: 51, - LanguageAlias.C_CLANG: 104, + LanguageAlias.C_CLANG: 110, LanguageAlias.C_GCC: 103, LanguageAlias.D: 56, LanguageAlias.DART: 90, @@ -101,7 +101,7 @@ LanguageAlias.EXECUTABLE: 44, LanguageAlias.FORTRAN: 59, LanguageAlias.FSHARP: 87, - LanguageAlias.GO: 95, + LanguageAlias.GO: 107, LanguageAlias.GROOVY: 88, LanguageAlias.HASKELL: 61, LanguageAlias.JAVA: 62, @@ -109,7 +109,7 @@ LanguageAlias.JAVASCRIPT: 102, LanguageAlias.JAVA_JDK: 91, LanguageAlias.JAVA_OPENJDK: 62, - LanguageAlias.KOTLIN: 78, + LanguageAlias.KOTLIN: 111, LanguageAlias.LUA: 64, LanguageAlias.MULTI_FILE: 89, LanguageAlias.OBJECTIVE_C: 79, @@ -120,12 +120,12 @@ LanguageAlias.PHP: 98, LanguageAlias.PLAIN_TEXT: 43, LanguageAlias.PROLOG: 69, - LanguageAlias.PYTHON: 100, + LanguageAlias.PYTHON: 109, LanguageAlias.PYTHON2: 70, - LanguageAlias.PYTHON3: 100, + LanguageAlias.PYTHON3: 109, LanguageAlias.R: 99, LanguageAlias.RUBY: 72, - LanguageAlias.RUST: 73, + LanguageAlias.RUST: 108, LanguageAlias.SCALA: 81, LanguageAlias.SQLITE: 82, LanguageAlias.SWIFT: 83, From 08ff35193a6fc6c1f5d8746f5581d61297c5e8d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sat, 26 Jul 2025 16:54:35 +0200 Subject: [PATCH 116/161] Update latest languages for Judge0 Extra CE v1.14.0 --- src/judge0/data.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/judge0/data.py b/src/judge0/data.py index 4e1aeec8..ff4ed9ef 100644 --- a/src/judge0/data.py +++ b/src/judge0/data.py @@ -141,9 +141,9 @@ LanguageAlias.CPP_TEST: 12, LanguageAlias.CPP_TEST_CLANG: 15, LanguageAlias.CPP_TEST_GCC: 12, - LanguageAlias.CSHARP: 29, + LanguageAlias.CSHARP: 30, LanguageAlias.CSHARP_MONO: 22, - LanguageAlias.CSHARP_DOTNET: 29, + LanguageAlias.CSHARP_DOTNET: 30, LanguageAlias.CSHARP_TEST: 23, LanguageAlias.C_CLANG: 1, LanguageAlias.FSHARP: 24, @@ -155,12 +155,12 @@ LanguageAlias.MPI_PYTHON: 8, LanguageAlias.MULTI_FILE: 89, LanguageAlias.NIM: 9, - LanguageAlias.PYTHON: 25, + LanguageAlias.PYTHON: 32, LanguageAlias.PYTHON2: 26, LanguageAlias.PYTHON2_PYPY: 26, - LanguageAlias.PYTHON3: 25, + LanguageAlias.PYTHON3: 32, LanguageAlias.PYTHON3_PYPY: 28, - LanguageAlias.PYTHON_FOR_ML: 25, + LanguageAlias.PYTHON_FOR_ML: 32, LanguageAlias.VISUAL_BASIC: 20, }, } From 3080ac3480d7a0bab8cfcc99949ce6def80ad08e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sat, 26 Jul 2025 16:58:55 +0200 Subject: [PATCH 117/161] Prepare for release 0.0.4 --- pyproject.toml | 2 +- src/judge0/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e8a4b3d0..665f9ac2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.1.0.dev0" +version = "0.0.4" description = "The official Python SDK for Judge0." readme = "README.md" requires-python = ">=3.9" diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index f5f33a5a..6e569c40 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -29,7 +29,7 @@ from .retry import MaxRetries, MaxWaitTime, RegularPeriodRetry from .submission import Submission -__version__ = "0.1.0.dev0" +__version__ = "0.0.4" __all__ = [ "ATD", From 802ce9ee52b2c7bcb033c13e39eb8350865bdabb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sat, 26 Jul 2025 17:10:06 +0200 Subject: [PATCH 118/161] Update working version. Update docs for creating a release. --- docs/source/contributors_guide/release_notes.rst | 3 ++- pyproject.toml | 2 +- src/judge0/__init__.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/contributors_guide/release_notes.rst b/docs/source/contributors_guide/release_notes.rst index da50b25b..af4e92ab 100644 --- a/docs/source/contributors_guide/release_notes.rst +++ b/docs/source/contributors_guide/release_notes.rst @@ -18,6 +18,7 @@ Creating a release is a simple process that involves a few steps: #. Release title should be ``Judge0 Python SDK vX.Y.Z``. #. Release notes should include a changes from the previous release to the newest release. #. Use the `template `_ from the repo to organize the changes. + #. Click the `Generate release notes` button and use only `Full Changelog` section. #. Create the release. ("Set as a pre-release" should NOT be checked.) #. **Release on PyPI**: #. Use the `GitHub Actions workflow `_ to create a release on PyPI. @@ -29,4 +30,4 @@ that updates the working version in ``judge0/__init__.py`` and ``judge0/pyproje to the minor version. Merge the pull request and you're done! For example, if the new release was ``1.2.2``, the working version should be updated to ``1.3.0.dev0``. -You've successfully created a release! Congratulations! 🎉 \ No newline at end of file +You've successfully created a release! Congratulations! 🎉 diff --git a/pyproject.toml b/pyproject.toml index 665f9ac2..c869e238 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.0.4" +version = "0.0.5.dev0" description = "The official Python SDK for Judge0." readme = "README.md" requires-python = ">=3.9" diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index 6e569c40..a4686233 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -29,7 +29,7 @@ from .retry import MaxRetries, MaxWaitTime, RegularPeriodRetry from .submission import Submission -__version__ = "0.0.4" +__version__ = "0.0.5.dev0" __all__ = [ "ATD", From de933507326b438dc8f1da63d11572902ae2e47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sat, 26 Jul 2025 18:04:33 +0200 Subject: [PATCH 119/161] Add warning when using preview client --- src/judge0/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index a4686233..3e487e0b 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -1,3 +1,4 @@ +import logging import os from typing import Union @@ -65,6 +66,8 @@ JUDGE0_IMPLICIT_CE_CLIENT = None JUDGE0_IMPLICIT_EXTRA_CE_CLIENT = None +logger = logging.getLogger(__name__) + def _get_implicit_client(flavor: Flavor) -> Client: global JUDGE0_IMPLICIT_CE_CLIENT, JUDGE0_IMPLICIT_EXTRA_CE_CLIENT @@ -104,6 +107,12 @@ def _get_implicit_client(flavor: Flavor) -> Client: def _get_preview_client(flavor: Flavor) -> Union[SuluJudge0CE, SuluJudge0ExtraCE]: + logger.warning( + "You are using a preview version of the client which is not recommended" + " for production.\n" + "For production, please specify your API key in the environment variable." + ) + if flavor == Flavor.CE: return SuluJudge0CE(retry_strategy=RegularPeriodRetry(0.5)) else: From 59175c5d106608c9261cf5d31909e6d58f9b6d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sat, 26 Jul 2025 18:11:24 +0200 Subject: [PATCH 120/161] Use default endpoints for JUDGE0_CE_ENDPOINT and JUDGE0_EXTRA_CE_ENDPOINT --- src/judge0/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index 3e487e0b..4bb3d6c2 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -122,9 +122,11 @@ def _get_preview_client(flavor: Flavor) -> Union[SuluJudge0CE, SuluJudge0ExtraCE def _get_custom_client(flavor: Flavor) -> Union[Client, None]: from json import loads - ce_endpoint = os.getenv("JUDGE0_CE_ENDPOINT") + ce_endpoint = os.getenv("JUDGE0_CE_ENDPOINT", "https://ce.judge0.com") ce_auth_header = os.getenv("JUDGE0_CE_AUTH_HEADERS") - extra_ce_endpoint = os.getenv("JUDGE0_EXTRA_CE_ENDPOINT") + extra_ce_endpoint = os.getenv( + "JUDGE0_EXTRA_CE_ENDPOINT", "https://extra-ce.judge0.com" + ) extra_ce_auth_header = os.getenv("JUDGE0_EXTRA_CE_AUTH_HEADERS") if flavor == Flavor.CE and ce_endpoint is not None and ce_auth_header is not None: From dd3ba0cc1047d8c666e0cdddb0033acb4bbc3cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sat, 26 Jul 2025 18:57:47 +0200 Subject: [PATCH 121/161] Add support for Judge0 Cloud client --- src/judge0/__init__.py | 16 +++++---- src/judge0/clients.py | 76 ++++++++++++++++++++++++++++++++++++++++-- tests/conftest.py | 26 +++++++++++++++ tests/test_clients.py | 2 ++ 4 files changed, 112 insertions(+), 8 deletions(-) diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index 4bb3d6c2..65c842d7 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -19,6 +19,9 @@ ATDJudge0CE, ATDJudge0ExtraCE, Client, + Judge0Cloud, + Judge0CloudCE, + Judge0CloudExtraCE, Rapid, RapidJudge0CE, RapidJudge0ExtraCE, @@ -39,6 +42,9 @@ "Client", "File", "Filesystem", + "Judge0Cloud", + "Judge0CloudCE", + "Judge0CloudExtraCE", "Language", "LanguageAlias", "MaxRetries", @@ -54,12 +60,12 @@ "SuluJudge0ExtraCE", "TestCase", "async_execute", + "async_run", "execute", "get_client", - "async_run", - "sync_run", "run", "sync_execute", + "sync_run", "wait", ] @@ -122,11 +128,9 @@ def _get_preview_client(flavor: Flavor) -> Union[SuluJudge0CE, SuluJudge0ExtraCE def _get_custom_client(flavor: Flavor) -> Union[Client, None]: from json import loads - ce_endpoint = os.getenv("JUDGE0_CE_ENDPOINT", "https://ce.judge0.com") + ce_endpoint = os.getenv("JUDGE0_CE_ENDPOINT") ce_auth_header = os.getenv("JUDGE0_CE_AUTH_HEADERS") - extra_ce_endpoint = os.getenv( - "JUDGE0_EXTRA_CE_ENDPOINT", "https://extra-ce.judge0.com" - ) + extra_ce_endpoint = os.getenv("JUDGE0_EXTRA_CE_ENDPOINT") extra_ce_auth_header = os.getenv("JUDGE0_EXTRA_CE_AUTH_HEADERS") if flavor == Flavor.CE and ce_endpoint is not None and ce_auth_header is not None: diff --git a/src/judge0/clients.py b/src/judge0/clients.py index 2d8366a9..ae7c8d50 100644 --- a/src/judge0/clients.py +++ b/src/judge0/clients.py @@ -715,5 +715,77 @@ def __init__(self, api_key=None, **kwargs): super().__init__(self.DEFAULT_ENDPOINT, api_key, **kwargs) -CE = (SuluJudge0CE, RapidJudge0CE, ATDJudge0CE) -EXTRA_CE = (SuluJudge0ExtraCE, RapidJudge0ExtraCE, ATDJudge0ExtraCE) +class Judge0Cloud(Client): + """Base class for all Judge0 Cloud clients. + + Parameters + ---------- + endpoint : str + Default request endpoint. + auth_headers : str or dict + Judge0 Cloud authentication headers, either as a JSON string or a dictionary. + **kwargs : dict + Additional keyword arguments for the base Client. + """ + + def __init__(self, endpoint, auth_headers, **kwargs): + if isinstance(auth_headers, str): + from json import loads + + auth_headers = loads(auth_headers) + + super().__init__( + endpoint, + auth_headers, + **kwargs, + ) + + +class Judge0CloudCE(Judge0Cloud): + """Judge0 Cloud client for CE flavor. + + Parameters + ---------- + endpoint : str + Default request endpoint. + auth_headers : str or dict + Judge0 Cloud authentication headers, either as a JSON string or a dictionary. + **kwargs : dict + Additional keyword arguments for the base Client. + """ + + DEFAULT_ENDPOINT: ClassVar[str] = "https://ce.judge0.com" + HOME_URL: ClassVar[str] = "https://ce.judge0.com" + API_KEY_ENV: ClassVar[str] = "JUDGE0_CLOUD_CE_AUTH_HEADERS" + + def __init__(self, auth_headers, **kwargs): + super().__init__( + self.DEFAULT_ENDPOINT, + auth_headers, + **kwargs, + ) + + +class Judge0CloudExtraCE(Judge0Cloud): + """Judge0 Cloud client for Extra CE flavor. + + Parameters + ---------- + endpoint : str + Default request endpoint. + auth_headers : str or dict + Judge0 Cloud authentication headers, either as a JSON string or a dictionary. + **kwargs : dict + Additional keyword arguments for the base Client. + """ + + DEFAULT_ENDPOINT: ClassVar[str] = "https://extra-ce.judge0.com" + HOME_URL: ClassVar[str] = "https://extra-ce.judge0.com" + API_KEY_ENV: ClassVar[str] = "JUDGE0_CLOUD_EXTRA_CE_AUTH_HEADERS" + + def __init__(self, auth_headers, **kwargs): + super().__init__(self.DEFAULT_ENDPOINT, auth_headers, **kwargs) + + +CE = (Judge0CloudCE, SuluJudge0CE, RapidJudge0CE, ATDJudge0CE) +EXTRA_CE = (Judge0CloudExtraCE, SuluJudge0ExtraCE, RapidJudge0ExtraCE, ATDJudge0ExtraCE) diff --git a/tests/conftest.py b/tests/conftest.py index 4e1547c5..b8c950e7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -91,6 +91,26 @@ def sulu_extra_ce_client(): return clients.SuluJudge0ExtraCE(api_key) +@pytest.fixture(scope="session") +def judge0_cloud_ce_client(): + auth_headers = os.getenv("JUDGE0_CLOUD_CE_AUTH_HEADERS") + + if auth_headers is None: + return None + else: + return clients.Judge0CloudCE(auth_headers) + + +@pytest.fixture(scope="session") +def judge0_cloud_extra_ce_client(): + auth_headers = os.getenv("JUDGE0_CLOUD_EXTRA_CE_AUTH_HEADERS") + + if auth_headers is None: + return None + else: + return clients.Judge0CloudExtraCE(auth_headers) + + @pytest.fixture(scope="session") def preview_ce_client() -> clients.SuluJudge0CE: return clients.SuluJudge0CE(retry_strategy=RegularPeriodRetry(0.5)) @@ -104,6 +124,7 @@ def preview_extra_ce_client() -> clients.SuluJudge0ExtraCE: @pytest.fixture(scope="session") def ce_client( custom_ce_client, + judge0_cloud_ce_client, sulu_ce_client, rapid_ce_client, atd_ce_client, @@ -111,6 +132,8 @@ def ce_client( ): if custom_ce_client is not None: return custom_ce_client + if judge0_cloud_ce_client is not None: + return judge0_cloud_ce_client if sulu_ce_client is not None: return sulu_ce_client if rapid_ce_client is not None: @@ -126,6 +149,7 @@ def ce_client( @pytest.fixture(scope="session") def extra_ce_client( custom_extra_ce_client, + judge0_cloud_extra_ce_client, sulu_extra_ce_client, rapid_extra_ce_client, atd_extra_ce_client, @@ -133,6 +157,8 @@ def extra_ce_client( ): if custom_extra_ce_client is not None: return custom_extra_ce_client + if judge0_cloud_extra_ce_client is not None: + return judge0_cloud_extra_ce_client if sulu_extra_ce_client is not None: return sulu_extra_ce_client if rapid_extra_ce_client is not None: diff --git a/tests/test_clients.py b/tests/test_clients.py index c22c9dca..e5ccd458 100644 --- a/tests/test_clients.py +++ b/tests/test_clients.py @@ -7,6 +7,8 @@ "rapid_extra_ce_client", "sulu_ce_client", "sulu_extra_ce_client", + "judge0_cloud_ce_client", + "judge0_cloud_extra_ce_client", ) From 9f852ef26c9b1f1f6067b14a53b4aef22ca53431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Fri, 22 Aug 2025 03:27:37 +0200 Subject: [PATCH 122/161] Update latest ID for Scala --- src/judge0/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/judge0/data.py b/src/judge0/data.py index ff4ed9ef..5182e7a5 100644 --- a/src/judge0/data.py +++ b/src/judge0/data.py @@ -126,7 +126,7 @@ LanguageAlias.R: 99, LanguageAlias.RUBY: 72, LanguageAlias.RUST: 108, - LanguageAlias.SCALA: 81, + LanguageAlias.SCALA: 112, LanguageAlias.SQLITE: 82, LanguageAlias.SWIFT: 83, LanguageAlias.TYPESCRIPT: 101, From 8899a870278d7d122b2d299f2d0b2c99057a3a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Thu, 11 Sep 2025 20:23:48 +0200 Subject: [PATCH 123/161] Add .env.template. Allow empty auth headers for Judge0 Cloud. Use Judge0 Cloud as preview client. --- .env.template | 26 ++++++++++++++++++++++++++ src/judge0/__init__.py | 9 ++++----- src/judge0/clients.py | 6 +++--- 3 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 .env.template diff --git a/.env.template b/.env.template new file mode 100644 index 00000000..990a8d1d --- /dev/null +++ b/.env.template @@ -0,0 +1,26 @@ +# Sulu API Key. Get yours at https://platform.sulu.sh/portal/consumer/dashboard +# Subscribe here: https://platform.sulu.sh/apis/judge0 +JUDGE0_SULU_API_KEY= + +# Rapid API Key. Get yours at https://rapidapi.com/developer/apps +# Subscribe here: https://rapidapi.com/organization/judge0 +JUDGE0_RAPID_API_KEY= + +# ATD API Key. Get yours at https://www.allthingsdev.co/account/apps +# Subscribe here: https://www.allthingsdev.co/publisher/profile/Herman%20Zvonimir%20Došilović +JUDGE0_ATD_API_KEY= + +# Auth headers for Judge0 Cloud. +# Contact us to get your credentials. +JUDGE0_CLOUD_CE_AUTH_HEADERS= +JUDGE0_CLOUD_EXTRA_CE_AUTH_HEADERS= + +# Endpoint and auth headers for custom Judge0 CE instance. +# Use to connect to your own Judge0 CE server. +JUDGE0_CE_ENDPOINT= +JUDGE0_CE_AUTH_HEADERS= # Use "{}" for no auth headers. + +# Endpoint and auth headers for custom Judge0 Extra CE instance. +# Use to connect to your own Judge0 Extra CE server. +JUDGE0_EXTRA_CE_ENDPOINT= +JUDGE0_EXTRA_CE_AUTH_HEADERS= # Use "{}" for no auth headers. diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index 65c842d7..d19f8df6 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -94,13 +94,12 @@ def _get_implicit_client(flavor: Flavor) -> Client: # Let's check if we can find a self-hosted client. client = _get_custom_client(flavor) - # Try to find one of the API keys JUDGE0_{SULU,RAPID,ATD}_API_KEY - # for hub clients. + # Try to find one of the API keys for hub clients. if client is None: client = _get_hub_client(flavor) # If we didn't find any of the possible keys, initialize - # the preview Sulu client based on the flavor. + # the preview client based on the flavor. if client is None: client = _get_preview_client(flavor) @@ -120,9 +119,9 @@ def _get_preview_client(flavor: Flavor) -> Union[SuluJudge0CE, SuluJudge0ExtraCE ) if flavor == Flavor.CE: - return SuluJudge0CE(retry_strategy=RegularPeriodRetry(0.5)) + return Judge0CloudCE() else: - return SuluJudge0ExtraCE(retry_strategy=RegularPeriodRetry(0.5)) + return Judge0CloudExtraCE() def _get_custom_client(flavor: Flavor) -> Union[Client, None]: diff --git a/src/judge0/clients.py b/src/judge0/clients.py index ae7c8d50..5fcaafe3 100644 --- a/src/judge0/clients.py +++ b/src/judge0/clients.py @@ -728,7 +728,7 @@ class Judge0Cloud(Client): Additional keyword arguments for the base Client. """ - def __init__(self, endpoint, auth_headers, **kwargs): + def __init__(self, endpoint, auth_headers=None, **kwargs): if isinstance(auth_headers, str): from json import loads @@ -758,7 +758,7 @@ class Judge0CloudCE(Judge0Cloud): HOME_URL: ClassVar[str] = "https://ce.judge0.com" API_KEY_ENV: ClassVar[str] = "JUDGE0_CLOUD_CE_AUTH_HEADERS" - def __init__(self, auth_headers, **kwargs): + def __init__(self, auth_headers=None, **kwargs): super().__init__( self.DEFAULT_ENDPOINT, auth_headers, @@ -783,7 +783,7 @@ class Judge0CloudExtraCE(Judge0Cloud): HOME_URL: ClassVar[str] = "https://extra-ce.judge0.com" API_KEY_ENV: ClassVar[str] = "JUDGE0_CLOUD_EXTRA_CE_AUTH_HEADERS" - def __init__(self, auth_headers, **kwargs): + def __init__(self, auth_headers=None, **kwargs): super().__init__(self.DEFAULT_ENDPOINT, auth_headers, **kwargs) From 0a96d03413f40888d2ee82adc88db29a661e76b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Thu, 11 Sep 2025 20:54:49 +0200 Subject: [PATCH 124/161] Add JUDGE0_CLOUD_CE_AUTH_HEADERS to test.yml workflow. --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2beadbd3..80f2e529 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,6 +37,8 @@ jobs: JUDGE0_EXTRA_CE_AUTH_HEADERS: ${{ secrets.JUDGE0_EXTRA_CE_AUTH_HEADERS }} JUDGE0_CE_ENDPOINT: ${{ secrets.JUDGE0_CE_ENDPOINT }} JUDGE0_EXTRA_CE_ENDPOINT: ${{ secrets.JUDGE0_EXTRA_CE_ENDPOINT }} + JUDGE0_CLOUD_CE_AUTH_HEADERS: ${{ secrets.JUDGE0_CLOUD_CE_AUTH_HEADERS }} + JUDGE0_CLOUD_EXTRA_CE_AUTH_HEADERS: ${{ secrets.JUDGE0_CLOUD_EXTRA_CE_AUTH_HEADERS }} run: | source venv/bin/activate pytest tests From 50896191391f958e77723d9c6e407ae49740cb26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sat, 8 Nov 2025 21:54:14 +0100 Subject: [PATCH 125/161] Remove Sulu --- .env.template | 4 - .github/workflows/test.yml | 1 - README.md | 11 +-- docs/source/api/clients.rst | 9 --- .../contributors_guide/contributing.rst | 4 +- examples/sulu_clients.py | 22 ------ examples/sulu_submission.py | 32 -------- examples/sulu_submissions.py | 39 ---------- src/judge0/__init__.py | 8 +- src/judge0/clients.py | 74 +------------------ src/judge0/utils.py | 4 +- tests/conftest.py | 34 +-------- tests/test_api.py | 4 +- tests/test_clients.py | 2 - 14 files changed, 19 insertions(+), 229 deletions(-) delete mode 100644 examples/sulu_clients.py delete mode 100644 examples/sulu_submission.py delete mode 100644 examples/sulu_submissions.py diff --git a/.env.template b/.env.template index 990a8d1d..84f0e034 100644 --- a/.env.template +++ b/.env.template @@ -1,7 +1,3 @@ -# Sulu API Key. Get yours at https://platform.sulu.sh/portal/consumer/dashboard -# Subscribe here: https://platform.sulu.sh/apis/judge0 -JUDGE0_SULU_API_KEY= - # Rapid API Key. Get yours at https://rapidapi.com/developer/apps # Subscribe here: https://rapidapi.com/organization/judge0 JUDGE0_RAPID_API_KEY= diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 80f2e529..affae10a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,6 @@ jobs: env: # Add necessary api keys as env variables. JUDGE0_ATD_API_KEY: ${{ secrets.JUDGE0_ATD_API_KEY }} JUDGE0_RAPID_API_KEY: ${{ secrets.JUDGE0_RAPID_API_KEY }} - JUDGE0_SULU_API_KEY: ${{ secrets.JUDGE0_SULU_API_KEY }} JUDGE0_CE_AUTH_HEADERS: ${{ secrets.JUDGE0_CE_AUTH_HEADERS }} JUDGE0_EXTRA_CE_AUTH_HEADERS: ${{ secrets.JUDGE0_EXTRA_CE_AUTH_HEADERS }} JUDGE0_CE_ENDPOINT: ${{ secrets.JUDGE0_CE_ENDPOINT }} diff --git a/README.md b/README.md index 722a0c55..33eee1e8 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,11 @@ pip install judge0 ### Getting The API Key -Get your API key from [Sulu](https://platform.sulu.sh/apis/judge0), [Rapid](https://rapidapi.com/organization/judge0), or [ATD](https://www.allthingsdev.co/publisher/profile/Herman%20Zvonimir%20Do%C5%A1ilovi%C4%87). +Get your API key from [Rapid](https://rapidapi.com/organization/judge0), or [ATD](https://www.allthingsdev.co/publisher/profile/Herman%20Zvonimir%20Do%C5%A1ilovi%C4%87). #### Notes -* [**Recommended**] Choose Sulu if you need pay-as-you-go (pey-per-use) pricing. -* Choose Rapid or ATD if you need a quota-based (volume-based) plan. -* Judge0 has two flavors: Judge0 CE and Judge0 Extra CE, and their difference is just in the languages they support. When choosing Sulu, your API key will work for both flavors, but for Rapid and ATD you will need to explicitly subscribe to both flavors if you want to use both. +* Judge0 has two flavors: Judge0 CE and Judge0 Extra CE, and their difference is just in the languages they support. When choosing Rapid and ATD you will need to explicitly subscribe to both flavors if you want to use both. ### Using Your API Key @@ -48,7 +46,7 @@ Explicitly create a client object with your API key and pass it to Judge0 Python ```python import judge0 -client = judge0.SuluJudge0CE(api_key="xxx") +client = judge0.RapidJudge0CE(api_key="xxx") result = judge0.run(client=client, source_code="print('hello, world')") print(result.stdout) ``` @@ -56,13 +54,12 @@ print(result.stdout) Other options include: - `judge0.RapidJudge0CE` - `judge0.ATDJudge0CE` -- `judge0.SuluJudge0ExtraCE` - `judge0.RapidJudge0ExtraCE` - `judge0.ATDJudge0ExtraCE` #### Option 2: Implicit Client Object -Put your API key in one of the following environment variables, respectable to the provider that issued you the API key: `JUDGE0_SULU_API_KEY`, `JUDGE0_RAPID_API_KEY`, or `JUDGE0_ATD_API_KEY`. +Put your API key in one of the following environment variables, respectable to the provider that issued you the API key: `JUDGE0_RAPID_API_KEY`, or `JUDGE0_ATD_API_KEY`. Judge0 Python SDK will automatically detect the environment variable and use it to create a client object that will be used for all API calls if you do not explicitly pass a client object. diff --git a/docs/source/api/clients.rst b/docs/source/api/clients.rst index b4d15c94..29e7e45a 100644 --- a/docs/source/api/clients.rst +++ b/docs/source/api/clients.rst @@ -35,12 +35,3 @@ Clients Module .. autoclass:: judge0.clients.RapidJudge0ExtraCE :show-inheritance: - -.. autoclass:: judge0.clients.Sulu - :show-inheritance: - -.. autoclass:: judge0.clients.SuluJudge0CE - :show-inheritance: - -.. autoclass:: judge0.clients.SuluJudge0ExtraCE - :show-inheritance: \ No newline at end of file diff --git a/docs/source/contributors_guide/contributing.rst b/docs/source/contributors_guide/contributing.rst index 867f3f14..4c1623b9 100644 --- a/docs/source/contributors_guide/contributing.rst +++ b/docs/source/contributors_guide/contributing.rst @@ -52,8 +52,8 @@ Testing .. warning:: If you are implementing features or fixing bugs, you are expected to have - all of the three API keys (ATD, Sulu, and RapidAPI) setup and set in you - environment variables - ``JUDGE0_SULU_API_KEY``, ``JUDGE0_RAPID_API_KEY``, + all API keys (RapidAPI and ATD) setup and set in you + environment variables - ``JUDGE0_RAPID_API_KEY``, and ``JUDGE0_ATD_API_KEY``. Every bug fix or new feature should have tests for it. The tests are located in diff --git a/examples/sulu_clients.py b/examples/sulu_clients.py deleted file mode 100644 index b9c60874..00000000 --- a/examples/sulu_clients.py +++ /dev/null @@ -1,22 +0,0 @@ -import os - -import judge0 -from dotenv import load_dotenv - -load_dotenv() - -api_key = os.getenv("JUDGE0_SULU_API_KEY") - -client_ce = judge0.SuluJudge0CE(api_key=api_key) -print(client_ce.get_about()) -print(client_ce.get_config_info()) -print(client_ce.get_statuses()) -print(client_ce.get_languages()) -print(client_ce.get_language(language_id=42)) - -client_extra_ce = judge0.SuluJudge0ExtraCE(api_key=api_key) -print(client_extra_ce.get_about()) -print(client_extra_ce.get_config_info()) -print(client_extra_ce.get_statuses()) -print(client_extra_ce.get_languages()) -print(client_extra_ce.get_language(language_id=24)) diff --git a/examples/sulu_submission.py b/examples/sulu_submission.py deleted file mode 100644 index ef744927..00000000 --- a/examples/sulu_submission.py +++ /dev/null @@ -1,32 +0,0 @@ -import os - -import judge0 - -from dotenv import load_dotenv - -load_dotenv() - -api_key = os.getenv("JUDGE0_SULU_API_KEY") - - -def run_example(client_class, language_id): - client = client_class(api_key=api_key) - submission = judge0.Submission( - source_code="print('Hello Judge0')", - language=language_id, - expected_output="Hello Judge0", - ) - - judge0.execute(client=client, submissions=submission) - - print(f"{submission.status=}") - print(f"{submission.stdout=}") - - -def main(): - run_example(judge0.SuluJudge0CE, 100) - run_example(judge0.SuluJudge0ExtraCE, 25) - - -if __name__ == "__main__": - main() diff --git a/examples/sulu_submissions.py b/examples/sulu_submissions.py deleted file mode 100644 index b7088701..00000000 --- a/examples/sulu_submissions.py +++ /dev/null @@ -1,39 +0,0 @@ -import os - -import judge0 - -from dotenv import load_dotenv - -load_dotenv() - -api_key = os.getenv("JUDGE0_SULU_API_KEY") - - -def run_example(client_class, lang_id_python, lang_id_c): - client = client_class(api_key=api_key) - submission1 = judge0.Submission( - source_code="print('Hello Judge0')", - language=lang_id_python, - expected_output="Hello Judge0", - ) - submission2 = judge0.Submission( - source_code='#include \n\nint main() {\n printf("Hello World!");\n return 0;\n}', - language=lang_id_c, - expected_output="Hello World!", - ) - - submissions = [submission1, submission2] - judge0.execute(client=client, submissions=submissions) - - for submission in submissions: - print(f"{submission.status=}") - print(f"{submission.stdout=}") - - -def main(): - run_example(judge0.SuluJudge0CE, 100, 50) - run_example(judge0.SuluJudge0ExtraCE, 25, 1) - - -if __name__ == "__main__": - main() diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index d19f8df6..97c9a7e4 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -25,9 +25,6 @@ Rapid, RapidJudge0CE, RapidJudge0ExtraCE, - Sulu, - SuluJudge0CE, - SuluJudge0ExtraCE, ) from .filesystem import File, Filesystem from .retry import MaxRetries, MaxWaitTime, RegularPeriodRetry @@ -55,9 +52,6 @@ "RegularPeriodRetry", "Status", "Submission", - "Sulu", - "SuluJudge0CE", - "SuluJudge0ExtraCE", "TestCase", "async_execute", "async_run", @@ -111,7 +105,7 @@ def _get_implicit_client(flavor: Flavor) -> Client: return client -def _get_preview_client(flavor: Flavor) -> Union[SuluJudge0CE, SuluJudge0ExtraCE]: +def _get_preview_client(flavor: Flavor) -> Union[Judge0CloudCE, Judge0CloudExtraCE]: logger.warning( "You are using a preview version of the client which is not recommended" " for production.\n" diff --git a/src/judge0/clients.py b/src/judge0/clients.py index 5fcaafe3..49d1ac61 100644 --- a/src/judge0/clients.py +++ b/src/judge0/clients.py @@ -23,11 +23,11 @@ class Client: ---------- API_KEY_ENV : str Environment variable where judge0-python should look for API key for - the client. Set to default values for ATD, RapidAPI, and Sulu clients. + the client. Set to default values for RapidAPI and ATD clients. """ # Environment variable where judge0-python should look for API key for - # the client. Set to default values for ATD, RapidAPI, and Sulu clients. + # the client. Set to default values for RapidAPI and ATD clients. API_KEY_ENV: ClassVar[str] = None def __init__( @@ -649,72 +649,6 @@ def __init__(self, api_key, **kwargs): ) -class Sulu(Client): - """Base class for all Sulu clients. - - Parameters - ---------- - endpoint : str - Default request endpoint. - api_key : str, optional - Sulu API key. - **kwargs : dict - Additional keyword arguments for the base Client. - """ - - API_KEY_ENV: ClassVar[str] = "JUDGE0_SULU_API_KEY" - - def __init__(self, endpoint, api_key=None, **kwargs): - self.api_key = api_key - super().__init__( - endpoint, - {"Authorization": f"Bearer {api_key}"} if api_key else None, - **kwargs, - ) - - -class SuluJudge0CE(Sulu): - """Sulu client for CE flavor. - - Parameters - ---------- - api_key : str, optional - Sulu API key. - **kwargs : dict - Additional keyword arguments for the base Client. - """ - - DEFAULT_ENDPOINT: ClassVar[str] = "https://judge0-ce.p.sulu.sh" - HOME_URL: ClassVar[str] = "https://sparkhub.sulu.sh/apis/judge0/judge0-ce/readme" - - def __init__(self, api_key=None, **kwargs): - super().__init__( - self.DEFAULT_ENDPOINT, - api_key, - **kwargs, - ) - - -class SuluJudge0ExtraCE(Sulu): - """Sulu client for Extra CE flavor. - - Parameters - ---------- - api_key : str - Sulu API key. - **kwargs : dict - Additional keyword arguments for the base Client. - """ - - DEFAULT_ENDPOINT: ClassVar[str] = "https://judge0-extra-ce.p.sulu.sh" - HOME_URL: ClassVar[str] = ( - "https://sparkhub.sulu.sh/apis/judge0/judge0-extra-ce/readme" - ) - - def __init__(self, api_key=None, **kwargs): - super().__init__(self.DEFAULT_ENDPOINT, api_key, **kwargs) - - class Judge0Cloud(Client): """Base class for all Judge0 Cloud clients. @@ -787,5 +721,5 @@ def __init__(self, auth_headers=None, **kwargs): super().__init__(self.DEFAULT_ENDPOINT, auth_headers, **kwargs) -CE = (Judge0CloudCE, SuluJudge0CE, RapidJudge0CE, ATDJudge0CE) -EXTRA_CE = (Judge0CloudExtraCE, SuluJudge0ExtraCE, RapidJudge0ExtraCE, ATDJudge0ExtraCE) +CE = (Judge0CloudCE, RapidJudge0CE, ATDJudge0CE) +EXTRA_CE = (Judge0CloudExtraCE, RapidJudge0ExtraCE, ATDJudge0ExtraCE) diff --git a/src/judge0/utils.py b/src/judge0/utils.py index e38b41f5..94eb5efc 100644 --- a/src/judge0/utils.py +++ b/src/judge0/utils.py @@ -23,14 +23,14 @@ def wrapper(*args, **kwargs): return func(*args, **kwargs) except HTTPError as err: if is_http_too_many_requests_error(exception=err): - # If the raised exception is inside the one of the Sulu clients + # If the raised exception is inside the one of the Judge0 Cloud clients # let's check if we are dealing with the implicit client. if args: instance = args[0] class_name = instance.__class__.__name__ # Check if we are using a preview version of the client. if ( - class_name in ("SuluJudge0CE", "SuluJudge0ExtraCE") + class_name in ("Judge0CloudCE", "Judge0CloudExtraCE") and instance.api_key is None ): raise PreviewClientLimitError( diff --git a/tests/conftest.py b/tests/conftest.py index b8c950e7..38a85732 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -71,26 +71,6 @@ def rapid_extra_ce_client(): return clients.RapidJudge0ExtraCE(api_key) -@pytest.fixture(scope="session") -def sulu_ce_client(): - api_key = os.getenv("JUDGE0_SULU_API_KEY") - - if api_key is None: - return None - else: - return clients.SuluJudge0CE(api_key) - - -@pytest.fixture(scope="session") -def sulu_extra_ce_client(): - api_key = os.getenv("JUDGE0_SULU_API_KEY") - - if api_key is None: - return None - else: - return clients.SuluJudge0ExtraCE(api_key) - - @pytest.fixture(scope="session") def judge0_cloud_ce_client(): auth_headers = os.getenv("JUDGE0_CLOUD_CE_AUTH_HEADERS") @@ -112,20 +92,19 @@ def judge0_cloud_extra_ce_client(): @pytest.fixture(scope="session") -def preview_ce_client() -> clients.SuluJudge0CE: - return clients.SuluJudge0CE(retry_strategy=RegularPeriodRetry(0.5)) +def preview_ce_client() -> clients.Judge0CloudCE: + return clients.Judge0CloudCE(retry_strategy=RegularPeriodRetry(0.5)) @pytest.fixture(scope="session") -def preview_extra_ce_client() -> clients.SuluJudge0ExtraCE: - return clients.SuluJudge0ExtraCE(retry_strategy=RegularPeriodRetry(0.5)) +def preview_extra_ce_client() -> clients.Judge0CloudExtraCE: + return clients.Judge0CloudExtraCE(retry_strategy=RegularPeriodRetry(0.5)) @pytest.fixture(scope="session") def ce_client( custom_ce_client, judge0_cloud_ce_client, - sulu_ce_client, rapid_ce_client, atd_ce_client, preview_ce_client, @@ -134,8 +113,6 @@ def ce_client( return custom_ce_client if judge0_cloud_ce_client is not None: return judge0_cloud_ce_client - if sulu_ce_client is not None: - return sulu_ce_client if rapid_ce_client is not None: return rapid_ce_client if atd_ce_client is not None: @@ -150,7 +127,6 @@ def ce_client( def extra_ce_client( custom_extra_ce_client, judge0_cloud_extra_ce_client, - sulu_extra_ce_client, rapid_extra_ce_client, atd_extra_ce_client, preview_extra_ce_client, @@ -159,8 +135,6 @@ def extra_ce_client( return custom_extra_ce_client if judge0_cloud_extra_ce_client is not None: return judge0_cloud_extra_ce_client - if sulu_extra_ce_client is not None: - return sulu_extra_ce_client if rapid_extra_ce_client is not None: return rapid_extra_ce_client if atd_extra_ce_client is not None: diff --git a/tests/test_api.py b/tests/test_api.py index 3ba95267..b58aa24e 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -9,8 +9,8 @@ "atd_extra_ce_client", "rapid_ce_client", "rapid_extra_ce_client", - "sulu_ce_client", - "sulu_extra_ce_client", + "judge0_cloud_ce_client", + "judge0_cloud_extra_ce_client", ) diff --git a/tests/test_clients.py b/tests/test_clients.py index e5ccd458..98eb45e4 100644 --- a/tests/test_clients.py +++ b/tests/test_clients.py @@ -5,8 +5,6 @@ "atd_extra_ce_client", "rapid_ce_client", "rapid_extra_ce_client", - "sulu_ce_client", - "sulu_extra_ce_client", "judge0_cloud_ce_client", "judge0_cloud_extra_ce_client", ) From 70f09c987aa70f48995eccee46c6d73e2a34abd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sat, 8 Nov 2025 22:23:20 +0100 Subject: [PATCH 126/161] Deprecate Python 3.9. Update logo and year in the docs. --- .github/workflows/docs.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/test.yml | 4 ++-- README.md | 2 +- docs/assets/logo.png | Bin 31233 -> 247958 bytes docs/source/conf.py | 2 +- .../contributors_guide/contributing.rst | 6 +++--- pyproject.toml | 5 ++--- 8 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 11d9d8e3..b8e06c26 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,7 +24,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.9" + python-version: "3.10" - name: Install dependencies run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0a4e3e38..0ab3dff3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: 3.10 # Install build and twine - name: Install Build Tools diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index affae10a..31c375ad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,10 +18,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v3 with: - python-version: "3.9" + python-version: "3.10" - name: Install dependencies run: | python -m venv venv diff --git a/README.md b/README.md index 33eee1e8..a9eb6005 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ pip install judge0 ### Requirements -- Python 3.9+ +- Python 3.10+ ## Quick Start diff --git a/docs/assets/logo.png b/docs/assets/logo.png index 9661bbb3b66cba7024393224b4afefdedb102674..2b5912032eaa9bdab3167a48822f49e5f0bd5516 100644 GIT binary patch literal 247958 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 Vb~%Q&fr;v&;@i@t&(2}2l~zRo5E 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%0DEPDZ*U~NZ{Bgz-?UyLAKS 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

p4zq%} z!EvG_FYc+^sZ+d=dO*%1u0|f!!)3H(_BayOAX4(P{2dgQb~_>ge5g0=jD43W`ivNm zg<3@w3Q=Q6BAWbAlIVM(T?aLomwc?-UN{AcWxh0PfFarAwiuY#V*z!B<#H@0shXWU zOPQ-Pw+x75oEWJ>LUiLGTF9&9tx6oO{StYz5A*1_k(Z`p&J&@GqCOYQl`+GhsMDJ0 zdrUKAy9zSF>aBQ)8E5^knZd;!148_Ra*UI5vOAJISJJw9(8}{c>^DUm)F>d@K@>YN8u}g@H@(P7Jeq6eOLI(E`32e-w>|1O(6if zLVokP{}+b_T6`(BvlPA){3qZq&*wckg38&ySa4|-=tFo*tNA;_rZYM)! z{EZ`{`mMd^DG67+u}rx2?LZL(Iki>w244=(o&{*j)Nai-Fv^umA9J&A%eI0}YTC_} z$0}cY9ttX?{(lWdS(WrdDS>*Nu)c({4NDb>!?6%4qK#jNAElSi)`!5DkP^ zB7odlLo>F4q2#oDaBC$1J0=0L{M|8kA>5>m0FmyGG zE&#w|?T&KZ;MqpmY+s)TLh#$Z;uK6P9wDwkex3G}TIS-O76|N!k@#QTUv2haS?kXo`9U zDsmSB0DeE8J^MA>+c)j-VY>oFP}@F%?OcbM2(WKtQqtfCMz4Ah0al(cwGF*En>%y) z7mMZnchYYQa!G9-pli0i-@mKp-mQQEZt#xwl83c{gg*gq7iKkc=iEh6&UNgqrltj% zVY|0fcg52s2{<2+#nB{$gG+bVAlh6ia z${oIECi<|ksR=*=F*7Q_3PTLS>MyV-c66H7K9tc|lsfg=!Q zFXWSW_jV80apDR8AS)t2B=WL_O#IAiDeJKhSy}_IT5K>}EBQG`^EQzArd<*x0f>n1 zIyZKt3E><$m)^@m+DC>SCo#giQz61?AbNhc|C7V>u<3pw&ZFVIP7S;8NNy)e^yTMhNCh|JA_o-_`r`?q8dw4g^l#pZg$w*6@kD zT@LcDA$!EVD9uNO-h#GLsfBrRm(|)Uuswg=6z38FcEnJmIu>OYEwjspweST3!Xi$9 zYz8WxM|!P`@fpFfC?BQQ88OxO8FS^ht*NTOu$qA5qEa=ZuCS(GPksdS&lZeIzN{M4 zK>c6f6A)`^zoxHDjx4M70rO%ACM3T>oYsENTfb-@;+_ll{JVn{t0}V&xZ!wPeW@ulg?%O%2)0sIZbSs z-T5xl@gtTvs~!*?uImIsfXzLI>`2Di*}+w_yB>A7y0Pby#BTs=@-1O99sajNH#h{2 z`{wETK-rHVWa1HK=pxeqh*l3PXUru&iYyQP{sx8LU}Y2NCvoj1gY2Rh8@|tYvb_Vv z$~hdo0{Or|0zhR&LB9n9R!jljMa=>Hyg$S>Ifj!wO1B@d@8p;H2uOfC5q&7}fSjzr z|39@NzrnaO@?g|Klc(_gEce^R1j_3QLOuT0}@j*#|x_F=0{P z2tu@#&o(821~Uymei4;xZ+t0EMkl?)#}+Oed;Ny@?R8+4e;4k>0b}IC^hrV=#QT^y z*j4WwBod@~E2SGcAKhVX=>qn>4d%193=imP-?7K~{@dr@QHN*KJd=G+7muF(>hKQh zkF)UWYyDi{zy9SvEnSr}KPSqV93L3KPmQi)NPZ>W)qK_|9Lwko^pgRECoL@=wa~aS zsd|mB0{>@6+|{yk1$2)kBL~FFaTRCp!RG-!IF$=12rCR;m8nXIoRtsXpH=4MefN5s zl)X_Ottt*xlBi)uA!cPy~XF1j|2OCq@(i5Un9|9QnhzRg9%I=(yM4V(e zE6($f_WdNm&kw&s2LJeSul`j2DBOU@@0 z1(ql)u~}!!ro9)^<1dK#k$S`Nkk-t9N$hhrdu%46Io$%f)|&=x{1mnY2@qR9@wGoI z3T`Q1wF)@%X^=Jd8mY)*<%#RSU!gg?iWE1^E?qtq2~9ifdDnS9vstvzu~<%QCmV?X znU(4(G>AlMd;9GJt;XqjV_%zhlR4~TE1}_^XBDMdtfaP{@TKlJ;V$b20I6JAC6ssK zn0V^1oy818#RPU_W5+x)b#+dx4@r0!w>mi3D>pABH0QZ{Mx*g_nq=p@?~+~`5nxSf zO~Dj9q<+e}fOZ$T7E-Lf#MAMQIsf|Jga+h*?xxG8k4Mhe$SDVqJ{M?=EfjiYac}tI z?Iw9_7&uY4@lFUZC%-TzBP%l2BM)Ahi2$Jl&+WW)$e-P7{iL8vPJggWL;(63z^mv; z^lY`taHHZQ!7U)-%DG|J@g--6VbQU{b%KIp)tb@cB~J__GGLq${JW2byUq`_Pj57w z1QyV%qFzi_Y(|Ld~f|>lpPu6MhS7IfDd(`Ec8U1b{gV zEseThRtzYsTUkr2_XwSVdaT>tC)e;x`>VvfaTJ*LBOgRWpC_P>ZGG{pH@i3+eV|_& zZKN~LN5K!dEcS^VG}x^Z1p4-*V)n&nYatUXtF*0^ph5|CGuv+Wmh$ z_&S;?s0#lGB4i7&+_K zdSfC1GT@r>EO`3Vk3hFm-pOOyhKx9ewXP14R!V21luhR)S8do19WExhV3_*_%ZK(A zYDzX19J%Ju)jv5tL~skwq$Ds|MmU$kU_fhg&RqC;hniDd2TE1Jb)aPmZ&w!CjX-N% zxs-?c?wFoixuBos&GdO}iv#6cr#T;4S_IT#Az=N}?NVZq4P0Tbo8?^#gn8}jZL*5t zjYLKw0`NoOkcYdoE0aK!8`{CXscl#a$`s!8yK;9s`SHLQpKe~s@$POh5^_sGzNAsX zM70PIPh0=5%7q_&xj_YBmzw~9x!3<<#liNif=x0aU}|j3qV&_ItZ~dkJicdBk{u4^ znPiOoelfnMVxcwMeCDQ|+&7SoK-=G&xW92+GiY%!UQ6J2&$O*g*}Z$4L~(Brm)v?t z(zw2Zo|V>{c4CL|SJs$H1Q3iTF1iXMs3l4hQnwaewE!TPYaR{K^uHYPT5&at`-Riu zM?U&=DmH-W~pNiGp{8UoESv`9Cqheqe|jSq|d-^)LUql}bwhRPQ`f zhADw-g?yXo|0^2tIq*-emNkp;4+H-62>7Q9tIx7}7c(hJ*rP?7zUvT6{`#YRC3aGtE)K}z^f*9OW*Y!lQf|qOslO}|=^hVgprQC4vhLix_3W)2H##Q*a^I9@-SzG<6_49v zA$z-VDytvrj{t_N8}|ptL;xXiklqb1=in`f zfPrQWc3xgfS<;?}0RJ||nbdYX0DT=0tMy^d(_5)(oTavQ;F)k3Ef3D~baT+YsoVhO zamNb*{tdvR_2l2XwDj30IJa970e3B|87OT{et5jXCMLJ?xWxzEv8qFT5{6?(0$^Q6 z8ux3kv6c3|qDBm`Uil-E03P?E&Lrw7z;qYEynr<9<;G2K0@YVW0F(BR& z{#fnCfBnmU{vEAu{`fop>o||*bDYZa;Q!1d>`KYbzq}CRk?fyYaj*=n0(nP}yZBx0uQU=qyiS#NEhNf4VG zuyyV1znaB+TV^6aaJRb>0VB_oT->Y+$j+{@iz5mR@btkPY0ef7860zU;s^5ER6a-q z^dtaXA_BT8RSMw$z41jn8eUhLoo;NidjhsQ*c~u`A#n6-6~Su6;tTUm?v#9vbgdw# z`!M_DSnFR$K6+{vXibsRSQbSI z;J4p$v$x>v2%4ZigsW533$5&;&M!(pFnexP`dv)TB_`K8tn zNyE#{saHEz6ksQIGK-u6Z%IK4_A6W?Xx$Xm_?gwlU~Ia+vWU|>nuKfwz?_!Tkc4lE zl|HJOGgG?DJ+fo?r1br&DQ6J?03ZNKL_t)e;j&Rm3(H=2d9a?-UF|jP=2}8iu#pI` zKF`ukUqVCWvhR3Co@9iy6+l!rHrJFJ0p?pN=r9-{jrk+wk_C@G2w<*H<0ZM**P@v( zm(9N$a0|N21?IyLY4%0JI?l!Xag+|o;Mb%Q^^9Y)Sw6J)M?bJSQW(-{s5QoA-XR3ik7c-xSW>drGGn zN4OkpCEf>zO~FR$(?cZuxddAZlDHns=S6kyIIthXdY3jLiV`}20NCud2fNkbNqx6o ziTEjT0|i=|NH-Xp@PPM(`P-`V{qQiBHwRq{@E)2p{vuuuQBxt{8+C+A)@CgL z&(2h*vD@fv44_F;vx)=Px?$|u>D+=OZj!siH}SEiUiud1lbrLR{7?+gCINmE21b?M(E>#Ke%u7vA-@B8`@ z6aD@{M1Q{&^;O|V@BUx;elhtc)v>%o=JZ0RYC(Su4RE6eCprJ75&}=j@$qjSy~1!c zCnufdj0iY)>p!JG<&R36@Avd3&xeFhwRq^t+U-anE59l;+#h`uP#?GNS87DS32A|` zJ)5in=t4a)El9_~Qr8w82~i*O{Mqyv5KDWGxB!{Js5MUg+XO2mod!vJ{QZv%=yIWJ zJ^k~>Y1FC<*L1nWp3+&604b?TP86@^i0!L+aVYVj&HL_=W*j0a8b7D5gQg(?$|Qfg z0ZCy$F#S^^00fCj{_<811>XVM@j0elyt1p%GDbJB6yVswEQ?!&thyT6&ib%chxLgB z+D)9qiT;7IY06rSxU3Co2SM&w*}ysv0$F&iFe0qCQ||; zz^kgV-8wZCCeeELOeF$H!5wi}qwHWe4iT{K01K2~)Z=u29o&3{CZ}&he#3+UiazeL zds|X&YHtGo!)89}vSHZGc7PxA`BsP^jx2QdIb^pLN!qauyv@Of1kN`iIyJBph1f)X zBS|}-3AD+`&IX(Q(|jJSt=3zIw-`}0%wVQliR1kNN1?_)HsjBhVXH78|2y~bZ9E;5 z0FnER;#Q06XY7yCl|&|O66M@r4n4b+`)Zg=d`*wSfMxt6gjWJwuM`+9kjA8?IT~Co_Q# z&6z+;;*$U+n*w6km9O#ZvyNN|AcSx?EJS)DAO=c0wIb8&kT1&2ss{aQ=YYzGzPpRU z3xF;7K+InO_@XI&d%u=al57%41;S#e6Vlklwy{jP!!+YG6FWS;pGnZ#kLyz)h3r$b zXo4acDN>1mbR4MUwW)ElIW@%NN!Bc)65oWz89#W^GfRrtt%mtLttolT^`X5?OUIGu9KI$3keHy(&x6DH zoQ@q5I3faYs_Ra&qhY*_rxws^Dz~%7-e>E9li{)ZxbZu);2{I8V$2g5_4A45{ga)D3l_)4NEE&Ag5Pk0U zNazBu>+~=&Qx>rKcmXQjxv2OD=Z^wdkpWLm@7oEqYGF~RDRzz@O80Rc)(XoMPKW@m zA8c_xp*u6v;4C8 z2=Q1xT9A%LRL~7&>WF|4TeN@0i3ELs5pX-v5&fLCk_iMzj4f|Jg5Bszp5nkS9_{mS zH%CZ)q;;N8+!SB{^92(v`zw_$W^q%S79zkvZzeAA9s(dlNQgImt3DJ`S;3sZO&hRGem6)M#D!oBrza_b&1T%w7=tQ~lpl9oVV1w;CINJU z2*_a6%E&64AOf-;&{-QqtzsPdi55E^;OF`EFC7>Te2pydGxgkDP7)1pxC+5sDUvAl znA584^2Da*z6$yDdcK>2v%BtsZ$NeoC{ssLn0&X??yo%R2tOZCb) zx*8sm1@r&=Afo?!oT5x_P@gTYO)ft(92veJ*H{*D&hMj#xQ~E;)OK3%a4o;DglE2f zDfq8pTE*riWPXrCHCevgS5MM?b(qT81u6Z+pO@Y6lVb>z@z25cKJZtvh7BT-&eA?wUFmPV3zM> z1&s9x@HK65W$8D0`J6+Pf0uC_q);4Pvg`dm^97S6HMCEQXe!aAf6*OV{o^UO?l>FgVRQaD?U|G9%)96LWHbnDDB z;WZb-?_o`Xwc-DM5YhiRj?*ah`1CTseWvdpr_C4RUCC#iLbt5q+r@$e;Qws*{~@#g zwAxHt#>Zd2=x|J%N;SiLQFxeDtauj`2)2daqPD;kzP*yQnfPHZ@_&$JfBa0;3Or*u zmuCvCJRPu@&fJG~-gU#t0HAXwNU*zCfx*IgQY{~s%nq$dj>rHJADNafs*$W0N+(ia zk`W*lQ-U@kzR@~J-v18|^NU*}8d+w~+;b%jB&VHufPGP)d3J0GF^bOAnYec(0LDot zO=3}q{}%9PKLq9@BZ4@ULEErcWtfvLGx6RN>JWR(%zit8?`k5L)Vi0Q#db=!+ZF(9 zPN4-{3g#-If!~yI6kS7-6&Yc(6@+8P<>>elWg0^E1fagK264m_paV7mz@%o!*p62A zTuA&|cbn0w!NyA2!NcCWnpcujH~5O7am*56BWO6BJL|ELBBpKA6=ZO}=e?=p5OF1T zQ}2{!nNeyq0b;K6fQS+fv93&(hF}q7JIB>?de;_{KMx={8PhkCwjIOdpc%`7;M@Aj z&>Dm@y{?RLb))6KpCJ#Q3uJPtZ6!>@fV&p1ojcXa!mN21{Re0jhp?N|n?-~O(HOGS zaq4{GWYbiFqJQZBh7jGMyn+H3Xm=1=w*86t$x39oh8mHzzr$l1o6ABV{jS#Ph|l_d zlgm-K!5HkJ!DGr2>(QC5+=zo|N&DA#hqHS9aRBf;!dI*a9wiT+&k5EI4POEAS4(}R zynGTb<;DDeK1k?)AAYekZw7_^6nMVAU(f38*He1@%Qg6a#Pt86uyn17J3jsUuZAgw zN9(mJ`*c40$+Ge^%O`>0qIoH~0KtWs!j@MP8>G{wnf>#JZkvmaqC7hhfV1Nym3=0${JgDm-U&D<`bbeq z83**?E{@gNh>{yV2a{=mlUJ*C2#J8buB_GsLA=;^4D%v7`P(SYwof2<>X);HO|Dqn z!r__@v`IMW?Aup#H+vMq;oi(oLkW~keH)l`Msny04g?{~-HF}4Gkteu6P+7|Ebi8r zaI9xo8an1~n%0ek?go-KBD={MyEMwu>+SunI}3!?1^f6NnrD?^5|QL5NG>Otvyxur z#?4aN^p}B`J4b)tYGYzCAeB4xz4724husQsKO_SBfN>fAf72tN_d=FZsr7d`c$;%M ztKlok^$ghh9(nBHJtW6tw>*?Vdlc_Nc(=cYSG7(mr_lfU5TgHinz(E_<9%l*S?9$U z;=C@W_tyEb-T&tM%OLy$`aH@fGJNX$!%!6IBi6Yslc!VuTttwClbvNg&5A#Hb*?#? z9q4Gd!NsbSmAnV0$!6h2RXOiEblOV=L-DtOCc&`OrZa?vM@T!B-5EyzK#v!l5Dy5T z4rP**upmDIFrg5%CP)%6%|t*Yrxrk<6!@a~2{s52iONAzl1u84ah=*=aR(5w zZwCG$8o~*sRm23dNiz@EzLxq`wg#9LeXod~aDdtH#2TO*dsF&d*DU?sJcA1;=Txi# z2%L~1=B{~2{n3EVOupoE$}_>(_1{Q5n{Mlk8q@hdtdAQW(6zITED6_g4us^>8 zS5vk2{$_p!2z(~^PIp;FykDw$c*w5wwTzA3w~0CO8z6_;iEV7Ngi>fnX8e{HoB+H{ zH0^G#RvC&d=@c?$BKnh7=I?r~WE_>)SvtEr{zISt&P;b(b)b6+bl2KWZbV$iXw&CK z0j$8}!~kSBxRUTrOtML8{xLgV*x$hzjyoMx4jgn2Q5@qKV+ZcZ!lIN_O2ziO>0urA zqYQAQ0@gbCx(&7~e=oiSrs=hLe%A-1G}T?O=uqtMQCZwO~4kq2sgxx^y>>tite+dHesKiU=M^Br-y zi~P>r|6df|c6R^hSOn3sD^DiXeWY-7mH@22E;w(I#Dyzw3P7Bcv|ee=nNYbnO4DvY zSNH94`HB=f#p%F`hYQ$03%eo>q=zBvl>P?sH)7Tsjt_`{D+)}`GumLjc|rd`Z__I*p8PiPPU^rl@(ESO;@12h=pB&2Jlb`1bB0dcsB&roCBG9ZvHlgMb<`W+Y>2~qB6x9>OYYh%zRwl}&F%ZR>K zcCtNW3^x-2@P`47H4%V-G0W2X5K`MBwDG=s${KmEU!GqMi2!NRw)tk&FPq~%6;vc5 zK)<)CEF?o9{Dre!7A?H5b45bl4HL4V`T*Cz4X7It}>!$2c#rXg*>ulOTiJvOi@s1yX0 zb_+lSwmQYz@s=CR_151%)Ea>N<^l!on=DCy5O?@@AOsyY=jlO@ZPRkwR9Ds)qYMxT zh&ON^%$?sBoYzKa)UB+lw?YO~hkdKH_^!bG_JlP6xfRGRhtHFbKlJ@wEeN(+nA6f8 z$Nu@@*^+(_pnZRMWlODPvRxnvUJ-6DA{sVXBGVN1xrG1igM|Lap)Aj|*tdi$LjU!A z+^@MOGQwM(>b*0MFQs`5{-@55iz&@JtA|8@!0y}0LLUqD0!S~;w!5MZFq_MQKARWL z_4CjS)lmL0UxHm8ibPsK$<6l$uAAv5X%y36(k!jk^%hM%xYBTwfy=DIupAoHx9kKX8lptK_?6pcslsE zdb@mO_5oA~i%*IP>Q5u)oB+YFe=kJ$`<_k^0rSe#TCQlOm7KoAhS>+xM7R==0hP7H zs|=ZD;?+Jez;AuQZxASDH<|uPLHX|&)ixsn1_1R(!XTm#IVSP=Njw@bj$tLyHT% zM2vT2ksO}QQjhC`8*WTFlwJ0JoGcEEN}wWXa!YLt31pzH$4P<|o;IEy8^ zgm_v-1jP0FJ&fQ^`BZtFhyY5hgTTwCzK#fBiuQuy%~uFoF%po;JD65a#}>OieIi?g zC5~a0{xZ&p0NkPWz7XvHo$IC$$p)7R9(0rN{FC`G|45ioOi zlE(WG1N`EEpFAo$S(0aptqhk#`sx;6b~_za=PSVBy@bpo`-`89s~H-FbM{Pv&)QM8ve9q4uKq5oFV^AJu<_Ru3jeI+!B499^JU>e$O+MZ z{}>zpx7oqd=69d&XLO(Q0U+a165u#}z7XeXKIb|0FGG<#kMP@m(DiJNpfP{Q^HK)fUZ0OU=1 zio!R*X@$JuWEu+KSG8-tv40n`^Q6<%mUs0f&5Pc~JkOOvbH}R|0RV46kiX2bksy`dX%i#RcU*cprnxGV5x_qXB2fnyK5;iG28H!yam*b`4q9MHlfH=h|1 z*iN^5SQnyk&wb-QB5p1_Pk@d^fKOfME9DzuRzgc?pnS?4$g-1==@y-;*Gxpf(E4s< zG|T1_bfoilA^JcI9_^mLsSq|bwXfNbfMX${_v^&^H+pdLCNh%HbQ^Jz2P0 zcl{vdLOUCu@?Y#OjY0$r;&L#OV<%7l%wD_AZP?JOuP%1Z(0X+y6EN&u5dpkABIAH3 zE|MniB(8d9+(4>ZN9XhW7>o$Wj?Sp>U=I=jq-4z=B?u;Rg*lFMr0j8HbjzgCPyp>7 zvM1&s%fX6wNCZ%B2Ld7hNd-3W2xj5thhjHu$lr=6ghEppl#y;B`>vl72HU1Y*J!5R z4f>5a0W?d33U+2|A1Z;!-dvfv?=D_kf>WD@Jj4{+XE@V-&UCi%g z>Gkh35uiP=9E2DDDCW7a!m_qHCjv0okojHnG$P_Hljk{64VsjAF zZzX3Wetw@0E7WqY^@R@|mm^8#S{TIi!J)Hlu-yKU!YhjWYti6O2~`TOlI-g`)j06S zao^~_ehA5bId!S_hywa_I9j;Q^iIy8;s@zR4- zXxM$>vI|<(!&z37>GH+O@g_k6)Ur2sgl7)SBa#uG99#nIBVyXQI7cj=xlg?wx1;z1^4xvUBkBP7gLWZ zq^z4c3rH3!l!$=Fm%WH?9HzZ)GP!?mWb|lD>zU&pz}NS{=j5@E%7dTBm!>k&@aX& zCou_-j8R)zIHu#;4tBjDGi@{|tuzw#W4{%k982oPXbD5xa6>Z0fP1z-NF`-BI=~Bu zaCN$<&q#LS54}=UCx?T=r@~YgeLjpVWDe7M+Bv2=&sf(OAg4_>+O};!&WGQZIe<^D z52%`>T@Pi;A$jTz2iMzwd-^0i{wGau?UB40vp1Sq@eGyHX{YvgUK-#2 z_O1govva7QVaT4_;^TtlYB9;zGVMmTe~R$`TwP2Q%+5CSzI5>ZR914Q2Sq@D*eP-x zO91%s01@L4e_RSv$Eh+wUuT}%uG7MuU{6UHmJTl7r4}&wmHNLDj)8MQxe6|oDf^wP z7$tY-NPwb=VdW!VHR}aaM8{!5o;spJMuST16d&cUY8LY78iOLpT>cHh zNWk%mObg*ScAi5}G*ko-4}m)b;f!+-Z&k@b;*B0MkPAfs-hQG23sbcgZNl=TTtalh zI7ADOt-l@e*nH(el?s+SlOjMQbrf+RQ@1NO4GD-)_lym>cn7^H*x(8?Q+9)dj7ppa zG>S?(95ZvNX$}Jd?NvJt)cQgp>vrQnx1y|9ibCXj94Z_u!~wb;Xj{9{-b=5=&ZDw<>);m37q+T(;XOx8?TiC(Q?SKC8Ag1&Aj@iWSTci7FPt&PzLltXHC|;vK0F+*10|Gvl24! zhS&G;puW5P-dqnt{p0@+BS;{kqm}&q_9iL@o|AaMtTnIBTPjP6XSDtdIpFJeQX+71 zHDi_Mt*Oht;_RE19>>VdmlYxnj2fA@;_2hxd7g7s91yocra{?94P6&sYdH6B;UfKs_}mG14{`k9rlUtpRAJ$?_DP z;?Z?{M=4lfg53l%zf#bfmLb#)#@cAlZ>o?qhAVf4Ntu`20x0hxC9OE^g`3&2EgT+= zabSu7B?XPnJ_Ly@hgXV#K(cAi4+0TIfgW5-6otSx1D+w3iU2mDG~gDe3|#5hnas~W zz=(u4l0CZBJd%r$EK&V;3pf8pEz*573j zq3`_2cmD$h4A zA1k+CXebB&Mb>&H^s*g!oz$OYSG^JHH`JR>#1wp*(ujk9dP?G})Pw5NFL@}CwHBNwA17Qd;)mJ zUI@|b158oHW)b>mnChcYP$)mjK#LRZ5+*VXp)54P1#OrRyZzPM1%h?NL_g5IX&vI3 z?towLP+}BL=-CDzPBQnjW8nn_%vE5!>@8q;gSP>WlX` z&8sAEc_e->$V1SPFb+d#A2CKc6TL3ozMSr}0%2ay#*m6bC58`m;@IW{#+S{4u~&$* zBQ983fK;68O&1ta5$ngZc6>iGzFV9CCZgA!>#S$Ds|Zs7nv85mUR;z0UWn*cG$*Jt z3W~qsZle7XqjIae*iiz+4C|JSA@qmGTr~F6DOsl@ zi-%`eE3gXwDH!F|dYR~N{P}PH#Bb13|G$+L0AEge>(oJJ?veU;gn!L(c=G?8>3>ds zo!GPK>(wu3Tg7yGIeClle|62e;_6UkWjry$0|K5lqD#G!_@gq{!#tG<=DI~??h7G; zz{3ZdlE^rCP5EKfhb#?@3`Uv>V*iJc=tBFen5k5MO$8N`*(YSa^Zh9l~+cbL@;zMPZeM>lluy3huAWBz;o?#Ch$w5{MZknLaWq zXHS&`0K-}~Z(XQwH=WY2p62U)7XVdAJ4{sQls*S!Qc3eqHGXi|oqsoL~ zDfPEV&HjbtZ!+<3j?HQMJer~BOpq&6_1bqk`9IUbCq>~Sjr}TLjuwoueOvx^W&y-A zK?$4&|Y}c|yYmO)=hCZe86HAXmq9or;p`b_t>tkm&6H-lR z65l0INLLWF+j(lNb`5hW+o2mtForsF-%{N{89Ulq3Sox3B)~xip2iu+#q(D}vb$0w z0bS{%uo_w^0cu2L$+p5Nn+TN!i_oR)q#l3A*B)%Eb+Ox-q<|7E@35R6 zR=XWe&(cli!~$&shV|E1R2lOxDgm4pm%DJniHI*28Jq^yYWoe`IFwjLw#!NsoEro- zt>8E%Fij@2!wMmibu6DQ(-9m5ia3{Cm=VwZt&dnD9R{(%cRY3L@E+G*yNFO^#1CLw zH+t&-w0DHrRHgV{Ef&lNx4V+HX+HrMMv8XR!=U@sZ99v(j7dpMpzf>$(68P$mFDw# z?qtC}U-GE9Y9eOsb5B7yO&tm+4MNeX<2LGQ;G4`tvC^v>bSqpPB(-C4$e!mSf#*jp6oNb;^f$@pR{Lu~gvqB}pCLUg zKox0S&(Sin_`WcCEwereedi+eXL6grzDME<_<%y!8hIx&%=Z9K&8A(r#I8t?5tNC} zak5qdQVCa($bn%rfgyAvS|;T7EFhb)W>eqvV0t>4NpiHE>Q;>fJLigniS5V6^ZO!D z0y2vS)Qo=rSPkcwWoJBt1>J(E`lX|EX6k_nAd)}}j$;s10omrCI>9&N39SK)k(+2X zo^s+)iOh&_7#(vMgo}WIW2A{>(kW2GL^nM9`$r`}5|mAVF8oV#kVuX0uj^7_9w94Q zq(m^~csh*Pcb-#QqE6~TD2|hlL201sXYgIXy8AP<%+uN@4ig0Tzjt@^k9HNRUJ z^Enp644bvDmH>cL;cfxdw%7$8BvZu(TT1~BVJhSB2)kIL(@RexqnWe!0YvL)!raH% ze``Z{B#03Z8VGoiynz@_IMP?Kl&}@ilWPXr7-jp0Lsb?i>^u|*=r)ikUGZ`^EJ>Y_ z)a;&ZqtL*EI8~(>ZnhW{h@ZGID%pg6GQ~^{C$vL}i~Cc9GA8s|-*rqkI}=^XH85sR zc*FP}3Q+^g3&J(JifaG?2)ijSw{{wu(dVfGbCRViL+A&g*>JR$m0U*->hVt zH^fO103cO6t;G`Z{YfL2SwYXtYL$GwN%%tPO0sM z%DOcnoK@M?eHd8&t?8(aʁ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 literal 31233 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

VJca6nosg~zu%#x_>VE4K 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 diff --git a/docs/source/conf.py b/docs/source/conf.py index ea153536..f4c02c87 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -13,7 +13,7 @@ from sphinxawesome_theme.postprocess import Icons project = "Judge0 Python SDK" -copyright = "2024, Judge0" +copyright = "2025, Judge0" author = "Judge0" release = "" diff --git a/docs/source/contributors_guide/contributing.rst b/docs/source/contributors_guide/contributing.rst index 4c1623b9..237bab67 100644 --- a/docs/source/contributors_guide/contributing.rst +++ b/docs/source/contributors_guide/contributing.rst @@ -4,20 +4,20 @@ Contributing Preparing the development setup ------------------------------- -1. Install Python 3.9 +1. Install Python 3.10 .. code-block:: console $ sudo add-apt-repository ppa:deadsnakes/ppa $ sudo apt update - $ sudo apt install python3.9 python3.9-venv + $ sudo apt install python3.10 python3.10-venv 2. Clone the repo, create and activate a new virtual environment .. code-block:: console $ cd judge0-python - $ python3.9 -m venv venv + $ python3.10 -m venv venv $ . venv/bin/activate 3. Install the library and development dependencies diff --git a/pyproject.toml b/pyproject.toml index c869e238..ac58706c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "judge0" version = "0.0.5.dev0" description = "The official Python SDK for Judge0." readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" authors = [{ name = "Judge0", email = "contact@judge0.com" }] classifiers = [ "Intended Audience :: Developers", @@ -11,7 +11,6 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -46,7 +45,7 @@ test = [ "flake8-docstrings==1.7.0", ] docs = [ - "sphinx==7.4.7", + "sphinx==8.1.3", "sphinxawesome-theme==5.3.2", "sphinx-autodoc-typehints==2.3.0", "sphinx-multiversion==0.2.4", From db408cd4ed64a026559bb4f8cdf12e9ac771a75d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sat, 8 Nov 2025 22:40:16 +0100 Subject: [PATCH 127/161] Bump version to 0.0.5 --- pyproject.toml | 2 +- src/judge0/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ac58706c..53718b3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.0.5.dev0" +version = "0.0.5" description = "The official Python SDK for Judge0." readme = "README.md" requires-python = ">=3.10" diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index 97c9a7e4..387b651f 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -30,7 +30,7 @@ from .retry import MaxRetries, MaxWaitTime, RegularPeriodRetry from .submission import Submission -__version__ = "0.0.5.dev0" +__version__ = "0.0.5" __all__ = [ "ATD", From 26c5c84e3d320fc32931319d9b5c8d87321a8116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sat, 8 Nov 2025 23:06:10 +0100 Subject: [PATCH 128/161] Fix python-version in publish.yml --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0ab3dff3..b768eb27 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.10 + python-version: "3.10" # Install build and twine - name: Install Build Tools From 3a8f970e469415deaba375a9224a2ea657d2088a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sat, 8 Nov 2025 23:09:00 +0100 Subject: [PATCH 129/161] Bump to new dev release --- pyproject.toml | 2 +- src/judge0/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 53718b3d..8a4d7a34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.0.5" +version = "0.1.0.dev0" description = "The official Python SDK for Judge0." readme = "README.md" requires-python = ">=3.10" diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index 387b651f..b2a4dcb4 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -30,7 +30,7 @@ from .retry import MaxRetries, MaxWaitTime, RegularPeriodRetry from .submission import Submission -__version__ = "0.0.5" +__version__ = "0.1.0.dev0" __all__ = [ "ATD", From 417648bb838f8e73d884df61178c86f88748aecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 9 Nov 2025 03:45:18 +0100 Subject: [PATCH 130/161] Add example for running LLM code --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index a9eb6005..ca554a1a 100644 --- a/README.md +++ b/README.md @@ -223,3 +223,34 @@ import judge0 client = judge0.get_client() print(client.get_languages()) ``` + +### Running LLM-Generated Code + +```python +import os +from ollama import Client +import judge0 + +# Get your Ollama Cloud API key from https://ollama.com. +client = Client( + host="https://ollama.com", + headers={"Authorization": "Bearer " + os.environ.get("OLLAMA_API_KEY")}, +) + +system = "You are a helpful assistant that can execute Python code. Only respond with the code to be executed and nothing else. Strip backticks in code blocks." +prompt = "Calculate how many r's are in the word 'strawberry'." + +response = client.chat( + model="gpt-oss:120b-cloud", + messages=[ + {"role": "system", "content": system}, + {"role": "user", "content": prompt}, + ], +) + +code = response["message"]["content"] +print(f"Generated code:\n{code}") + +result = judge0.run(source_code=code, language=judge0.PYTHON) +print(f"Execution result:\n{result.stdout}") +``` From 9478549635fecc0fe2287e9d4f7590791dcde7ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 9 Nov 2025 03:48:33 +0100 Subject: [PATCH 131/161] Add hello, world example --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index ca554a1a..4caf7db4 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,13 @@ print(result.stdout) ``` ## Examples +### hello, world + +```python +import judge0 +result = judge0.run(source_code="print('hello, world')") +print(result.stdout) +``` ### Running C Programming Language From 8d93f74569a0ea78a77543a39c4cf2c82d724413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 9 Nov 2025 04:33:30 +0100 Subject: [PATCH 132/161] Add Tool Calling Example --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/README.md b/README.md index 4caf7db4..9d5aa122 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,8 @@ print(client.get_languages()) ### Running LLM-Generated Code +#### Simple + ```python import os from ollama import Client @@ -261,3 +263,59 @@ print(f"Generated code:\n{code}") result = judge0.run(source_code=code, language=judge0.PYTHON) print(f"Execution result:\n{result.stdout}") ``` + +#### Tool Calling (a.k.a. Function Calling) + +```python +import os +from ollama import Client +import judge0 + +# Get your Ollama Cloud API key from https://ollama.com. +client = Client( + host="https://ollama.com", + headers={"Authorization": "Bearer " + os.environ.get("OLLAMA_API_KEY")}, +) + +model="qwen3-coder:480b-cloud" + +messages=[ + {"role": "user", "content": "Calculate how many r's are in the word 'strawberry'."}, +] + +tools = [{ + "type": "function", + "function": { + "name": "execute_python", + "description": "Execute Python code and return result", + "parameters": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The Python code to execute" + } + }, + "required": ["code"] + } + } +}] + +response = client.chat(model=model, messages=messages, tools=tools) + +response_message = response["message"] +messages.append(response_message) + +if response_message.tool_calls: + for tool_call in response_message.tool_calls: + if tool_call.function.name == "execute_python": + result = judge0.run(source_code=tool_call.function.arguments["code"], language=judge0.PYTHON) + messages.append({ + "role": "tool", + "tool_name": "execute_python", + "content": result.stdout, + }) + +final_response = client.chat(model=model, messages=messages) +print(final_response["message"]["content"]) +``` From 93c6bdab03abf0a9922e60ef300010afa90b16e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 9 Nov 2025 13:40:09 +0100 Subject: [PATCH 133/161] Add Filesystem example --- README.md | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9d5aa122..0cd7054b 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ print(result.stdout) ```python import judge0 -result = judge0.run(source_code="print('hello, world')") +result = judge0.run(source_code="print('hello, world')", language=judge0.PYTHON) print(result.stdout) ``` @@ -233,7 +233,7 @@ print(client.get_languages()) ### Running LLM-Generated Code -#### Simple +#### Simple Example with Ollama ```python import os @@ -264,7 +264,7 @@ result = judge0.run(source_code=code, language=judge0.PYTHON) print(f"Execution result:\n{result.stdout}") ``` -#### Tool Calling (a.k.a. Function Calling) +#### Tool Calling (a.k.a. Function Calling) with Ollama ```python import os @@ -319,3 +319,38 @@ if response_message.tool_calls: final_response = client.chat(model=model, messages=messages) print(final_response["message"]["content"]) ``` + +### Filesystem + +```python +import judge0 +from judge0 import Filesystem, File, Submission + +fs = Filesystem( + content=[ + File(name="./my_dir1/my_file1.txt", content="hello from my_file.txt"), + ] +) + +source_code = """ +cat ./my_dir1/my_file1.txt + +mkdir my_dir2 +echo "hello, world" > ./my_dir2/my_file2.txt +""" + +submission = Submission( + source_code=source_code, + language=judge0.BASH, + additional_files=fs, +) + +result = judge0.run(submissions=submission) +fs = Filesystem(content=result.post_execution_filesystem) + +print(result.stdout) + +matches = [f for f in fs if f.name == "my_dir2/my_file2.txt"] +f = matches[0] if matches else None +print(f) +``` From 2000ef6251015fa61a47c5b4b9b19ad15600068c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 9 Nov 2025 13:50:26 +0100 Subject: [PATCH 134/161] Update Filesystem example --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 0cd7054b..12a47643 100644 --- a/README.md +++ b/README.md @@ -346,11 +346,9 @@ submission = Submission( ) result = judge0.run(submissions=submission) -fs = Filesystem(content=result.post_execution_filesystem) - print(result.stdout) -matches = [f for f in fs if f.name == "my_dir2/my_file2.txt"] +matches = [f for f in result.post_execution_filesystem if f.name == "my_dir2/my_file2.txt"] f = matches[0] if matches else None print(f) ``` From 1b1f8b19607e120058861c7e870952d2d5e31d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 9 Nov 2025 20:56:38 +0100 Subject: [PATCH 135/161] Switch to uv for project management (#20) * Remove .python-version from gitignore. Add uv.lock and .python-version. * Adapt the pyproject.toml. Update contributing docs. Update docs build Makefile. * Update workflows. --- .github/workflows/docs.yml | 91 +- .github/workflows/publish.yml | 59 +- .github/workflows/test.yml | 12 +- .gitignore | 2 +- .python-version | 1 + docs/Makefile | 2 +- .../contributors_guide/contributing.rst | 31 +- pyproject.toml | 8 +- uv.lock | 1305 +++++++++++++++++ 9 files changed, 1404 insertions(+), 107 deletions(-) create mode 100644 .python-version create mode 100644 uv.lock diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b8e06c26..182c3f95 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,63 +5,62 @@ on: push: branches: ["master"] - jobs: build: runs-on: ubuntu-24.04 permissions: contents: write steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - fetch-depth: 0 # Fetch the full history - ref: ${{ github.ref }} # Check out the current branch or tag + - uses: actions/checkout@v4 + with: + persist-credentials: false + fetch-depth: 0 # Fetch the full history + ref: ${{ github.ref }} # Check out the current branch or tag - - name: Fetch tags only - run: git fetch --tags --no-recurse-submodules + - name: Fetch tags only + run: git fetch --tags --no-recurse-submodules - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.10" + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -e .[docs] + - name: Install uv and dependencies + run: | + curl -LsSf https://astral.sh/uv/0.9.8/install.sh | sh + uv sync --group docs - - name: Build documentation - run: sphinx-multiversion docs/source docs/build/html --keep-going --no-color + - name: Build documentation + run: uv run sphinx-multiversion docs/source docs/build/html --keep-going --no-color - - name: Get the latest tag - run: | - # Fetch all tags - git fetch --tags - # Get the latest tag - latest_tag=$(git tag --sort=-creatordate | head -n 1) - echo "LATEST_RELEASE=$latest_tag" >> $GITHUB_ENV + - name: Get the latest tag + run: | + # Fetch all tags + git fetch --tags + # Get the latest tag + latest_tag=$(git tag --sort=-creatordate | head -n 1) + echo "LATEST_RELEASE=$latest_tag" >> $GITHUB_ENV - - name: Generate index.html for judge0.github.io/judge0-python. - run: | - echo ' - - - - - ' > docs/build/html/index.html - env: - latest_release: ${{ env.LATEST_RELEASE }} + - name: Generate index.html for judge0.github.io/judge0-python. + run: | + echo ' + + + + + ' > docs/build/html/index.html + env: + latest_release: ${{ env.LATEST_RELEASE }} - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: html-docs - path: docs/build/html/ + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: html-docs + path: docs/build/html/ - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 - if: github.ref == 'refs/heads/master' - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: docs/build/html + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + if: github.ref == 'refs/heads/master' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: docs/build/html diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b768eb27..ab561792 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -10,39 +10,30 @@ on: jobs: publish: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - # Checkout the repository - - name: Checkout Code - uses: actions/checkout@v3 - - # Set up Python environment - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - # Install build and twine - - name: Install Build Tools - run: | - python -m pip install --upgrade pip - pip install build twine - - # Build the package - - name: Build the Package - run: python -m build - - - name: Publish to Test PyPI - if: github.event.inputs.repository == 'test' - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.TEST_PYPI_TOKEN }} - run: twine upload --repository-url https://test.pypi.org/legacy/ dist/* - - - name: Publish to Regular PyPI - if: github.event.inputs.repository == 'pypi' - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - run: twine upload dist/* + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install uv + run: curl -LsSf https://astral.sh/uv/0.9.8/install.sh | sh + + - name: Build package with uv + run: uv build + + - name: Publish to Test PyPI + if: github.event.inputs.repository == 'test' + env: + UV_PUBLISH_TOKEN: ${{ secrets.TEST_PYPI_TOKEN }} + run: uv publish --token $UV_PUBLISH_TOKEN --repository testpypi + + - name: Publish to Regular PyPI + if: github.event.inputs.repository == 'pypi' + env: + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }} + run: uv publish --token $UV_PUBLISH_TOKEN diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 31c375ad..a2aa0db1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,12 +22,10 @@ jobs: uses: actions/setup-python@v3 with: python-version: "3.10" - - name: Install dependencies + - name: Install uv and project run: | - python -m venv venv - source venv/bin/activate - python -m pip install --upgrade pip - pip install -e .[test] + curl -LsSf https://astral.sh/uv/0.9.8/install.sh | sh + uv sync --group test - name: Test with pytest env: # Add necessary api keys as env variables. JUDGE0_ATD_API_KEY: ${{ secrets.JUDGE0_ATD_API_KEY }} @@ -38,6 +36,4 @@ jobs: JUDGE0_EXTRA_CE_ENDPOINT: ${{ secrets.JUDGE0_EXTRA_CE_ENDPOINT }} JUDGE0_CLOUD_CE_AUTH_HEADERS: ${{ secrets.JUDGE0_CLOUD_CE_AUTH_HEADERS }} JUDGE0_CLOUD_EXTRA_CE_AUTH_HEADERS: ${{ secrets.JUDGE0_CLOUD_EXTRA_CE_AUTH_HEADERS }} - run: | - source venv/bin/activate - pytest tests + run: uv run pytest tests diff --git a/.gitignore b/.gitignore index 13480660..82f92755 100644 --- a/.gitignore +++ b/.gitignore @@ -85,7 +85,7 @@ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: -.python-version +# .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..7c7a975f --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10 \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile index d0c3cbf1..faf5089f 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -4,7 +4,7 @@ # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build +SPHINXBUILD ?= uv run sphinx-build SOURCEDIR = source BUILDDIR = build diff --git a/docs/source/contributors_guide/contributing.rst b/docs/source/contributors_guide/contributing.rst index 237bab67..7c4e240d 100644 --- a/docs/source/contributors_guide/contributing.rst +++ b/docs/source/contributors_guide/contributing.rst @@ -4,28 +4,37 @@ Contributing Preparing the development setup ------------------------------- -1. Install Python 3.10 +1. Install `uv `_. +2. Fork the repository (in GitHub) and clone the forked repository: .. code-block:: console - $ sudo add-apt-repository ppa:deadsnakes/ppa - $ sudo apt update - $ sudo apt install python3.10 python3.10-venv + $ git clone https://github.com//judge0-python + $ cd judge0-python -2. Clone the repo, create and activate a new virtual environment +3. Prepare the virtual environment and dependencies: .. code-block:: console - $ cd judge0-python - $ python3.10 -m venv venv - $ . venv/bin/activate + $ uv sync --group dev + +4. Install the necessary development tools: + +.. code-block:: console + + $ uv tool install pre-commit + +5. Make sure all the necessary tools are installed: + +.. code-block:: console + + $ uv tool list -3. Install the library and development dependencies +6. Finally, install the pre-commit hooks: .. code-block:: console - $ pip install -e .[dev] - $ pre-commit install + $ uv tool run pre-commit install Building documentation ---------------------- diff --git a/pyproject.toml b/pyproject.toml index 8a4d7a34..8595aac9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,10 +35,9 @@ Homepage = "https://github.com/judge0/judge0-python" Repository = "https://github.com/judge0/judge0-python.git" Issues = "https://github.com/judge0/judge0-python/issues" -[project.optional-dependencies] +[dependency-groups] test = [ "ufmt==2.7.3", - "pre-commit==3.8.0", "pytest==8.3.3", "python-dotenv==1.0.1", "pytest-cov==6.0.0", @@ -50,10 +49,7 @@ docs = [ "sphinx-autodoc-typehints==2.3.0", "sphinx-multiversion==0.2.4", ] -dev = [ - "judge0[test]", - "judge0[docs]", -] +dev = [{ include-group = "test" }, { include-group = "docs" }] [tool.flake8] extend-ignore = [ diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..8d05bf15 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1305 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version == '3.13.*'", + "python_full_version >= '3.11' and python_full_version < '3.13'", + "python_full_version < '3.11'", +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, +] + +[[package]] +name = "black" +version = "25.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/43/20b5c90612d7bdb2bdbcceeb53d588acca3bb8f0e4c5d5c751a2c8fdd55a/black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619", size = 648393, upload-time = "2025-09-19T00:27:37.758Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/40/dbe31fc56b218a858c8fc6f5d8d3ba61c1fa7e989d43d4a4574b8b992840/black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7", size = 1715605, upload-time = "2025-09-19T00:36:13.483Z" }, + { url = "https://files.pythonhosted.org/packages/92/b2/f46800621200eab6479b1f4c0e3ede5b4c06b768e79ee228bc80270bcc74/black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92", size = 1571829, upload-time = "2025-09-19T00:32:42.13Z" }, + { url = "https://files.pythonhosted.org/packages/4e/64/5c7f66bd65af5c19b4ea86062bb585adc28d51d37babf70969e804dbd5c2/black-25.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96b6726d690c96c60ba682955199f8c39abc1ae0c3a494a9c62c0184049a713", size = 1631888, upload-time = "2025-09-19T00:30:54.212Z" }, + { url = "https://files.pythonhosted.org/packages/3b/64/0b9e5bfcf67db25a6eef6d9be6726499a8a72ebab3888c2de135190853d3/black-25.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:d119957b37cc641596063cd7db2656c5be3752ac17877017b2ffcdb9dfc4d2b1", size = 1327056, upload-time = "2025-09-19T00:31:08.877Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f4/7531d4a336d2d4ac6cc101662184c8e7d068b548d35d874415ed9f4116ef/black-25.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa", size = 1698727, upload-time = "2025-09-19T00:31:14.264Z" }, + { url = "https://files.pythonhosted.org/packages/28/f9/66f26bfbbf84b949cc77a41a43e138d83b109502cd9c52dfc94070ca51f2/black-25.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d", size = 1555679, upload-time = "2025-09-19T00:31:29.265Z" }, + { url = "https://files.pythonhosted.org/packages/bf/59/61475115906052f415f518a648a9ac679d7afbc8da1c16f8fdf68a8cebed/black-25.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608", size = 1617453, upload-time = "2025-09-19T00:30:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5b/20fd5c884d14550c911e4fb1b0dae00d4abb60a4f3876b449c4d3a9141d5/black-25.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f", size = 1333655, upload-time = "2025-09-19T00:30:56.715Z" }, + { url = "https://files.pythonhosted.org/packages/fb/8e/319cfe6c82f7e2d5bfb4d3353c6cc85b523d677ff59edc61fdb9ee275234/black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0", size = 1742012, upload-time = "2025-09-19T00:33:08.678Z" }, + { url = "https://files.pythonhosted.org/packages/94/cc/f562fe5d0a40cd2a4e6ae3f685e4c36e365b1f7e494af99c26ff7f28117f/black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4", size = 1581421, upload-time = "2025-09-19T00:35:25.937Z" }, + { url = "https://files.pythonhosted.org/packages/84/67/6db6dff1ebc8965fd7661498aea0da5d7301074b85bba8606a28f47ede4d/black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e", size = 1655619, upload-time = "2025-09-19T00:30:49.241Z" }, + { url = "https://files.pythonhosted.org/packages/10/10/3faef9aa2a730306cf469d76f7f155a8cc1f66e74781298df0ba31f8b4c8/black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a", size = 1342481, upload-time = "2025-09-19T00:31:29.625Z" }, + { url = "https://files.pythonhosted.org/packages/48/99/3acfea65f5e79f45472c45f87ec13037b506522719cd9d4ac86484ff51ac/black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175", size = 1742165, upload-time = "2025-09-19T00:34:10.402Z" }, + { url = "https://files.pythonhosted.org/packages/3a/18/799285282c8236a79f25d590f0222dbd6850e14b060dfaa3e720241fd772/black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f", size = 1581259, upload-time = "2025-09-19T00:32:49.685Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ce/883ec4b6303acdeca93ee06b7622f1fa383c6b3765294824165d49b1a86b/black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831", size = 1655583, upload-time = "2025-09-19T00:30:44.505Z" }, + { url = "https://files.pythonhosted.org/packages/21/17/5c253aa80a0639ccc427a5c7144534b661505ae2b5a10b77ebe13fa25334/black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357", size = 1343428, upload-time = "2025-09-19T00:32:13.839Z" }, + { url = "https://files.pythonhosted.org/packages/1b/46/863c90dcd3f9d41b109b7f19032ae0db021f0b2a81482ba0a1e28c84de86/black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae", size = 203363, upload-time = "2025-09-19T00:27:35.724Z" }, +] + +[[package]] +name = "certifi" +version = "2025.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.11.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/32/e6/7c4006cf689ed7a4aa75dcf1f14acbc04e585714c220b5cc6d231096685a/coverage-7.11.2.tar.gz", hash = "sha256:ae43149b7732df15c3ca9879b310c48b71d08cd8a7ba77fda7f9108f78499e93", size = 814849, upload-time = "2025-11-08T20:26:33.011Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/5b/d943b719938467d313973fd83af9c810e248fcec33165d5ab0148ab1c602/coverage-7.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:004bdc5985b86f565772af627925e368256ee2172623db10a0d78a3b53f20ef1", size = 216802, upload-time = "2025-11-08T20:23:47.186Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f7/d3c096ca6a6212e8a536ae2144406d28b43e7528ff05a0bf6a5336319d0d/coverage-7.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3aa8c62460499e10ceac5ea61cc09c4f7ddcd8a68c6313cf08785ad353dfd311", size = 217317, upload-time = "2025-11-08T20:23:50.255Z" }, + { url = "https://files.pythonhosted.org/packages/10/46/d0dbafbd3604293b73a44ae9c88e339921c13f309138b31ec60b451895b9/coverage-7.11.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d73da4893125e0671f762e408dea9957b2bda0036c9589c2fd258a6b870acbdb", size = 244068, upload-time = "2025-11-08T20:23:51.63Z" }, + { url = "https://files.pythonhosted.org/packages/3d/16/ef8aba300f7224167c556d15852bf35d42c7af93b68f3ef82323737515e8/coverage-7.11.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:805efa416085999da918f15f81b26636d8e79863e1fbac1495664686d1e6a6e9", size = 245896, upload-time = "2025-11-08T20:23:53.1Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ea/02fa537e61bc61fd111d5d9611184a354dd26bbc31e58ccd922f76404723/coverage-7.11.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c65f4291aec39692a3bfbe1d92ae5bea58c16b5553fdf021de61c655d987233f", size = 247755, upload-time = "2025-11-08T20:23:54.88Z" }, + { url = "https://files.pythonhosted.org/packages/41/3b/6cc19074059c030e489fd5ff934aa49521a75ba6236d27badb3b4270b21c/coverage-7.11.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7658f3d4f728092368c091c18efcfb679be9b612c93bfdf345f33635a325188", size = 244714, upload-time = "2025-11-08T20:23:56.655Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d5/b3480a0fd9c45fad37884c38ee943788ef43b64abf156b3f8e6af096c62e/coverage-7.11.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9f5f6ee021b3b25e748a9a053f3a8dd61a62b6689efd6425cb47e27360994903", size = 245800, upload-time = "2025-11-08T20:23:58.06Z" }, + { url = "https://files.pythonhosted.org/packages/07/2a/34f1476db9c58c410193f8f0cbecdfd9931912ed07de628fdffe0dae216d/coverage-7.11.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9a95b7a6043b221ec1a0d4d5481e424272b37028353265fbe5fcd3768d652eb7", size = 243808, upload-time = "2025-11-08T20:23:59.756Z" }, + { url = "https://files.pythonhosted.org/packages/73/fd/b43a0a4f6306a486d31cdd4166afd4dc0b08a8f072d7ab2ccc23893b6d19/coverage-7.11.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:94ced4a29a6987af99faaa49a513bf8d0458e8af004c54174e05dd7a8a31c7d9", size = 244070, upload-time = "2025-11-08T20:24:01.281Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8c/bcbe2c9cb81ef008d05b04ebc37a3a1c65d61b61c9cf772f0ae473ddc56b/coverage-7.11.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8014a28a37ffabf7da7107f4f154d68c6b89672f27fef835a0574591c5cd140b", size = 244688, upload-time = "2025-11-08T20:24:02.641Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f7/c6c276f6663a1d7e29f8cc4a5a8c76dbf834ecb74017936187146adbce9e/coverage-7.11.2-cp310-cp310-win32.whl", hash = "sha256:43ecf9dca4fcb3baf8a886019dd5ce663c95a5e1c5172719c414f0ebd9eeb785", size = 219382, upload-time = "2025-11-08T20:24:04.476Z" }, + { url = "https://files.pythonhosted.org/packages/4f/aa/0d07b2d567f1d005088b4afad533b4a6af48ec75f3f9071afbe5f7076cab/coverage-7.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:230317450af65a37c1fdbdd3546f7277e0c1c1b65e0d57409248e5dd0fa13493", size = 220319, upload-time = "2025-11-08T20:24:06.464Z" }, + { url = "https://files.pythonhosted.org/packages/89/39/326336c0adc6dc624be0edb5143dec90a9da2626335e83f6d09da120922f/coverage-7.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:36c41bf2ee6f6062de8177e249fee17cd5c9662cd373f7a41e6468a34c5b9c0f", size = 216927, upload-time = "2025-11-08T20:24:08.167Z" }, + { url = "https://files.pythonhosted.org/packages/b7/68/cd1d3422fc9525827cddf62b2385f78356b88e745e90e8e512fefcc05f8f/coverage-7.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:397778cf6d50df59c890bd3ac10acb5bf413388ff6a013305134f1403d5db648", size = 217429, upload-time = "2025-11-08T20:24:09.939Z" }, + { url = "https://files.pythonhosted.org/packages/36/73/3f384dd79d6bbdf7fbceda3c7e0db33e148559bc18c49022c9c0c5e512c1/coverage-7.11.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c85f44ed4260221e46a4e9e8e8df4b359ab6c0a742c79e85d649779bcf77b534", size = 247832, upload-time = "2025-11-08T20:24:11.897Z" }, + { url = "https://files.pythonhosted.org/packages/45/3c/27839b6f343998e82f3e470397c058566c953dc71fe37e0abb953133a341/coverage-7.11.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cbffd1d5c5bf4c576ca247bf77646cdad4dced82928337eeb0b85e2b3be4d64b", size = 249749, upload-time = "2025-11-08T20:24:13.705Z" }, + { url = "https://files.pythonhosted.org/packages/6e/51/011102c7f6902084e632128ac0f42cd3345acc543a7c9f8ce5e1a94397ef/coverage-7.11.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea10a57568af7cf082a7a4d98a699f993652c2ffbdd5a6c9d63c9ca10b693b4d", size = 251860, upload-time = "2025-11-08T20:24:15.113Z" }, + { url = "https://files.pythonhosted.org/packages/bb/4c/4622eb7aac98c2552ed8a176a6015ea8cf36a2ec75cbcfb5f2ccf100bbd6/coverage-7.11.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4b1bea4c707f4c09f682fe0e646a114dfd068f627880d4a208850d01f8164ad", size = 247942, upload-time = "2025-11-08T20:24:16.637Z" }, + { url = "https://files.pythonhosted.org/packages/95/94/42ba12fc827fb504f8f8ec5313e46cf5582cdb9d4823e76d70ed22e88bdf/coverage-7.11.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1ac3f647ecf25d883051ef42d38d823016e715b9f289f8c1768be5117075d1bd", size = 249553, upload-time = "2025-11-08T20:24:18.153Z" }, + { url = "https://files.pythonhosted.org/packages/a3/47/2cd8014c872a3e469ffe50fbc692d02c7460e20cd701a0d6366fbef759e3/coverage-7.11.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d423991415f73a70c0a5f3e0a226cf4ab374dd0da7409978069b844df3d31582", size = 247627, upload-time = "2025-11-08T20:24:19.644Z" }, + { url = "https://files.pythonhosted.org/packages/a9/31/e722f2c7f0f16954d13e6441a24d841174bcb1ff2421c6504c024c09c7af/coverage-7.11.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0f4a958ff286038ac870f836351e9fb8912f1614d1cdbda200fc899235f7dc9b", size = 247353, upload-time = "2025-11-08T20:24:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/d4fd26be0ce7993f0013df9788e52cd83a1adf5cfb9887bfd1b38722380e/coverage-7.11.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d1ff4b87ad438148976f2215141a490ae000e878536370d53f8da8c59a175a6", size = 248251, upload-time = "2025-11-08T20:24:22.724Z" }, + { url = "https://files.pythonhosted.org/packages/1c/33/003f7b5f10fae2ad7390e57a1520c46a24bd46e374b197e97050ae47751f/coverage-7.11.2-cp311-cp311-win32.whl", hash = "sha256:e448ceee2fb880427eafc9a3f8e6162b2ac7cc3e9b30b85d6511f25cc8a11820", size = 219410, upload-time = "2025-11-08T20:24:24.15Z" }, + { url = "https://files.pythonhosted.org/packages/22/e8/5db102c57143f33a9229ecdc8d7976ad0c5d103fcd26f2b939db96789990/coverage-7.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:bc65e32fe5bb942f0f5247e1500e355cbbdf326181198f5e27e3bb3ddb81e203", size = 220342, upload-time = "2025-11-08T20:24:25.947Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b2/9908f6b4b979045c01e02a069ae5f73c16dff022c296a5e1fd756c602c6c/coverage-7.11.2-cp311-cp311-win_arm64.whl", hash = "sha256:e8eb6cbd7d3b238335b5da0f3ce281102435afb503be4d7bdd69eea3c700a952", size = 219014, upload-time = "2025-11-08T20:24:27.382Z" }, + { url = "https://files.pythonhosted.org/packages/4f/98/aef630a13bc974333aeb83d69765eb513f790bf4bd5b79b8036ec176de8e/coverage-7.11.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:eaa2a5eeb82fa7a6a9cd65c4f968ee2a53839d451b4e88e060c67d87a0a40732", size = 217103, upload-time = "2025-11-08T20:24:28.938Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1f/41f144dc49c07043230ad79126a9c79236724579c43175e476e0731ddc2a/coverage-7.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:07e14a4050525fd98bf3d793f229eb8b3ae81678f4031e38e6a18a068bd59fd4", size = 217467, upload-time = "2025-11-08T20:24:30.758Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/6fc4b47c7c8323b0326c57786858b6185668f008edc2ea626bc35fb53e28/coverage-7.11.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:03e7e7dc31a7deaebf121c3c3bd3c6442b7fbf50aca72aae2a1d08aa30ca2a20", size = 248947, upload-time = "2025-11-08T20:24:32.559Z" }, + { url = "https://files.pythonhosted.org/packages/22/38/03bb7b3d991259ef8d483b83560f87eb4c6d5e8889ad836d212e010d08b3/coverage-7.11.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d752a8e398a19e2fb24781e4c73089bfeb417b6ac55f96c2c42cfe5bdb21cc18", size = 251707, upload-time = "2025-11-08T20:24:34.371Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/c32c7c76c8373978bf68bcfd87a1d265ace9c973ed9a007cada37f25948a/coverage-7.11.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a02818ec44803e325d66bd022828212df934739b894d1699c9a05b9105d30f2", size = 252793, upload-time = "2025-11-08T20:24:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/60/16/86582ab283bad8e137f76e97c5b75a81f547174bca9bb2eba8b7be33d8b6/coverage-7.11.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d30a717493583c2a83c99f195e934c073be7f4291b32b7352c246d52e43f6893", size = 249331, upload-time = "2025-11-08T20:24:37.462Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/24449d3e2a84bd38c1903757265cd45b6c9021ecf013f27e33155dba5ada/coverage-7.11.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:55ae008253df6000bc885a780c1b0e939bd8c932f41e16df1cfe19a00428a98a", size = 250728, upload-time = "2025-11-08T20:24:38.936Z" }, + { url = "https://files.pythonhosted.org/packages/86/bc/fcfe9bdda15f48ef6d78a8524837216752fe82474965d42310e6296c8bde/coverage-7.11.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:17047fb65fcd1ce8a2f97dd2247c2b59cb4bc8848b3911db02dcb05856f91b71", size = 248877, upload-time = "2025-11-08T20:24:40.444Z" }, + { url = "https://files.pythonhosted.org/packages/51/27/58db09afcb155f41739330c521258782eefc12fe18f70d3b8e5dbc61857b/coverage-7.11.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5f72a49504e1f35443b157d97997c9259a017384373eab52fd09b8ade2ae4674", size = 248455, upload-time = "2025-11-08T20:24:42.479Z" }, + { url = "https://files.pythonhosted.org/packages/24/6b/1eba5fa2b01b1aa727aa2a1c480c5f475fccecf32decae95b890cef7ee68/coverage-7.11.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5c31cdbb95ab0f4a60224a04efc43cfb406ce904f0b60fb6b2a72f37718ea5cb", size = 250316, upload-time = "2025-11-08T20:24:44.029Z" }, + { url = "https://files.pythonhosted.org/packages/08/58/46d3dcb99366c74b0478f2a58fd97e82419871a50989937e08578f9a5c5c/coverage-7.11.2-cp312-cp312-win32.whl", hash = "sha256:632904d126ca97e5d4ecf7e51ae8b20f086b6f002c6075adcfd4ff3a28574527", size = 219617, upload-time = "2025-11-08T20:24:46.086Z" }, + { url = "https://files.pythonhosted.org/packages/94/19/ab26b96a5c6fd0b5d644524997b60523b3ccbe7473a069e1385a272be238/coverage-7.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:c7ea5dec77d79dabb7b5fc712c59361aac52e459cd22028480625c3c743323d0", size = 220427, upload-time = "2025-11-08T20:24:47.809Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/948b268909f04eb2b0a55e22f1e4b3ffd472a8a398d05ebcf95c36d8b1eb/coverage-7.11.2-cp312-cp312-win_arm64.whl", hash = "sha256:ed6ba9f1777fdd1c8e5650c7d123211fa484a187c61af4d82948dc5ee3c0afcc", size = 219068, upload-time = "2025-11-08T20:24:49.813Z" }, + { url = "https://files.pythonhosted.org/packages/ec/00/57f3f8adaced9e4c74f482932e093176df7e400b4bb95dc1f3cd499511b5/coverage-7.11.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:38a5509fe7fabb6fb3161059b947641753b6529150ef483fc01c4516a546f2ad", size = 217125, upload-time = "2025-11-08T20:24:51.368Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2a/ff1a55673161608c895080950cdfbb6485c95e6fa57a92d2cd1e463717b3/coverage-7.11.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7e01ab8d69b6cffa2463e78a4d760a6b69dfebe5bf21837eabcc273655c7e7b3", size = 217499, upload-time = "2025-11-08T20:24:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/73/e3/eaac01709ffbef291a12ca2526b6247f55ab17724e2297cc70921cd9a81f/coverage-7.11.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b4776c6555a9f378f37fa06408f2e1cc1d06e4c4e06adb3d157a4926b549efbe", size = 248479, upload-time = "2025-11-08T20:24:54.825Z" }, + { url = "https://files.pythonhosted.org/packages/75/25/d846d2d08d182eeb30d1eba839fabdd9a3e6c710a1f187657b9c697bab23/coverage-7.11.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f70fa1ef17cba5dada94e144ea1b6e117d4f174666842d1da3aaf765d6eb477", size = 251074, upload-time = "2025-11-08T20:24:56.442Z" }, + { url = "https://files.pythonhosted.org/packages/2e/7a/34c9402ad12bce609be4be1146a7d22a7fae8e9d752684b6315cce552a65/coverage-7.11.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:811bff1f93566a8556a9aeb078bd82573e37f4d802a185fba4cbe75468615050", size = 252318, upload-time = "2025-11-08T20:24:57.987Z" }, + { url = "https://files.pythonhosted.org/packages/cf/2f/292fe3cea4cc1c4b8fb060fa60e565ab1b3bfc67bda74bedefb24b4a2407/coverage-7.11.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d0e80c9946da61cc0bf55dfd90d65707acc1aa5bdcb551d4285ea8906255bb33", size = 248641, upload-time = "2025-11-08T20:24:59.642Z" }, + { url = "https://files.pythonhosted.org/packages/c5/af/33ccb2aa2f43bbc330a1fccf84a396b90f2e61c00dccb7b72b2993a3c795/coverage-7.11.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:10f10c9acf584ef82bfaaa7296163bd11c7487237f1670e81fc2fa7e972be67b", size = 250457, upload-time = "2025-11-08T20:25:01.358Z" }, + { url = "https://files.pythonhosted.org/packages/bd/91/4b5b58f34e0587fbc5c1a28d644d9c20c13349c1072aea507b6e372c8f20/coverage-7.11.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fd3f7cc6cb999e3eff91a2998a70c662b0fcd3c123d875766147c530ca0d3248", size = 248421, upload-time = "2025-11-08T20:25:02.895Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d5/5c5ed220b15f490717522d241629c522fa22275549a6ccfbc96a3654b009/coverage-7.11.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e52a028a56889d3ad036c0420e866e4a69417d3203e2fc5f03dcb8841274b64c", size = 248244, upload-time = "2025-11-08T20:25:04.742Z" }, + { url = "https://files.pythonhosted.org/packages/1e/27/504088aba40735132db838711d966e1314931ff9bddcd0e2ea6bc7e345a7/coverage-7.11.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f6f985e175dfa1fb8c0a01f47186720ae25d5e20c181cc5f3b9eba95589b8148", size = 250004, upload-time = "2025-11-08T20:25:06.633Z" }, + { url = "https://files.pythonhosted.org/packages/ea/89/4d61c0ad0d39656bd5e73fe41a93a34b063c90333258e6307aadcfcdbb97/coverage-7.11.2-cp313-cp313-win32.whl", hash = "sha256:e48b95abe2983be98cdf52900e07127eb7fe7067c87a700851f4f1f53d2b00e6", size = 219639, upload-time = "2025-11-08T20:25:08.27Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a7/a298afa025ebe7a2afd6657871a1ac2d9c49666ce00f9a35ee9df61a3bd8/coverage-7.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:ea910cc737ee8553c81ad5c104bc5b135106ebb36f88be506c3493e001b4c733", size = 220445, upload-time = "2025-11-08T20:25:09.906Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a1/1825f5eadc0a0a6ea1c6e678827e1ec8c0494dbd23270016fccfc3358fbf/coverage-7.11.2-cp313-cp313-win_arm64.whl", hash = "sha256:ef2d3081562cd83f97984a96e02e7a294efa28f58d5e7f4e28920f59fd752b41", size = 219077, upload-time = "2025-11-08T20:25:11.777Z" }, + { url = "https://files.pythonhosted.org/packages/c0/61/98336c6f4545690b482e805c3a1a83fb2db4c19076307b187db3d421b5b3/coverage-7.11.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:87d7c7b0b2279e174f36d276e2afb7bf16c9ea04e824d4fa277eea1854f4cfd4", size = 217818, upload-time = "2025-11-08T20:25:13.697Z" }, + { url = "https://files.pythonhosted.org/packages/57/ee/6dca6e5f1a4affba8d3224996d0e9145e6d67817da753cc436e48bb8d0e6/coverage-7.11.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:940d195f4c8ba3ec6e7c302c9f546cdbe63e57289ed535452bc52089b1634f1c", size = 218170, upload-time = "2025-11-08T20:25:15.284Z" }, + { url = "https://files.pythonhosted.org/packages/ec/17/9c9ca3ef09d3576027e77cf580eb599d8d655f9ca2456a26ca50c53e07e3/coverage-7.11.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e3b92e10ca996b5421232dd6629b9933f97eb57ce374bca800ab56681fbeda2b", size = 259466, upload-time = "2025-11-08T20:25:17.373Z" }, + { url = "https://files.pythonhosted.org/packages/53/96/2001a596827a0b91ba5f627f21b5ce998fa1f27d861a8f6d909f5ea663ff/coverage-7.11.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:61d6a7cc1e7a7a761ac59dcc88cee54219fd4231face52bd1257cfd3df29ae9f", size = 261530, upload-time = "2025-11-08T20:25:19.085Z" }, + { url = "https://files.pythonhosted.org/packages/4d/bb/fea7007035fdc3c40fcca0ab740da549ff9d38fa50b0d37cd808fbbf9683/coverage-7.11.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bee1911c44c52cad6b51d436aa8c6ff5ca5d414fa089c7444592df9e7b890be9", size = 263963, upload-time = "2025-11-08T20:25:21.168Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b3/7452071353441b632ebea42f6ad328a7ab592e4bc50a31f9921b41667017/coverage-7.11.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4c4423ea9c28749080b41e18ec74d658e6c9f148a6b47e719f3d7f56197f8227", size = 258644, upload-time = "2025-11-08T20:25:22.928Z" }, + { url = "https://files.pythonhosted.org/packages/e6/05/6e56b1c2b3308f587508ad4b0a4cb76c8d6179fea2df148e071979b3eb77/coverage-7.11.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:689d3b4dd0d4c912ed8bfd7a1b5ff2c5ecb1fa16571840573174704ff5437862", size = 261539, upload-time = "2025-11-08T20:25:25.277Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/7afeeac2a49f651318e4a83f1d5f4d3d4f4092f1d451ac4aec8069cddbdb/coverage-7.11.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:75ef769be19d69ea71b0417d7fbf090032c444792579cdf9b166346a340987d5", size = 259153, upload-time = "2025-11-08T20:25:28.098Z" }, + { url = "https://files.pythonhosted.org/packages/1e/77/08f3b5c7500b2031cee74e8a01f9a5bc407f781ff6a826707563bb9dd5b7/coverage-7.11.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6681164bc697b93676945c8c814b76ac72204c395e11b71ba796a93b33331c24", size = 258043, upload-time = "2025-11-08T20:25:30.087Z" }, + { url = "https://files.pythonhosted.org/packages/ca/49/8e080e7622bd7c82df0f8324bbe0461ed1032a638b80046f1a53a88ea3a8/coverage-7.11.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4aa799c61869318d2b86c0d3c413d6805546aec42069f009cbb27df2eefb2790", size = 260243, upload-time = "2025-11-08T20:25:31.722Z" }, + { url = "https://files.pythonhosted.org/packages/dc/75/da033d8589661527b4a6d30c414005467e48fbccc0f3c10898af183e14e1/coverage-7.11.2-cp313-cp313t-win32.whl", hash = "sha256:9a6468e1a3a40d3d1f9120a9ff221d3eacef4540a6f819fff58868fe0bd44fa9", size = 220309, upload-time = "2025-11-08T20:25:33.9Z" }, + { url = "https://files.pythonhosted.org/packages/29/ef/8a477d41dbcde1f1179c13c43c9f77ee926b793fe3e5f1cf5d868a494679/coverage-7.11.2-cp313-cp313t-win_amd64.whl", hash = "sha256:30c437e8b51ce081fe3903c9e368e85c9a803b093fd062c49215f3bf4fd1df37", size = 221374, upload-time = "2025-11-08T20:25:35.88Z" }, + { url = "https://files.pythonhosted.org/packages/0d/a3/4c3cdd737ed1f630b821430004c2d5f1088b9bc0a7115aa5ad7c40d7d5cb/coverage-7.11.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a35701fe0b5ee9d4b67d31aa76555237af32a36b0cf8dd33f8a74470cf7cd2f5", size = 219648, upload-time = "2025-11-08T20:25:37.572Z" }, + { url = "https://files.pythonhosted.org/packages/52/d1/43d17c299249085d6e0df36db272899e92aa09e68e27d3e92a4cf8d9523e/coverage-7.11.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7f933bc1fead57373922e383d803e1dd5ec7b5a786c220161152ebee1aa3f006", size = 217170, upload-time = "2025-11-08T20:25:39.254Z" }, + { url = "https://files.pythonhosted.org/packages/78/66/f21c03307079a0b7867b364af057430018a3d4a18ed1b99e1adaf5a0f305/coverage-7.11.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f80cb5b328e870bf3df0568b41643a85ee4b8ccd219a096812389e39aa310ea4", size = 217497, upload-time = "2025-11-08T20:25:41.277Z" }, + { url = "https://files.pythonhosted.org/packages/f0/dd/0a2257154c32f442fe3b4622501ab818ae4bd7cde33bd7a740630f6bd24c/coverage-7.11.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6b2498f86f2554ed6cb8df64201ee95b8c70fb77064a8b2ae8a7185e7a4a5f0", size = 248539, upload-time = "2025-11-08T20:25:43.349Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ca/c55ab0ee5ebfc4ab56cfc1b3585cba707342dc3f891fe19f02e07bc0c25f/coverage-7.11.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a913b21f716aa05b149a8656e9e234d9da04bc1f9842136ad25a53172fecc20e", size = 251057, upload-time = "2025-11-08T20:25:45.083Z" }, + { url = "https://files.pythonhosted.org/packages/db/01/a149b88ebe714b76d95427d609e629446d1df5d232f4bdaec34e471da124/coverage-7.11.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5769159986eb174f0f66d049a52da03f2d976ac1355679371f1269e83528599", size = 252393, upload-time = "2025-11-08T20:25:47.272Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a4/a992c805e95c46f0ac1b83782aa847030cb52bbfd8fc9015cff30f50fb9e/coverage-7.11.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:89565d7c9340858424a5ca3223bfefe449aeb116942cdc98cd76c07ca50e9db8", size = 248534, upload-time = "2025-11-08T20:25:49.034Z" }, + { url = "https://files.pythonhosted.org/packages/78/01/318ed024ae245dbc76152bc016919aef69c508a5aac0e2da5de9b1efea61/coverage-7.11.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b7fc943097fa48de00d14d2a2f3bcebfede024e031d7cd96063fe135f8cbe96e", size = 250412, upload-time = "2025-11-08T20:25:51.2Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f9/f05c7984ef48c8d1c6c1ddb243223b344dcd8c6c0d54d359e4e325e2fa7e/coverage-7.11.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:72a3d109ac233666064d60b29ae5801dd28bc51d1990e69f183a2b91b92d4baf", size = 248367, upload-time = "2025-11-08T20:25:53.399Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ac/461ed0dcaba0c727b760057ffa9837920d808a35274e179ff4a94f6f755a/coverage-7.11.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:4648c90cf741fb61e142826db1557a44079de0ca868c5c5a363c53d852897e84", size = 248187, upload-time = "2025-11-08T20:25:55.402Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bf/8510ce8c7b1a8d682726df969e7523ee8aac23964b2c8301b8ce2400c1b4/coverage-7.11.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f1aa017b47e1879d7bac50161b00d2b886f2ff3882fa09427119e1b3572ede1", size = 249849, upload-time = "2025-11-08T20:25:57.186Z" }, + { url = "https://files.pythonhosted.org/packages/75/6f/ea1c8990ca35d607502c9e531f164573ea59bb6cd5cd4dc56d7cc3d1fcb5/coverage-7.11.2-cp314-cp314-win32.whl", hash = "sha256:44b6e04bb94e59927a2807cd4de86386ce34248eaea95d9f1049a72f81828c38", size = 219908, upload-time = "2025-11-08T20:25:58.896Z" }, + { url = "https://files.pythonhosted.org/packages/1e/04/a64e2a8b9b65ae84670207dc6073e3d48ee9192646440b469e9b8c335d1f/coverage-7.11.2-cp314-cp314-win_amd64.whl", hash = "sha256:7ea36e981a8a591acdaa920704f8dc798f9fff356c97dbd5d5702046ae967ce1", size = 220724, upload-time = "2025-11-08T20:26:01.122Z" }, + { url = "https://files.pythonhosted.org/packages/73/df/eb4e9f9d0d55f7ec2b55298c30931a665c2249c06e3d1d14c5a6df638c77/coverage-7.11.2-cp314-cp314-win_arm64.whl", hash = "sha256:4aaf2212302b6f748dde596424b0f08bc3e1285192104e2480f43d56b6824f35", size = 219296, upload-time = "2025-11-08T20:26:02.918Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b5/e9bb3b17a65fe92d1c7a2363eb5ae9893fafa578f012752ed40eee6aa3c8/coverage-7.11.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:84e8e0f5ab5134a2d32d4ebadc18b433dbbeddd0b73481f816333b1edd3ff1c8", size = 217905, upload-time = "2025-11-08T20:26:04.633Z" }, + { url = "https://files.pythonhosted.org/packages/38/6f/1f38dd0b63a9d82fb3c9d7fbe1c9dab26ae77e5b45e801d129664e039034/coverage-7.11.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5db683000ff6217273071c752bd6a1d341b6dc5d6aaa56678c53577a4e70e78a", size = 218172, upload-time = "2025-11-08T20:26:06.677Z" }, + { url = "https://files.pythonhosted.org/packages/fd/5d/2aeb513c6841270783b216478c6edc65b128c6889850c5f77568aa3a3098/coverage-7.11.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2970c03fefee2a5f1aebc91201a0706a7d0061cc71ab452bb5c5345b7174a349", size = 259537, upload-time = "2025-11-08T20:26:08.481Z" }, + { url = "https://files.pythonhosted.org/packages/d2/45/ddd9b22ec1b5c69cc579b149619c354f981aaaafc072b92574f2d3d6c267/coverage-7.11.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9f28b900d96d83e2ae855b68d5cf5a704fa0b5e618999133fd2fb3bbe35ecb1", size = 261648, upload-time = "2025-11-08T20:26:10.551Z" }, + { url = "https://files.pythonhosted.org/packages/29/e2/8743b7281decd3f73b964389fea18305584dd6ba96f0aff91b4880b50310/coverage-7.11.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8b9a7ebc6a29202fb095877fd8362aab09882894d1c950060c76d61fb116114", size = 264061, upload-time = "2025-11-08T20:26:12.306Z" }, + { url = "https://files.pythonhosted.org/packages/00/1b/46daea7c4349c4530c62383f45148cc878845374b7a632e3ac2769b2f26a/coverage-7.11.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4f8f6bcaa7fe162460abb38f7a5dbfd7f47cfc51e2a0bf0d3ef9e51427298391", size = 258580, upload-time = "2025-11-08T20:26:14.5Z" }, + { url = "https://files.pythonhosted.org/packages/d7/53/f9b1c2d921d585dd6499e05bd71420950cac4e800f71525eb3d2690944fe/coverage-7.11.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:461577af3f8ad4da244a55af66c0731b68540ce571dbdc02598b5ec9e7a09e73", size = 261526, upload-time = "2025-11-08T20:26:16.353Z" }, + { url = "https://files.pythonhosted.org/packages/86/7d/55acee453a71a71b08b05848d718ce6ac4559d051b4a2c407b0940aa72be/coverage-7.11.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5b284931d57389ec97a63fb1edf91c68ec369cee44bc40b37b5c3985ba0a2914", size = 259135, upload-time = "2025-11-08T20:26:18.101Z" }, + { url = "https://files.pythonhosted.org/packages/7d/3f/cf1e0217efdebab257eb0f487215fe02ff2b6f914cea641b2016c33358e1/coverage-7.11.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2ca963994d28e44285dc104cf94b25d8a7fd0c6f87cf944f46a23f473910703f", size = 257959, upload-time = "2025-11-08T20:26:19.894Z" }, + { url = "https://files.pythonhosted.org/packages/68/0e/e9be33e55346e650c3218a313e888df80418415462c63bceaf4b31e36ab5/coverage-7.11.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7d3fccd5781c5d29ca0bd1ea272630f05cd40a71d419e7e6105c0991400eb14", size = 260290, upload-time = "2025-11-08T20:26:22.05Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1d/9e93937c2a9bd255bb5efeff8c5df1c8322e508371f76f21a58af0e36a31/coverage-7.11.2-cp314-cp314t-win32.whl", hash = "sha256:f633da28958f57b846e955d28661b2b323d8ae84668756e1eea64045414dbe34", size = 220691, upload-time = "2025-11-08T20:26:24.043Z" }, + { url = "https://files.pythonhosted.org/packages/bf/30/893b5a67e2914cf2be8e99c511b8084eaa8c0585e42d8b3cd78208f5f126/coverage-7.11.2-cp314-cp314t-win_amd64.whl", hash = "sha256:410cafc1aba1f7eb8c09823d5da381be30a2c9b3595758a4c176fcfc04732731", size = 221800, upload-time = "2025-11-08T20:26:26.24Z" }, + { url = "https://files.pythonhosted.org/packages/2b/8b/6d93448c494a35000cc97d8d5d9c9b3774fa2b0c0d5be55f16877f962d71/coverage-7.11.2-cp314-cp314t-win_arm64.whl", hash = "sha256:595c6bb2b565cc2d930ee634cae47fa959dfd24cc0e8ae4cf2b6e7e131e0d1f7", size = 219838, upload-time = "2025-11-08T20:26:28.479Z" }, + { url = "https://files.pythonhosted.org/packages/05/7a/99766a75c88e576f47c2d9a06416ff5d95be9b42faca5c37e1ab77c4cd1a/coverage-7.11.2-py3-none-any.whl", hash = "sha256:2442afabe9e83b881be083238bb7cf5afd4a10e47f29b6094470338d2336b33c", size = 208891, upload-time = "2025-11-08T20:26:30.739Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "flake8" +version = "7.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, +] + +[[package]] +name = "flake8-docstrings" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flake8" }, + { name = "pydocstyle" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/24/f839e3a06e18f4643ccb81370909a497297909f15106e6af2fecdef46894/flake8_docstrings-1.7.0.tar.gz", hash = "sha256:4c8cc748dc16e6869728699e5d0d685da9a10b0ea718e090b1ba088e67a941af", size = 5995, upload-time = "2023-01-25T14:27:13.903Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/7d/76a278fa43250441ed9300c344f889c7fb1817080c8fb8996b840bf421c2/flake8_docstrings-1.7.0-py2.py3-none-any.whl", hash = "sha256:51f2344026da083fc084166a9353f5082b01f72901df422f74b4d953ae88ac75", size = 4994, upload-time = "2023-01-25T14:27:12.32Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "judge0" +version = "0.1.0.dev0" +source = { editable = "." } +dependencies = [ + { name = "pydantic" }, + { name = "requests" }, +] + +[package.dev-dependencies] +dev = [ + { name = "flake8-docstrings" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "python-dotenv" }, + { name = "sphinx" }, + { name = "sphinx-autodoc-typehints" }, + { name = "sphinx-multiversion" }, + { name = "sphinxawesome-theme" }, + { name = "ufmt" }, +] +docs = [ + { name = "sphinx" }, + { name = "sphinx-autodoc-typehints" }, + { name = "sphinx-multiversion" }, + { name = "sphinxawesome-theme" }, +] +test = [ + { name = "flake8-docstrings" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "python-dotenv" }, + { name = "ufmt" }, +] + +[package.metadata] +requires-dist = [ + { name = "pydantic", specifier = ">=2.0.0,<3.0.0" }, + { name = "requests", specifier = ">=2.28.0,<3.0.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "flake8-docstrings", specifier = "==1.7.0" }, + { name = "pytest", specifier = "==8.3.3" }, + { name = "pytest-cov", specifier = "==6.0.0" }, + { name = "python-dotenv", specifier = "==1.0.1" }, + { name = "sphinx", specifier = "==8.1.3" }, + { name = "sphinx-autodoc-typehints", specifier = "==2.3.0" }, + { name = "sphinx-multiversion", specifier = "==0.2.4" }, + { name = "sphinxawesome-theme", specifier = "==5.3.2" }, + { name = "ufmt", specifier = "==2.7.3" }, +] +docs = [ + { name = "sphinx", specifier = "==8.1.3" }, + { name = "sphinx-autodoc-typehints", specifier = "==2.3.0" }, + { name = "sphinx-multiversion", specifier = "==0.2.4" }, + { name = "sphinxawesome-theme", specifier = "==5.3.2" }, +] +test = [ + { name = "flake8-docstrings", specifier = "==1.7.0" }, + { name = "pytest", specifier = "==8.3.3" }, + { name = "pytest-cov", specifier = "==6.0.0" }, + { name = "python-dotenv", specifier = "==1.0.1" }, + { name = "ufmt", specifier = "==2.7.3" }, +] + +[[package]] +name = "libcst" +version = "1.8.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml", marker = "python_full_version != '3.13.*'" }, + { name = "pyyaml-ft", marker = "python_full_version == '3.13.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/cd/337df968b38d94c5aabd3e1b10630f047a2b345f6e1d4456bd9fe7417537/libcst-1.8.6.tar.gz", hash = "sha256:f729c37c9317126da9475bdd06a7208eb52fcbd180a6341648b45a56b4ba708b", size = 891354, upload-time = "2025-11-03T22:33:30.621Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/52/97d5454dee9d014821fe0c88f3dc0e83131b97dd074a4d49537056a75475/libcst-1.8.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a20c5182af04332cc94d8520792befda06d73daf2865e6dddc5161c72ea92cb9", size = 2211698, upload-time = "2025-11-03T22:31:50.117Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a4/d1205985d378164687af3247a9c8f8bdb96278b0686ac98ab951bc6d336a/libcst-1.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:36473e47cb199b7e6531d653ee6ffed057de1d179301e6c67f651f3af0b499d6", size = 2093104, upload-time = "2025-11-03T22:31:52.189Z" }, + { url = "https://files.pythonhosted.org/packages/9e/de/1338da681b7625b51e584922576d54f1b8db8fc7ff4dc79121afc5d4d2cd/libcst-1.8.6-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:06fc56335a45d61b7c1b856bfab4587b84cfe31e9d6368f60bb3c9129d900f58", size = 2237419, upload-time = "2025-11-03T22:31:53.526Z" }, + { url = "https://files.pythonhosted.org/packages/50/06/ee66f2d83b870534756e593d464d8b33b0914c224dff3a407e0f74dc04e0/libcst-1.8.6-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6b23d14a7fc0addd9795795763af26b185deb7c456b1e7cc4d5228e69dab5ce8", size = 2300820, upload-time = "2025-11-03T22:31:55.995Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ca/959088729de8e0eac8dd516e4fb8623d8d92bad539060fa85c9e94d418a5/libcst-1.8.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:16cfe0cfca5fd840e1fb2c30afb628b023d3085b30c3484a79b61eae9d6fe7ba", size = 2301201, upload-time = "2025-11-03T22:31:57.347Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4c/2a21a8c452436097dfe1da277f738c3517f3f728713f16d84b9a3d67ca8d/libcst-1.8.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:455f49a93aea4070132c30ebb6c07c2dea0ba6c1fde5ffde59fc45dbb9cfbe4b", size = 2408213, upload-time = "2025-11-03T22:31:59.221Z" }, + { url = "https://files.pythonhosted.org/packages/3e/26/8f7b671fad38a515bb20b038718fd2221ab658299119ac9bcec56c2ced27/libcst-1.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:72cca15800ffc00ba25788e4626189fe0bc5fe2a0c1cb4294bce2e4df21cc073", size = 2119189, upload-time = "2025-11-03T22:32:00.696Z" }, + { url = "https://files.pythonhosted.org/packages/5b/bf/ffb23a48e27001165cc5c81c5d9b3d6583b21b7f5449109e03a0020b060c/libcst-1.8.6-cp310-cp310-win_arm64.whl", hash = "sha256:6cad63e3a26556b020b634d25a8703b605c0e0b491426b3e6b9e12ed20f09100", size = 2001736, upload-time = "2025-11-03T22:32:02.986Z" }, + { url = "https://files.pythonhosted.org/packages/dc/15/95c2ecadc0fb4af8a7057ac2012a4c0ad5921b9ef1ace6c20006b56d3b5f/libcst-1.8.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3649a813660fbffd7bc24d3f810b1f75ac98bd40d9d6f56d1f0ee38579021073", size = 2211289, upload-time = "2025-11-03T22:32:04.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/c3/7e1107acd5ed15cf60cc07c7bb64498a33042dc4821874aea3ec4942f3cd/libcst-1.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cbe17067055829607c5ba4afa46bfa4d0dd554c0b5a583546e690b7367a29b6", size = 2092927, upload-time = "2025-11-03T22:32:06.209Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ff/0d2be87f67e2841a4a37d35505e74b65991d30693295c46fc0380ace0454/libcst-1.8.6-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:59a7e388c57d21d63722018978a8ddba7b176e3a99bd34b9b84a576ed53f2978", size = 2237002, upload-time = "2025-11-03T22:32:07.559Z" }, + { url = "https://files.pythonhosted.org/packages/69/99/8c4a1b35c7894ccd7d33eae01ac8967122f43da41325223181ca7e4738fe/libcst-1.8.6-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:b6c1248cc62952a3a005792b10cdef2a4e130847be9c74f33a7d617486f7e532", size = 2301048, upload-time = "2025-11-03T22:32:08.869Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8b/d1aa811eacf936cccfb386ae0585aa530ea1221ccf528d67144e041f5915/libcst-1.8.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6421a930b028c5ef4a943b32a5a78b7f1bf15138214525a2088f11acbb7d3d64", size = 2300675, upload-time = "2025-11-03T22:32:10.579Z" }, + { url = "https://files.pythonhosted.org/packages/c6/6b/7b65cd41f25a10c1fef2389ddc5c2b2cc23dc4d648083fa3e1aa7e0eeac2/libcst-1.8.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6d8b67874f2188399a71a71731e1ba2d1a2c3173b7565d1cc7ffb32e8fbaba5b", size = 2407934, upload-time = "2025-11-03T22:32:11.856Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8b/401cfff374bb3b785adfad78f05225225767ee190997176b2a9da9ed9460/libcst-1.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:b0d8c364c44ae343937f474b2e492c1040df96d94530377c2f9263fb77096e4f", size = 2119247, upload-time = "2025-11-03T22:32:13.279Z" }, + { url = "https://files.pythonhosted.org/packages/f1/17/085f59eaa044b6ff6bc42148a5449df2b7f0ba567307de7782fe85c39ee2/libcst-1.8.6-cp311-cp311-win_arm64.whl", hash = "sha256:5dcaaebc835dfe5755bc85f9b186fb7e2895dda78e805e577fef1011d51d5a5c", size = 2001774, upload-time = "2025-11-03T22:32:14.647Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3c/93365c17da3d42b055a8edb0e1e99f1c60c776471db6c9b7f1ddf6a44b28/libcst-1.8.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c13d5bd3d8414a129e9dccaf0e5785108a4441e9b266e1e5e9d1f82d1b943c9", size = 2206166, upload-time = "2025-11-03T22:32:16.012Z" }, + { url = "https://files.pythonhosted.org/packages/1d/cb/7530940e6ac50c6dd6022349721074e19309eb6aa296e942ede2213c1a19/libcst-1.8.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1472eeafd67cdb22544e59cf3bfc25d23dc94058a68cf41f6654ff4fcb92e09", size = 2083726, upload-time = "2025-11-03T22:32:17.312Z" }, + { url = "https://files.pythonhosted.org/packages/1b/cf/7e5eaa8c8f2c54913160671575351d129170db757bb5e4b7faffed022271/libcst-1.8.6-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:089c58e75cb142ec33738a1a4ea7760a28b40c078ab2fd26b270dac7d2633a4d", size = 2235755, upload-time = "2025-11-03T22:32:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/55/54/570ec2b0e9a3de0af9922e3bb1b69a5429beefbc753a7ea770a27ad308bd/libcst-1.8.6-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c9d7aeafb1b07d25a964b148c0dda9451efb47bbbf67756e16eeae65004b0eb5", size = 2301473, upload-time = "2025-11-03T22:32:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/11/4c/163457d1717cd12181c421a4cca493454bcabd143fc7e53313bc6a4ad82a/libcst-1.8.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:207481197afd328aa91d02670c15b48d0256e676ce1ad4bafb6dc2b593cc58f1", size = 2298899, upload-time = "2025-11-03T22:32:21.765Z" }, + { url = "https://files.pythonhosted.org/packages/35/1d/317ddef3669883619ef3d3395ea583305f353ef4ad87d7a5ac1c39be38e3/libcst-1.8.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:375965f34cc6f09f5f809244d3ff9bd4f6cb6699f571121cebce53622e7e0b86", size = 2408239, upload-time = "2025-11-03T22:32:23.275Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a1/f47d8cccf74e212dd6044b9d6dbc223636508da99acff1d54786653196bc/libcst-1.8.6-cp312-cp312-win_amd64.whl", hash = "sha256:da95b38693b989eaa8d32e452e8261cfa77fe5babfef1d8d2ac25af8c4aa7e6d", size = 2119660, upload-time = "2025-11-03T22:32:24.822Z" }, + { url = "https://files.pythonhosted.org/packages/19/d0/dd313bf6a7942cdf951828f07ecc1a7695263f385065edc75ef3016a3cb5/libcst-1.8.6-cp312-cp312-win_arm64.whl", hash = "sha256:bff00e1c766658adbd09a175267f8b2f7616e5ee70ce45db3d7c4ce6d9f6bec7", size = 1999824, upload-time = "2025-11-03T22:32:26.131Z" }, + { url = "https://files.pythonhosted.org/packages/90/01/723cd467ec267e712480c772aacc5aa73f82370c9665162fd12c41b0065b/libcst-1.8.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7445479ebe7d1aff0ee094ab5a1c7718e1ad78d33e3241e1a1ec65dcdbc22ffb", size = 2206386, upload-time = "2025-11-03T22:32:27.422Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/b944944f910f24c094f9b083f76f61e3985af5a376f5342a21e01e2d1a81/libcst-1.8.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4fc3fef8a2c983e7abf5d633e1884c5dd6fa0dcb8f6e32035abd3d3803a3a196", size = 2083945, upload-time = "2025-11-03T22:32:28.847Z" }, + { url = "https://files.pythonhosted.org/packages/36/a1/bd1b2b2b7f153d82301cdaddba787f4a9fc781816df6bdb295ca5f88b7cf/libcst-1.8.6-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:1a3a5e4ee870907aa85a4076c914ae69066715a2741b821d9bf16f9579de1105", size = 2235818, upload-time = "2025-11-03T22:32:30.504Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ab/f5433988acc3b4d188c4bb154e57837df9488cc9ab551267cdeabd3bb5e7/libcst-1.8.6-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6609291c41f7ad0bac570bfca5af8fea1f4a27987d30a1fa8b67fe5e67e6c78d", size = 2301289, upload-time = "2025-11-03T22:32:31.812Z" }, + { url = "https://files.pythonhosted.org/packages/5d/57/89f4ba7a6f1ac274eec9903a9e9174890d2198266eee8c00bc27eb45ecf7/libcst-1.8.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25eaeae6567091443b5374b4c7d33a33636a2d58f5eda02135e96fc6c8807786", size = 2299230, upload-time = "2025-11-03T22:32:33.242Z" }, + { url = "https://files.pythonhosted.org/packages/f2/36/0aa693bc24cce163a942df49d36bf47a7ed614a0cd5598eee2623bc31913/libcst-1.8.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04030ea4d39d69a65873b1d4d877def1c3951a7ada1824242539e399b8763d30", size = 2408519, upload-time = "2025-11-03T22:32:34.678Z" }, + { url = "https://files.pythonhosted.org/packages/db/18/6dd055b5f15afa640fb3304b2ee9df8b7f72e79513814dbd0a78638f4a0e/libcst-1.8.6-cp313-cp313-win_amd64.whl", hash = "sha256:8066f1b70f21a2961e96bedf48649f27dfd5ea68be5cd1bed3742b047f14acde", size = 2119853, upload-time = "2025-11-03T22:32:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ed/5ddb2a22f0b0abdd6dcffa40621ada1feaf252a15e5b2733a0a85dfd0429/libcst-1.8.6-cp313-cp313-win_arm64.whl", hash = "sha256:c188d06b583900e662cd791a3f962a8c96d3dfc9b36ea315be39e0a4c4792ebf", size = 1999808, upload-time = "2025-11-03T22:32:38.1Z" }, + { url = "https://files.pythonhosted.org/packages/25/d3/72b2de2c40b97e1ef4a1a1db4e5e52163fc7e7740ffef3846d30bc0096b5/libcst-1.8.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c41c76e034a1094afed7057023b1d8967f968782433f7299cd170eaa01ec033e", size = 2190553, upload-time = "2025-11-03T22:32:39.819Z" }, + { url = "https://files.pythonhosted.org/packages/0d/20/983b7b210ccc3ad94a82db54230e92599c4a11b9cfc7ce3bc97c1d2df75c/libcst-1.8.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5432e785322aba3170352f6e72b32bea58d28abd141ac37cc9b0bf6b7c778f58", size = 2074717, upload-time = "2025-11-03T22:32:41.373Z" }, + { url = "https://files.pythonhosted.org/packages/13/f2/9e01678fedc772e09672ed99930de7355757035780d65d59266fcee212b8/libcst-1.8.6-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:85b7025795b796dea5284d290ff69de5089fc8e989b25d6f6f15b6800be7167f", size = 2225834, upload-time = "2025-11-03T22:32:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/4a/0d/7bed847b5c8c365e9f1953da274edc87577042bee5a5af21fba63276e756/libcst-1.8.6-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:536567441182a62fb706e7aa954aca034827b19746832205953b2c725d254a93", size = 2287107, upload-time = "2025-11-03T22:32:44.549Z" }, + { url = "https://files.pythonhosted.org/packages/02/f0/7e51fa84ade26c518bfbe7e2e4758b56d86a114c72d60309ac0d350426c4/libcst-1.8.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f04d3672bde1704f383a19e8f8331521abdbc1ed13abb349325a02ac56e5012", size = 2288672, upload-time = "2025-11-03T22:32:45.867Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cd/15762659a3f5799d36aab1bc2b7e732672722e249d7800e3c5f943b41250/libcst-1.8.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f04febcd70e1e67917be7de513c8d4749d2e09206798558d7fe632134426ea4", size = 2392661, upload-time = "2025-11-03T22:32:47.232Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6b/b7f9246c323910fcbe021241500f82e357521495dcfe419004dbb272c7cb/libcst-1.8.6-cp313-cp313t-win_amd64.whl", hash = "sha256:1dc3b897c8b0f7323412da3f4ad12b16b909150efc42238e19cbf19b561cc330", size = 2105068, upload-time = "2025-11-03T22:32:49.145Z" }, + { url = "https://files.pythonhosted.org/packages/a6/0b/4fd40607bc4807ec2b93b054594373d7fa3d31bb983789901afcb9bcebe9/libcst-1.8.6-cp313-cp313t-win_arm64.whl", hash = "sha256:44f38139fa95e488db0f8976f9c7ca39a64d6bc09f2eceef260aa1f6da6a2e42", size = 1985181, upload-time = "2025-11-03T22:32:50.597Z" }, + { url = "https://files.pythonhosted.org/packages/3a/60/4105441989e321f7ad0fd28ffccb83eb6aac0b7cfb0366dab855dcccfbe5/libcst-1.8.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:b188e626ce61de5ad1f95161b8557beb39253de4ec74fc9b1f25593324a0279c", size = 2204202, upload-time = "2025-11-03T22:32:52.311Z" }, + { url = "https://files.pythonhosted.org/packages/67/2f/51a6f285c3a183e50cfe5269d4a533c21625aac2c8de5cdf2d41f079320d/libcst-1.8.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:87e74f7d7dfcba9efa91127081e22331d7c42515f0a0ac6e81d4cf2c3ed14661", size = 2083581, upload-time = "2025-11-03T22:32:54.269Z" }, + { url = "https://files.pythonhosted.org/packages/2f/64/921b1c19b638860af76cdb28bc81d430056592910b9478eea49e31a7f47a/libcst-1.8.6-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:3a926a4b42015ee24ddfc8ae940c97bd99483d286b315b3ce82f3bafd9f53474", size = 2236495, upload-time = "2025-11-03T22:32:55.723Z" }, + { url = "https://files.pythonhosted.org/packages/12/a8/b00592f9bede618cbb3df6ffe802fc65f1d1c03d48a10d353b108057d09c/libcst-1.8.6-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:3f4fbb7f569e69fd9e89d9d9caa57ca42c577c28ed05062f96a8c207594e75b8", size = 2301466, upload-time = "2025-11-03T22:32:57.337Z" }, + { url = "https://files.pythonhosted.org/packages/af/df/790d9002f31580fefd0aec2f373a0f5da99070e04c5e8b1c995d0104f303/libcst-1.8.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:08bd63a8ce674be431260649e70fca1d43f1554f1591eac657f403ff8ef82c7a", size = 2300264, upload-time = "2025-11-03T22:32:58.852Z" }, + { url = "https://files.pythonhosted.org/packages/21/de/dc3f10e65bab461be5de57850d2910a02c24c3ddb0da28f0e6e4133c3487/libcst-1.8.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e00e275d4ba95d4963431ea3e409aa407566a74ee2bf309a402f84fc744abe47", size = 2408572, upload-time = "2025-11-03T22:33:00.552Z" }, + { url = "https://files.pythonhosted.org/packages/20/3b/35645157a7590891038b077db170d6dd04335cd2e82a63bdaa78c3297dfe/libcst-1.8.6-cp314-cp314-win_amd64.whl", hash = "sha256:fea5c7fa26556eedf277d4f72779c5ede45ac3018650721edd77fd37ccd4a2d4", size = 2193917, upload-time = "2025-11-03T22:33:02.354Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a2/1034a9ba7d3e82f2c2afaad84ba5180f601aed676d92b76325797ad60951/libcst-1.8.6-cp314-cp314-win_arm64.whl", hash = "sha256:bb9b4077bdf8857b2483879cbbf70f1073bc255b057ec5aac8a70d901bb838e9", size = 2078748, upload-time = "2025-11-03T22:33:03.707Z" }, + { url = "https://files.pythonhosted.org/packages/95/a1/30bc61e8719f721a5562f77695e6154e9092d1bdf467aa35d0806dcd6cea/libcst-1.8.6-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:55ec021a296960c92e5a33b8d93e8ad4182b0eab657021f45262510a58223de1", size = 2188980, upload-time = "2025-11-03T22:33:05.152Z" }, + { url = "https://files.pythonhosted.org/packages/2c/14/c660204532407c5628e3b615015a902ed2d0b884b77714a6bdbe73350910/libcst-1.8.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ba9ab2b012fbd53b36cafd8f4440a6b60e7e487cd8b87428e57336b7f38409a4", size = 2074828, upload-time = "2025-11-03T22:33:06.864Z" }, + { url = "https://files.pythonhosted.org/packages/82/e2/c497c354943dff644749f177ee9737b09ed811b8fc842b05709a40fe0d1b/libcst-1.8.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c0a0cc80aebd8aa15609dd4d330611cbc05e9b4216bcaeabba7189f99ef07c28", size = 2225568, upload-time = "2025-11-03T22:33:08.354Z" }, + { url = "https://files.pythonhosted.org/packages/86/ef/45999676d07bd6d0eefa28109b4f97124db114e92f9e108de42ba46a8028/libcst-1.8.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:42a4f68121e2e9c29f49c97f6154e8527cd31021809cc4a941c7270aa64f41aa", size = 2286523, upload-time = "2025-11-03T22:33:10.206Z" }, + { url = "https://files.pythonhosted.org/packages/f4/6c/517d8bf57d9f811862f4125358caaf8cd3320a01291b3af08f7b50719db4/libcst-1.8.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a434c521fadaf9680788b50d5c21f4048fa85ed19d7d70bd40549fbaeeecab1", size = 2288044, upload-time = "2025-11-03T22:33:11.628Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/24d7d49478ffb61207f229239879845da40a374965874f5ee60f96b02ddb/libcst-1.8.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6a65f844d813ab4ef351443badffa0ae358f98821561d19e18b3190f59e71996", size = 2392605, upload-time = "2025-11-03T22:33:12.962Z" }, + { url = "https://files.pythonhosted.org/packages/39/c3/829092ead738b71e96a4e96896c96f276976e5a8a58b4473ed813d7c962b/libcst-1.8.6-cp314-cp314t-win_amd64.whl", hash = "sha256:bdb14bc4d4d83a57062fed2c5da93ecb426ff65b0dc02ddf3481040f5f074a82", size = 2181581, upload-time = "2025-11-03T22:33:14.514Z" }, + { url = "https://files.pythonhosted.org/packages/98/6d/5d6a790a02eb0d9d36c4aed4f41b277497e6178900b2fa29c35353aa45ed/libcst-1.8.6-cp314-cp314t-win_arm64.whl", hash = "sha256:819c8081e2948635cab60c603e1bbdceccdfe19104a242530ad38a36222cb88f", size = 2065000, upload-time = "2025-11-03T22:33:16.257Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, +] + +[[package]] +name = "moreorless" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/85/2e4999ac4a21ab3c5f31e2a48e0989a80be3afc512a7983e3253615983d4/moreorless-0.5.0.tar.gz", hash = "sha256:560a04f85006fccd74feaa4b6213a446392ff7b5ec0194a5464b6c30f182fa33", size = 14093, upload-time = "2025-05-04T22:29:59.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/2e/9ea80ca55b73530b7639c6f146a58f636ddfe5a852ad467a44fe3e80d809/moreorless-0.5.0-py3-none-any.whl", hash = "sha256:66228870cd2f14bad5c3c3780aa71e29d3b2d9b5a01c03bfbf105efd4f668ecf", size = 14380, upload-time = "2025-05-04T22:29:57.417Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pycodestyle" +version = "2.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038, upload-time = "2025-11-05T10:50:08.59Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400, upload-time = "2025-11-05T10:50:06.732Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, + { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, + { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, + { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, + { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, + { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, + { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, + { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, + { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, + { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, +] + +[[package]] +name = "pydocstyle" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "snowballstemmer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/d5385ca59fd065e3c6a5fe19f9bc9d5ea7f2509fa8c9c22fb6b2031dd953/pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1", size = 36796, upload-time = "2023-01-17T20:29:19.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/ea/99ddefac41971acad68f14114f38261c1f27dac0b3ec529824ebc739bdaa/pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019", size = 38038, upload-time = "2023-01-17T20:29:18.094Z" }, +] + +[[package]] +name = "pyflakes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487, upload-time = "2024-09-10T10:52:15.003Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341, upload-time = "2024-09-10T10:52:12.54Z" }, +] + +[[package]] +name = "pytest-cov" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945, upload-time = "2024-10-29T20:13:35.363Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949, upload-time = "2024-10-29T20:13:33.215Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115, upload-time = "2024-01-23T06:33:00.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863, upload-time = "2024-01-23T06:32:58.246Z" }, +] + +[[package]] +name = "pytokens" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/8d/a762be14dae1c3bf280202ba3172020b2b0b4c537f94427435f19c413b72/pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", size = 17644, upload-time = "2025-11-05T13:36:35.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/25/d9db8be44e205a124f6c98bc0324b2bb149b7431c53877fc6d1038dddaf5/pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3", size = 12195, upload-time = "2025-11-05T13:36:33.183Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "pyyaml-ft" +version = "8.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/eb/5a0d575de784f9a1f94e2b1288c6886f13f34185e13117ed530f32b6f8a8/pyyaml_ft-8.0.0.tar.gz", hash = "sha256:0c947dce03954c7b5d38869ed4878b2e6ff1d44b08a0d84dc83fdad205ae39ab", size = 141057, upload-time = "2025-06-10T15:32:15.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/ba/a067369fe61a2e57fb38732562927d5bae088c73cb9bb5438736a9555b29/pyyaml_ft-8.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8c1306282bc958bfda31237f900eb52c9bedf9b93a11f82e1aab004c9a5657a6", size = 187027, upload-time = "2025-06-10T15:31:48.722Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c5/a3d2020ce5ccfc6aede0d45bcb870298652ac0cf199f67714d250e0cdf39/pyyaml_ft-8.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30c5f1751625786c19de751e3130fc345ebcba6a86f6bddd6e1285342f4bbb69", size = 176146, upload-time = "2025-06-10T15:31:50.584Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bb/23a9739291086ca0d3189eac7cd92b4d00e9fdc77d722ab610c35f9a82ba/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fa992481155ddda2e303fcc74c79c05eddcdbc907b888d3d9ce3ff3e2adcfb0", size = 746792, upload-time = "2025-06-10T15:31:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c2/e8825f4ff725b7e560d62a3609e31d735318068e1079539ebfde397ea03e/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cec6c92b4207004b62dfad1f0be321c9f04725e0f271c16247d8b39c3bf3ea42", size = 786772, upload-time = "2025-06-10T15:31:54.712Z" }, + { url = "https://files.pythonhosted.org/packages/35/be/58a4dcae8854f2fdca9b28d9495298fd5571a50d8430b1c3033ec95d2d0e/pyyaml_ft-8.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06237267dbcab70d4c0e9436d8f719f04a51123f0ca2694c00dd4b68c338e40b", size = 778723, upload-time = "2025-06-10T15:31:56.093Z" }, + { url = "https://files.pythonhosted.org/packages/86/ed/fed0da92b5d5d7340a082e3802d84c6dc9d5fa142954404c41a544c1cb92/pyyaml_ft-8.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8a7f332bc565817644cdb38ffe4739e44c3e18c55793f75dddb87630f03fc254", size = 758478, upload-time = "2025-06-10T15:31:58.314Z" }, + { url = "https://files.pythonhosted.org/packages/f0/69/ac02afe286275980ecb2dcdc0156617389b7e0c0a3fcdedf155c67be2b80/pyyaml_ft-8.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d10175a746be65f6feb86224df5d6bc5c049ebf52b89a88cf1cd78af5a367a8", size = 799159, upload-time = "2025-06-10T15:31:59.675Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ac/c492a9da2e39abdff4c3094ec54acac9747743f36428281fb186a03fab76/pyyaml_ft-8.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:58e1015098cf8d8aec82f360789c16283b88ca670fe4275ef6c48c5e30b22a96", size = 158779, upload-time = "2025-06-10T15:32:01.029Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9b/41998df3298960d7c67653669f37710fa2d568a5fc933ea24a6df60acaf6/pyyaml_ft-8.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e64fa5f3e2ceb790d50602b2fd4ec37abbd760a8c778e46354df647e7c5a4ebb", size = 191331, upload-time = "2025-06-10T15:32:02.602Z" }, + { url = "https://files.pythonhosted.org/packages/0f/16/2710c252ee04cbd74d9562ebba709e5a284faeb8ada88fcda548c9191b47/pyyaml_ft-8.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d445bf6ea16bb93c37b42fdacfb2f94c8e92a79ba9e12768c96ecde867046d1", size = 182879, upload-time = "2025-06-10T15:32:04.466Z" }, + { url = "https://files.pythonhosted.org/packages/9a/40/ae8163519d937fa7bfa457b6f78439cc6831a7c2b170e4f612f7eda71815/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c56bb46b4fda34cbb92a9446a841da3982cdde6ea13de3fbd80db7eeeab8b49", size = 811277, upload-time = "2025-06-10T15:32:06.214Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/28d82dbff7f87b96f0eeac79b7d972a96b4980c1e445eb6a857ba91eda00/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dab0abb46eb1780da486f022dce034b952c8ae40753627b27a626d803926483b", size = 831650, upload-time = "2025-06-10T15:32:08.076Z" }, + { url = "https://files.pythonhosted.org/packages/e8/df/161c4566facac7d75a9e182295c223060373d4116dead9cc53a265de60b9/pyyaml_ft-8.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd48d639cab5ca50ad957b6dd632c7dd3ac02a1abe0e8196a3c24a52f5db3f7a", size = 815755, upload-time = "2025-06-10T15:32:09.435Z" }, + { url = "https://files.pythonhosted.org/packages/05/10/f42c48fa5153204f42eaa945e8d1fd7c10d6296841dcb2447bf7da1be5c4/pyyaml_ft-8.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:052561b89d5b2a8e1289f326d060e794c21fa068aa11255fe71d65baf18a632e", size = 810403, upload-time = "2025-06-10T15:32:11.051Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d2/e369064aa51009eb9245399fd8ad2c562bd0bcd392a00be44b2a824ded7c/pyyaml_ft-8.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3bb4b927929b0cb162fb1605392a321e3333e48ce616cdcfa04a839271373255", size = 835581, upload-time = "2025-06-10T15:32:12.897Z" }, + { url = "https://files.pythonhosted.org/packages/c0/28/26534bed77109632a956977f60d8519049f545abc39215d086e33a61f1f2/pyyaml_ft-8.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:de04cfe9439565e32f178106c51dd6ca61afaa2907d143835d501d84703d3793", size = 171579, upload-time = "2025-06-10T15:32:14.34Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, +] + +[[package]] +name = "sphinx-autodoc-typehints" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/cd/03e7b917230dc057922130a79ba0240df1693bfd76727ea33fae84b39138/sphinx_autodoc_typehints-2.3.0.tar.gz", hash = "sha256:535c78ed2d6a1bad393ba9f3dfa2602cf424e2631ee207263e07874c38fde084", size = 40709, upload-time = "2024-08-29T16:25:48.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f3/e0a4ce49da4b6f4e4ce84b3c39a0677831884cb9d8a87ccbf1e9e56e53ac/sphinx_autodoc_typehints-2.3.0-py3-none-any.whl", hash = "sha256:3098e2c6d0ba99eacd013eb06861acc9b51c6e595be86ab05c08ee5506ac0c67", size = 19836, upload-time = "2024-08-29T16:25:46.707Z" }, +] + +[[package]] +name = "sphinx-multiversion" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/10/25231164a97a9016bdc73a3530af8f4a6846bdc564af1460af2ff3e59a50/sphinx-multiversion-0.2.4.tar.gz", hash = "sha256:5cd1ca9ecb5eed63cb8d6ce5e9c438ca13af4fa98e7eb6f376be541dd4990bcb", size = 7024, upload-time = "2020-08-12T15:48:20.566Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/51/203bb30b3ce76373237288e92cb71fb66f80ee380473f36bfe8a9d299c5d/sphinx_multiversion-0.2.4-py2.py3-none-any.whl", hash = "sha256:5c38d5ce785a335d8c8d768b46509bd66bfb9c6252b93b700ca8c05317f207d6", size = 9597, upload-time = "2024-10-03T21:45:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/05/ad/4989e6be165805694e93d09bc57425aa1368273b7de4fe3083fe4310803a/sphinx_multiversion-0.2.4-py3-none-any.whl", hash = "sha256:dec29f2a5890ad68157a790112edc0eb63140e70f9df0a363743c6258fbeb478", size = 9642, upload-time = "2020-08-12T15:48:19.649Z" }, +] + +[[package]] +name = "sphinxawesome-theme" +version = "5.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "sphinx", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/58/6f3e05f77c881e1356d73fc1d99561efe1795671bd56e7fb47c63a32cc1f/sphinxawesome_theme-5.3.2.tar.gz", hash = "sha256:0629d38b80aefc279b1186c53a7a6faf21e721b5b2ecda14f488ca1074e2631f", size = 364039, upload-time = "2024-11-08T11:51:03.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/e9/ca6a7178677bb171c8fd7f9eab726c00182168d38a79d2a58e7678590227/sphinxawesome_theme-5.3.2-py3-none-any.whl", hash = "sha256:5f2bb6b6ab8fa11db1ded5590106eb557351d6a6c36adea7ebd4e0f4bc96945d", size = 375173, upload-time = "2024-11-08T11:51:01.441Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "stdlibs" +version = "2025.10.28" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/83/ac15c4a3c059725dcb5f5d76270b986808cc12d2d7d417ee540d37609e46/stdlibs-2025.10.28.tar.gz", hash = "sha256:18db81f45f7783ddf86b80771e061782c70e2f4a8642843b3c80b42cd774b24f", size = 20108, upload-time = "2025-10-28T22:14:42.308Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/de/5fcc806280950b9535d3892c7f1f3477efc4c2f8624ae6c0b2c3baf9a339/stdlibs-2025.10.28-py3-none-any.whl", hash = "sha256:fc25a3608c417c7fecec06736a2671adaceafc9f20c3f536d967e894a998afea", size = 59232, upload-time = "2025-10-28T22:14:40.799Z" }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, +] + +[[package]] +name = "trailrunner" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pathspec" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/93/630e10bacd897daeb9ff5a408f4e7cb0fc2f243e7e3ef00f9e6cf319b11c/trailrunner-1.4.0.tar.gz", hash = "sha256:3fe61e259e6b2e5192f321c265985b7a0dc18497ced62b2da244f08104978398", size = 15836, upload-time = "2023-03-27T07:54:35.515Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/29/21001afea86bac5016c3940b43de3ce4786b0d8337d4ea79bb903c649ce3/trailrunner-1.4.0-py3-none-any.whl", hash = "sha256:a286d39f2723f28d167347f41cf8f232832648709366e722f55cf5545772a48e", size = 11071, upload-time = "2023-03-27T07:54:32.514Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +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 = "ufmt" +version = "2.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "black" }, + { name = "click" }, + { name = "libcst" }, + { name = "moreorless" }, + { name = "tomlkit" }, + { name = "trailrunner" }, + { name = "typing-extensions" }, + { name = "usort" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/24/8393cb5e96e640a858ac11c2ac65b28221b09c4a11f83210d2ab9d6108e5/ufmt-2.7.3.tar.gz", hash = "sha256:b661c575bfb95a617d04fa4fe5cde550d21eb848094b3f9c438165459bc04f77", size = 24425, upload-time = "2024-09-21T23:03:42.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/35/df52ce427d847396a3574887fef32f11f169834523493010b32b5600867c/ufmt-2.7.3-py3-none-any.whl", hash = "sha256:1a3d2515e3108ba7a68425ebdeb4cbb1fdfbcffbd031dac8f56a1f386a7b396e", size = 28186, upload-time = "2024-09-21T23:03:41.214Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "usort" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "click" }, + { name = "libcst" }, + { name = "moreorless" }, + { name = "stdlibs" }, + { name = "toml" }, + { name = "trailrunner" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/69/8bcbed123ec60ee3e529ec39ebb0bd405cc17bcf101094513ad05c326f6b/usort-1.1.0.tar.gz", hash = "sha256:a24d3a19c80d006b82d47319ec42cca740e4a1efceba35465e1fd49393a28966", size = 82981, upload-time = "2025-11-04T16:13:20.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/09/dc06e65fef86845b8471130e862825901b89c08652b4d38862f8d7446e6c/usort-1.1.0-py3-none-any.whl", hash = "sha256:5ae95f0be86d5a8a6fb7c9821e06f33ef4c235d193543926dc4bc0c276879dfe", size = 40751, upload-time = "2025-11-04T16:13:18.556Z" }, +] From 6eb306965954732965b4fd0f57e267801e1f67e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 9 Nov 2025 21:04:58 +0100 Subject: [PATCH 136/161] Add support for env variable JUDGE0_SUPPRESS_PREVIEW_WARNING. (#19) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add support for env variable JUDGE0_SUPPRESS_PREVIEW_WARNING. * Minor change to preview warning suppression logic --------- Co-authored-by: Filip Karlo Došilović --- src/judge0/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index b2a4dcb4..b74df46e 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -65,6 +65,7 @@ JUDGE0_IMPLICIT_CE_CLIENT = None JUDGE0_IMPLICIT_EXTRA_CE_CLIENT = None +SUPPRESS_PREVIEW_WARNING = os.getenv("JUDGE0_SUPPRESS_PREVIEW_WARNING") logger = logging.getLogger(__name__) @@ -106,11 +107,12 @@ def _get_implicit_client(flavor: Flavor) -> Client: def _get_preview_client(flavor: Flavor) -> Union[Judge0CloudCE, Judge0CloudExtraCE]: - logger.warning( - "You are using a preview version of the client which is not recommended" - " for production.\n" - "For production, please specify your API key in the environment variable." - ) + if SUPPRESS_PREVIEW_WARNING is not None: + logger.warning( + "You are using a preview version of the client which is not recommended" + " for production.\n" + "For production, please specify your API key in the environment variable." + ) if flavor == Flavor.CE: return Judge0CloudCE() From 29ce7f7f6b3e8fa753b092b6967f5d540e6dadfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 9 Nov 2025 21:11:01 +0100 Subject: [PATCH 137/161] Update Python language aliases to latest IDs. (#17) --- src/judge0/data.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/judge0/data.py b/src/judge0/data.py index 5182e7a5..70f04228 100644 --- a/src/judge0/data.py +++ b/src/judge0/data.py @@ -120,9 +120,9 @@ LanguageAlias.PHP: 98, LanguageAlias.PLAIN_TEXT: 43, LanguageAlias.PROLOG: 69, - LanguageAlias.PYTHON: 109, + LanguageAlias.PYTHON: 113, LanguageAlias.PYTHON2: 70, - LanguageAlias.PYTHON3: 109, + LanguageAlias.PYTHON3: 113, LanguageAlias.R: 99, LanguageAlias.RUBY: 72, LanguageAlias.RUST: 108, @@ -155,12 +155,12 @@ LanguageAlias.MPI_PYTHON: 8, LanguageAlias.MULTI_FILE: 89, LanguageAlias.NIM: 9, - LanguageAlias.PYTHON: 32, + LanguageAlias.PYTHON: 33, LanguageAlias.PYTHON2: 26, LanguageAlias.PYTHON2_PYPY: 26, - LanguageAlias.PYTHON3: 32, + LanguageAlias.PYTHON3: 33, LanguageAlias.PYTHON3_PYPY: 28, - LanguageAlias.PYTHON_FOR_ML: 32, + LanguageAlias.PYTHON_FOR_ML: 33, LanguageAlias.VISUAL_BASIC: 20, }, } From 9dc1c8561dd2101a873e90cb93f8ebf9587f104f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 9 Nov 2025 21:13:38 +0100 Subject: [PATCH 138/161] Add Filesystem.find method (#18) --- README.md | 5 +---- src/judge0/filesystem.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 12a47643..08de5602 100644 --- a/README.md +++ b/README.md @@ -347,8 +347,5 @@ submission = Submission( result = judge0.run(submissions=submission) print(result.stdout) - -matches = [f for f in result.post_execution_filesystem if f.name == "my_dir2/my_file2.txt"] -f = matches[0] if matches else None -print(f) +print(result.post_execution_filesystem.find("./my_dir2/my_file2.txt")) ``` diff --git a/src/judge0/filesystem.py b/src/judge0/filesystem.py index 27fae223..b753dfc4 100644 --- a/src/judge0/filesystem.py +++ b/src/judge0/filesystem.py @@ -92,6 +92,27 @@ def encode(self) -> bytes: zip_file.writestr(file.name, file.content) return zip_buffer.getvalue() + def find(self, name: str) -> Optional[File]: + """Find file by name in Filesystem object. + + Parameters + ---------- + name : str + File name to find. + + Returns + ------- + File or None + Found File object or None if not found. + """ + if name.startswith("./"): + name = name[2:] + elif name.startswith("/"): + name = name[1:] + + matches = [f for f in self.files if f.name == name] + return matches[0] if matches else None + def __str__(self) -> str: """Create string representation of Filesystem object.""" return b64encode(self.encode()).decode() From 7265c92ea2a207b6fb684bf84acaf42ff725a16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 9 Nov 2025 21:25:40 +0100 Subject: [PATCH 139/161] Update version to 0.0.6 alpha release. (#21) --- pyproject.toml | 2 +- src/judge0/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8595aac9..9f9c65dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.1.0.dev0" +version = "0.0.6-alpha.1" description = "The official Python SDK for Judge0." readme = "README.md" requires-python = ">=3.10" diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index b74df46e..f4679740 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -30,7 +30,7 @@ from .retry import MaxRetries, MaxWaitTime, RegularPeriodRetry from .submission import Submission -__version__ = "0.1.0.dev0" +__version__ = "0.0.6-alpha.1" __all__ = [ "ATD", From 40ca59599cca54f60a646e45ec667532c200d9bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 9 Nov 2025 21:37:43 +0100 Subject: [PATCH 140/161] Fix publish CI job. Update alpha version. --- .github/workflows/publish.yml | 2 +- pyproject.toml | 2 +- src/judge0/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ab561792..a07edf53 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,7 +30,7 @@ jobs: if: github.event.inputs.repository == 'test' env: UV_PUBLISH_TOKEN: ${{ secrets.TEST_PYPI_TOKEN }} - run: uv publish --token $UV_PUBLISH_TOKEN --repository testpypi + run: uv publish --token $UV_PUBLISH_TOKEN --index-url https://test.pypi.org/legacy/ - name: Publish to Regular PyPI if: github.event.inputs.repository == 'pypi' diff --git a/pyproject.toml b/pyproject.toml index 9f9c65dd..a7be43f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.0.6-alpha.1" +version = "0.0.6-alpha.2" description = "The official Python SDK for Judge0." readme = "README.md" requires-python = ">=3.10" diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index f4679740..de66d975 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -30,7 +30,7 @@ from .retry import MaxRetries, MaxWaitTime, RegularPeriodRetry from .submission import Submission -__version__ = "0.0.6-alpha.1" +__version__ = "0.0.6-alpha.2" __all__ = [ "ATD", From 771c04f22f9519c1e09bcaa5563b8d2607d59a1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 9 Nov 2025 21:43:21 +0100 Subject: [PATCH 141/161] Fix publish CI job to use publish url. Update alpha version. --- .github/workflows/publish.yml | 2 +- pyproject.toml | 2 +- src/judge0/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a07edf53..416c9c8b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,7 +30,7 @@ jobs: if: github.event.inputs.repository == 'test' env: UV_PUBLISH_TOKEN: ${{ secrets.TEST_PYPI_TOKEN }} - run: uv publish --token $UV_PUBLISH_TOKEN --index-url https://test.pypi.org/legacy/ + run: uv publish --token $UV_PUBLISH_TOKEN --publish-url https://test.pypi.org/legacy/ - name: Publish to Regular PyPI if: github.event.inputs.repository == 'pypi' diff --git a/pyproject.toml b/pyproject.toml index a7be43f2..36f56166 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.0.6-alpha.2" +version = "0.0.6-alpha.3" description = "The official Python SDK for Judge0." readme = "README.md" requires-python = ">=3.10" diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index de66d975..0eb7d7f0 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -30,7 +30,7 @@ from .retry import MaxRetries, MaxWaitTime, RegularPeriodRetry from .submission import Submission -__version__ = "0.0.6-alpha.2" +__version__ = "0.0.6-alpha.3" __all__ = [ "ATD", From 8864ca3202f9f2c30b1d789fe25d08a9dccaa856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 9 Nov 2025 21:50:13 +0100 Subject: [PATCH 142/161] Release v0.0.6 --- pyproject.toml | 2 +- src/judge0/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 36f56166..58a84aaa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.0.6-alpha.3" +version = "0.0.6" description = "The official Python SDK for Judge0." readme = "README.md" requires-python = ">=3.10" diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index 0eb7d7f0..ae62b15f 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -30,7 +30,7 @@ from .retry import MaxRetries, MaxWaitTime, RegularPeriodRetry from .submission import Submission -__version__ = "0.0.6-alpha.3" +__version__ = "0.0.6" __all__ = [ "ATD", From 3e4d088a966ab968cb6f15f85db91df735ffdfc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sun, 9 Nov 2025 21:59:51 +0100 Subject: [PATCH 143/161] Update to new minor release. --- pyproject.toml | 2 +- src/judge0/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 58a84aaa..8595aac9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.0.6" +version = "0.1.0.dev0" description = "The official Python SDK for Judge0." readme = "README.md" requires-python = ">=3.10" diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index ae62b15f..b74df46e 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -30,7 +30,7 @@ from .retry import MaxRetries, MaxWaitTime, RegularPeriodRetry from .submission import Submission -__version__ = "0.0.6" +__version__ = "0.1.0.dev0" __all__ = [ "ATD", From fa3c86f074577cbb60757b76d1e5f34a74257c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 10 Nov 2025 00:45:26 +0100 Subject: [PATCH 144/161] Add Custom Judge0 Client example --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 08de5602..aee1b648 100644 --- a/README.md +++ b/README.md @@ -349,3 +349,25 @@ result = judge0.run(submissions=submission) print(result.stdout) print(result.post_execution_filesystem.find("./my_dir2/my_file2.txt")) ``` + +### Custom Judge0 Client + +This example shows how to use Judge0 Python SDK with your own Judge0 instance. + +```python +import judge0 + +client = judge0.Client(endpoint="http://127.0.0.1:2358", auth_headers=None) + +source_code = """ +#include + +int main() { + printf("hello, world\\n"); + return 0; +} +""" + +result = judge0.run(client=client, source_code=source_code, language=judge0.C) +print(result.stdout) +``` From 3c8efcc23836f800d6f46262d61a714c9ebb9c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 10 Nov 2025 00:58:11 +0100 Subject: [PATCH 145/161] Simplify custom client example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aee1b648..db853f01 100644 --- a/README.md +++ b/README.md @@ -357,7 +357,7 @@ This example shows how to use Judge0 Python SDK with your own Judge0 instance. ```python import judge0 -client = judge0.Client(endpoint="http://127.0.0.1:2358", auth_headers=None) +client = judge0.Client("http://127.0.0.1:2358", None) source_code = """ #include From 0955d857ac2ec5715cfc886cf3b0c61ca2c7b87b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 10 Nov 2025 01:00:34 +0100 Subject: [PATCH 146/161] Don't require auth_headers in judge0.Client --- README.md | 2 +- src/judge0/clients.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index db853f01..c2dc8ae7 100644 --- a/README.md +++ b/README.md @@ -357,7 +357,7 @@ This example shows how to use Judge0 Python SDK with your own Judge0 instance. ```python import judge0 -client = judge0.Client("http://127.0.0.1:2358", None) +client = judge0.Client("http://127.0.0.1:2358") source_code = """ #include diff --git a/src/judge0/clients.py b/src/judge0/clients.py index 49d1ac61..7b50047c 100644 --- a/src/judge0/clients.py +++ b/src/judge0/clients.py @@ -33,7 +33,7 @@ class Client: def __init__( self, endpoint, - auth_headers, + auth_headers=None, *, retry_strategy: Optional[RetryStrategy] = None, ) -> None: From fd2ab3c80e7c6bd48d952ae63c14a817d68b8ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 10 Nov 2025 21:27:04 +0100 Subject: [PATCH 147/161] Fix bug in suppressing preview warning message. --- src/judge0/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index b74df46e..069c96ca 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -65,9 +65,9 @@ JUDGE0_IMPLICIT_CE_CLIENT = None JUDGE0_IMPLICIT_EXTRA_CE_CLIENT = None -SUPPRESS_PREVIEW_WARNING = os.getenv("JUDGE0_SUPPRESS_PREVIEW_WARNING") logger = logging.getLogger(__name__) +suppress_preview_warning = os.getenv("JUDGE0_SUPPRESS_PREVIEW_WARNING") is not None def _get_implicit_client(flavor: Flavor) -> Client: @@ -107,7 +107,7 @@ def _get_implicit_client(flavor: Flavor) -> Client: def _get_preview_client(flavor: Flavor) -> Union[Judge0CloudCE, Judge0CloudExtraCE]: - if SUPPRESS_PREVIEW_WARNING is not None: + if not suppress_preview_warning: logger.warning( "You are using a preview version of the client which is not recommended" " for production.\n" From 61324c3d602ebaed1b8f95b3fc9dc9dfc1196aa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 10 Nov 2025 21:30:15 +0100 Subject: [PATCH 148/161] Bump to version 0.0.7 --- pyproject.toml | 2 +- src/judge0/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8595aac9..e0f27ab2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.1.0.dev0" +version = "0.0.7" description = "The official Python SDK for Judge0." readme = "README.md" requires-python = ">=3.10" diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index 069c96ca..28a89f41 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -30,7 +30,7 @@ from .retry import MaxRetries, MaxWaitTime, RegularPeriodRetry from .submission import Submission -__version__ = "0.1.0.dev0" +__version__ = "0.0.7" __all__ = [ "ATD", From 78ef628afa416f9acfba6bbd5dca23e1d4037c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 10 Nov 2025 21:37:45 +0100 Subject: [PATCH 149/161] Prepare for next release --- pyproject.toml | 2 +- src/judge0/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e0f27ab2..8595aac9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "judge0" -version = "0.0.7" +version = "0.1.0.dev0" description = "The official Python SDK for Judge0." readme = "README.md" requires-python = ">=3.10" diff --git a/src/judge0/__init__.py b/src/judge0/__init__.py index 28a89f41..069c96ca 100644 --- a/src/judge0/__init__.py +++ b/src/judge0/__init__.py @@ -30,7 +30,7 @@ from .retry import MaxRetries, MaxWaitTime, RegularPeriodRetry from .submission import Submission -__version__ = "0.0.7" +__version__ = "0.1.0.dev0" __all__ = [ "ATD", From fcef3785ee00bd5ebc4f436d1996065a88a0cf5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 10 Nov 2025 21:57:20 +0100 Subject: [PATCH 150/161] Update simple example with Ollama --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c2dc8ae7..6976a0b2 100644 --- a/README.md +++ b/README.md @@ -246,8 +246,12 @@ client = Client( headers={"Authorization": "Bearer " + os.environ.get("OLLAMA_API_KEY")}, ) -system = "You are a helpful assistant that can execute Python code. Only respond with the code to be executed and nothing else. Strip backticks in code blocks." -prompt = "Calculate how many r's are in the word 'strawberry'." +system = """ +You are a helpful assistant that can execute code written in the C programming language. +Only respond with the code written in the C programming language that needs to be executed and nothing else. +Strip the backticks in code blocks. +""" +prompt = "How many r's are in the word 'strawberry'." response = client.chat( model="gpt-oss:120b-cloud", @@ -258,10 +262,10 @@ response = client.chat( ) code = response["message"]["content"] -print(f"Generated code:\n{code}") +print(f"CODE GENERATED BY THE MODEL:\n{code}\n") -result = judge0.run(source_code=code, language=judge0.PYTHON) -print(f"Execution result:\n{result.stdout}") +result = judge0.run(source_code=code, language=judge0.C) +print(f"CODE EXECUTION RESULT:\n{result.stdout}") ``` #### Tool Calling (a.k.a. Function Calling) with Ollama From bc2c693299081811858fe81dc07fe0e0637c881a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 10 Nov 2025 22:58:16 +0100 Subject: [PATCH 151/161] Update Ollama examples --- README.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6976a0b2..d88e865b 100644 --- a/README.md +++ b/README.md @@ -251,7 +251,7 @@ You are a helpful assistant that can execute code written in the C programming l Only respond with the code written in the C programming language that needs to be executed and nothing else. Strip the backticks in code blocks. """ -prompt = "How many r's are in the word 'strawberry'." +prompt = "How many r's are in the word 'strawberry'?" response = client.chat( model="gpt-oss:120b-cloud", @@ -284,20 +284,20 @@ client = Client( model="qwen3-coder:480b-cloud" messages=[ - {"role": "user", "content": "Calculate how many r's are in the word 'strawberry'."}, + {"role": "user", "content": "How many r's are in the word 'strawberry'?"}, ] tools = [{ "type": "function", "function": { - "name": "execute_python", - "description": "Execute Python code and return result", + "name": "execute_c", + "description": "Execute the C programming language code.", "parameters": { "type": "object", "properties": { "code": { "type": "string", - "description": "The Python code to execute" + "description": "The code written in the C programming language." } }, "required": ["code"] @@ -312,16 +312,21 @@ messages.append(response_message) if response_message.tool_calls: for tool_call in response_message.tool_calls: - if tool_call.function.name == "execute_python": - result = judge0.run(source_code=tool_call.function.arguments["code"], language=judge0.PYTHON) + if tool_call.function.name == "execute_c": + code = tool_call.function.arguments["code"] + print(f"CODE GENERATED BY THE MODEL:\n{code}\n") + + result = judge0.run(source_code=code, language=judge0.C) + print(f"CODE EXECUTION RESULT:\n{result.stdout}\n") + messages.append({ "role": "tool", - "tool_name": "execute_python", + "tool_name": "execute_c", "content": result.stdout, }) final_response = client.chat(model=model, messages=messages) -print(final_response["message"]["content"]) +print(f'FINAL RESPONSE BY THE MODEL:\n{final_response["message"]["content"]}') ``` ### Filesystem From 319a2a883aa63cf6dc99d1d98c9517c2560c0034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 10 Nov 2025 23:16:54 +0100 Subject: [PATCH 152/161] Add comments for filesystem example --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index d88e865b..3f4f937f 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,10 @@ print(f'FINAL RESPONSE BY THE MODEL:\n{final_response["message"]["content"]}') ### Filesystem +This example shows how to use Judge0 Python SDK to: +1. Create a submission with additional files in the filesystem which will be available during the execution. +2. Read the files after the execution which were created during the execution. + ```python import judge0 from judge0 import Filesystem, File, Submission @@ -355,6 +359,7 @@ submission = Submission( ) result = judge0.run(submissions=submission) + print(result.stdout) print(result.post_execution_filesystem.find("./my_dir2/my_file2.txt")) ``` From 1a1fd19d3c36c270488bbae31d5cd569b28065a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 16 Nov 2025 22:41:00 +0100 Subject: [PATCH 153/161] Add Multi-Agent System For Iterative Code Generation, Execution, And Debugging --- README.md | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/README.md b/README.md index 3f4f937f..9cccc218 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,7 @@ print(client.get_languages()) #### Simple Example with Ollama ```python +# pip install judge0 ollama import os from ollama import Client import judge0 @@ -271,6 +272,7 @@ print(f"CODE EXECUTION RESULT:\n{result.stdout}") #### Tool Calling (a.k.a. Function Calling) with Ollama ```python +# pip install judge0 ollama import os from ollama import Client import judge0 @@ -329,6 +331,87 @@ final_response = client.chat(model=model, messages=messages) print(f'FINAL RESPONSE BY THE MODEL:\n{final_response["message"]["content"]}') ``` +#### Multi-Agent System For Iterative Code Generation, Execution, And Debugging + +```python +# pip install judge0 ag2[openai] +from typing import Annotated, Optional + +from autogen import ConversableAgent, LLMConfig, register_function +from autogen.tools import Tool +from pydantic import BaseModel, Field +import judge0 + + +class PythonCodeExecutionTool(Tool): + def __init__(self) -> None: + class CodeExecutionRequest(BaseModel): + code: Annotated[str, Field(description="Python code to execute")] + + async def execute_python_code( + code_execution_request: CodeExecutionRequest, + ) -> Optional[str]: + result = judge0.run( + source_code=code_execution_request.code, + language=judge0.PYTHON, + redirect_stderr_to_stdout=True, + ) + return result.stdout + + super().__init__( + name="python_execute_code", + description="Executes Python code and returns the result.", + func_or_tool=execute_python_code, + ) + + +python_executor = PythonCodeExecutionTool() + +llm_config = LLMConfig( + { + "api_type": "openai", + "base_url": "http://localhost:11434/v1", + "api_key": "ollama", + "model": "qwen3-coder:30b", + } +) + +code_runner = ConversableAgent( + name="code_runner", + system_message="You are a code executor agent, when you don't execute code write the message 'TERMINATE' by itself.", + human_input_mode="NEVER", + llm_config=llm_config, +) + +question_agent = ConversableAgent( + name="question_agent", + system_message=( + "You are a developer AI agent. " + "Send all your code suggestions to the python_executor tool where it will be executed and result returned to you. " + "Keep refining the code until it works." + ), + llm_config=llm_config, +) + +register_function( + python_executor, + caller=question_agent, + executor=code_runner, + description="Run Python code", +) + +result = code_runner.initiate_chat( + recipient=question_agent, + message=( + "Write Python code to print the current Python version followed by the numbers 1 to 11. " + "Make a syntax error in the first version and fix it in the second version." + ), + max_turns=5, +) + +print(f"Result: {result.summary}") +``` + ### Filesystem This example shows how to use Judge0 Python SDK to: From a0f21c4737836e58aa0c49092a9b0b986939b1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Sun, 16 Nov 2025 22:49:40 +0100 Subject: [PATCH 154/161] Update example to work with Ollama Cloud --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9cccc218..27d674dc 100644 --- a/README.md +++ b/README.md @@ -238,10 +238,11 @@ print(client.get_languages()) ```python # pip install judge0 ollama import os + from ollama import Client import judge0 -# Get your Ollama Cloud API key from https://ollama.com. +# Get your free tier Ollama Cloud API key at https://ollama.com. client = Client( host="https://ollama.com", headers={"Authorization": "Bearer " + os.environ.get("OLLAMA_API_KEY")}, @@ -274,10 +275,11 @@ print(f"CODE EXECUTION RESULT:\n{result.stdout}") ```python # pip install judge0 ollama import os + from ollama import Client import judge0 -# Get your Ollama Cloud API key from https://ollama.com. +# Get your free tier Ollama Cloud API key at https://ollama.com. client = Client( host="https://ollama.com", headers={"Authorization": "Bearer " + os.environ.get("OLLAMA_API_KEY")}, @@ -335,6 +337,7 @@ print(f'FINAL RESPONSE BY THE MODEL:\n{final_response["message"]["content"]}') ```python # pip install judge0 ag2[openai] +import os from typing import Annotated, Optional from autogen import ConversableAgent, LLMConfig, register_function @@ -367,12 +370,13 @@ class PythonCodeExecutionTool(Tool): python_executor = PythonCodeExecutionTool() +# Get your free tier Ollama Cloud API key at https://ollama.com. llm_config = LLMConfig( { "api_type": "openai", - "base_url": "http://localhost:11434/v1", - "api_key": "ollama", - "model": "qwen3-coder:30b", + "base_url": "https://ollama.com/v1", + "api_key": os.environ.get("OLLAMA_API_KEY"), + "model": "qwen3-coder:480b-cloud", } ) From 177ff092a5aebe20f536c61bb7aefa6e33e94e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 17 Nov 2025 00:14:53 +0100 Subject: [PATCH 155/161] Add Kaggle Dataset Visualization With LLM-Generated Code Using Ollama And Judge0 --- README.md | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 27d674dc..7d15228e 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,7 @@ print(client.get_languages()) ### Running LLM-Generated Code -#### Simple Example with Ollama +#### Simple Example With Ollama ```python # pip install judge0 ollama @@ -270,7 +270,7 @@ result = judge0.run(source_code=code, language=judge0.C) print(f"CODE EXECUTION RESULT:\n{result.stdout}") ``` -#### Tool Calling (a.k.a. Function Calling) with Ollama +#### Tool Calling (a.k.a. Function Calling) With Ollama ```python # pip install judge0 ollama @@ -416,6 +416,108 @@ result = code_runner.initiate_chat( print(f"Result: {result.summary}") ``` +#### Kaggle Dataset Visualization With LLM-Generated Code Using Ollama And Judge0 + +```python +# pip install judge0 ollama requests +import os +import zipfile + +import judge0 +import requests +from judge0 import File, Filesystem +from ollama import Client + +# Step 1: Download the dataset from Kaggle. +dataset_url = "https://www.kaggle.com/api/v1/datasets/download/gregorut/videogamesales" +dataset_zip_path = "vgsales.zip" +dataset_csv_path = "vgsales.csv" # P.S.: We know the CSV file name inside the zip. + +if not os.path.exists(dataset_csv_path): # Download only if not already downloaded. + with requests.get(dataset_url) as response: + with open(dataset_zip_path, "wb") as f: + f.write(response.content) + with zipfile.ZipFile(dataset_zip_path, "r") as f: + f.extractall(".") + +# Step 2: Prepare the submission for Judge0. +with open(dataset_csv_path, "r") as f: + submission = judge0.Submission( + language=judge0.PYTHON_FOR_ML, + additional_files=Filesystem( + content=[ + File(name=dataset_csv_path, content=f.read()), + ] + ), + ) + +# Step 3: Initialize Ollama Client. Get your free tier Ollama Cloud API key at https://ollama.com. +client = Client( + host="https://ollama.com", + headers={"Authorization": "Bearer " + os.environ.get("OLLAMA_API_KEY")}, +) + +# Step 4: Prepare the prompt, messages, tools, and choose the model. +prompt = f""" +I have a CSV that contains a list of video games with sales greater than 100,000 copies. It's saved in the file {dataset_csv_path}. +These are the columns: +- 'Rank': Ranking of overall sales +- 'Name': The games name +- 'Platform': Platform of the games release (i.e. PC,PS4, etc.) +- 'Year': Year of the game's release +- 'Genre': Genre of the game +- 'Publisher': Publisher of the game +- 'NA_Sales': Sales in North America (in millions) +- 'EU_Sales': Sales in Europe (in millions) +- 'JP_Sales': Sales in Japan (in millions) +- 'Other_Sales': Sales in the rest of the world (in millions) +- 'Global_Sales': Total worldwide sales. + +I want to better understand how the sales are distributed across different genres over the years. +Write Python code that analyzes the dataset based on my request, produces right chart and saves it as an image file. +""" +messages = [{"role": "user", "content": prompt}] +tools = [ + { + "type": "function", + "function": { + "name": "execute_python", + "description": "Execute the Python programming language code.", + "parameters": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The code written in the Python programming language.", + } + }, + "required": ["code"], + }, + }, + } +] +model = "qwen3-coder:480b-cloud" + +# Step 5: Start the interaction with the model. +response = client.chat(model=model, messages=messages, tools=tools) +response_message = response["message"] + +if response_message.tool_calls: + for tool_call in response_message.tool_calls: + if tool_call.function.name == "execute_python": + code = tool_call.function.arguments["code"] + print(f"CODE GENERATED BY THE MODEL:\n{code}\n") + + submission.source_code = code + result = judge0.run(submissions=submission) + + for f in result.post_execution_filesystem: + if f.name.endswith((".png", ".jpg", ".jpeg")): + with open(f.name, "wb") as img_file: + img_file.write(f.content) + print(f"Generated image saved as: {f.name}\n") +``` + ### Filesystem This example shows how to use Judge0 Python SDK to: From c4016fb07c1555eb623f8b42f019e9bc38ea8e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 17 Nov 2025 01:57:38 +0100 Subject: [PATCH 156/161] Add Minimal Example Using smolagents With Ollama And Judge0 --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/README.md b/README.md index 7d15228e..1f811139 100644 --- a/README.md +++ b/README.md @@ -518,6 +518,49 @@ if response_message.tool_calls: print(f"Generated image saved as: {f.name}\n") ``` +#### Minimal Example Using `smolagents` With Ollama And Judge0 + +```python +# pip install judge0 smolagents[openai] +import os +from typing import Any + +import judge0 +from smolagents import CodeAgent, OpenAIServerModel, Tool +from smolagents.local_python_executor import CodeOutput, PythonExecutor + + +class Judge0PythonExecutor(PythonExecutor): + def send_tools(self, tools: dict[str, Tool]) -> None: + pass + + def send_variables(self, variables: dict[str, Any]) -> None: + pass + + def __call__(self, code_action: str) -> CodeOutput: + source_code = f"final_answer = lambda x : print(x)\n{code_action}" + result = judge0.run(source_code=source_code, language=judge0.PYTHON_FOR_ML) + return CodeOutput( + output=result.stdout, + logs=result.stderr or "", + is_final_answer=result.exit_code == 0, + ) + + +# Get your free tier Ollama Cloud API key at https://ollama.com. +model = OpenAIServerModel( + model_id="gpt-oss:120b-cloud", + api_base="https://ollama.com/v1", + api_key=os.environ["OLLAMA_API_KEY"], +) + +agent = CodeAgent(tools=[], model=model) +agent.python_executor = Judge0PythonExecutor() + +result = agent.run("How many r's are in the word 'strawberry'?") +print(result) +``` + ### Filesystem This example shows how to use Judge0 Python SDK to: From 0eea3ac7a1264cf47d777d6e6459ab33492fc2c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herman=20Zvonimir=20Do=C5=A1ilovi=C4=87?= Date: Mon, 17 Nov 2025 02:07:26 +0100 Subject: [PATCH 157/161] Add example Generating And Saving An Image File --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index 1f811139..71f241d5 100644 --- a/README.md +++ b/README.md @@ -568,6 +568,7 @@ This example shows how to use Judge0 Python SDK to: 2. Read the files after the execution which were created during the execution. ```python +# pip install judge0 import judge0 from judge0 import Filesystem, File, Submission @@ -601,6 +602,7 @@ print(result.post_execution_filesystem.find("./my_dir2/my_file2.txt")) This example shows how to use Judge0 Python SDK with your own Judge0 instance. ```python +# pip install judge0 import judge0 client = judge0.Client("http://127.0.0.1:2358") @@ -617,3 +619,24 @@ int main() { result = judge0.run(client=client, source_code=source_code, language=judge0.C) print(result.stdout) ``` + +### Generating And Saving An Image File + +```python +# pip install judge0 +import judge0 + +source_code = """ +import matplotlib.pyplot as plt + +plt.plot([x for x in range(10)], [x**2 for x in range(10)]) +plt.savefig("chart.png") +""" + +result = judge0.run(source_code=source_code, language=judge0.PYTHON_FOR_ML) + +image = result.post_execution_filesystem.find("chart.png") +with open(image.name, "wb") as f: + f.write(image.content) +print(f"Generated image saved as: {image.name}\n") +``` From 0847529f78f72ed77a6e39866f4bc638e2ef5198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 28 Nov 2025 18:25:01 +0100 Subject: [PATCH 158/161] Filter only release tag versions (without prereleases). (#28) --- .github/workflows/docs.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 182c3f95..de80d4e6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,8 +17,12 @@ jobs: fetch-depth: 0 # Fetch the full history ref: ${{ github.ref }} # Check out the current branch or tag - - name: Fetch tags only - run: git fetch --tags --no-recurse-submodules + - name: Fetch version tags only (vx.y.z) + run: | + TAGS=$(git ls-remote --tags origin 'v*[0-9].[0-9].[0-9]' | sed 's/.*refs\/tags\///' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$') + for tag in $TAGS; do + git fetch origin "refs/tags/$tag:refs/tags/$tag" --no-recurse-submodules + done - name: Set up Python uses: actions/setup-python@v4 @@ -33,12 +37,12 @@ jobs: - name: Build documentation run: uv run sphinx-multiversion docs/source docs/build/html --keep-going --no-color - - name: Get the latest tag + - name: Get the latest stable tag run: | # Fetch all tags git fetch --tags - # Get the latest tag - latest_tag=$(git tag --sort=-creatordate | head -n 1) + # Get the latest stable tag + latest_tag=$(git tag --sort=-creatordate | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1) echo "LATEST_RELEASE=$latest_tag" >> $GITHUB_ENV - name: Generate index.html for judge0.github.io/judge0-python. From 38b13f961cbf3389d20a17aa21c336430b7f625c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Fri, 28 Nov 2025 18:43:32 +0100 Subject: [PATCH 159/161] Add tag filtering for sphinx-multiversion config as well. --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index f4c02c87..4ab5eccb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -102,7 +102,7 @@ napoleon_google_docstring = False # Whitelist pattern for tags (set to None to ignore all tags) -smv_tag_whitelist = r"^.*$" +smv_tag_whitelist = r"^v[0-9]+\.[0-9]+\.[0-9]+$" # Whitelist pattern for branches (set to None to ignore all branches) smv_branch_whitelist = r"^master$" # Whitelist pattern for remotes (set to None to use local branches only) From 48df1b960a36c4c1f07908fcbae20f4d0eb17bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Mon, 1 Dec 2025 11:22:17 +0100 Subject: [PATCH 160/161] Add docs for client resolution. (#30) --- docs/source/in_depth/client_resolution.rst | 53 +++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/docs/source/in_depth/client_resolution.rst b/docs/source/in_depth/client_resolution.rst index dfdd5321..808a705b 100644 --- a/docs/source/in_depth/client_resolution.rst +++ b/docs/source/in_depth/client_resolution.rst @@ -1,4 +1,55 @@ Client Resolution ================= -TODO: Describe the approach to client resolution. See `_get_implicit_client`. \ No newline at end of file +The Judge0 Python SDK supports two main client flavors: Community Edition (CE) and Extra Community Edition (Extra CE), which correspond to the Judge0 Cloud editions. When you use functions like ``judge0.run`` or ``judge0.execute`` without explicitly providing a client instance, the SDK automatically resolves and initializes a client for you. This process, handled by the internal ``_get_implicit_client`` function, follows a specific order of priority. + +The SDK determines which client to use based on environment variables. Here's the resolution order: + +1. **Custom Client (Self-Hosted)** + +The SDK first checks for a self-hosted Judge0 instance. If you have your own deployment of Judge0, you can configure the SDK to use it by setting the following environment variables: + +* For CE flavor: + * ``JUDGE0_CE_ENDPOINT``: The URL of your self-hosted Judge0 CE instance. + * ``JUDGE0_CE_AUTH_HEADERS``: A JSON string representing the authentication headers. +* For Extra CE flavor: + * ``JUDGE0_EXTRA_CE_ENDPOINT``: The URL of your self-hosted Judge0 Extra CE instance. + * ``JUDGE0_EXTRA_CE_AUTH_HEADERS``: A JSON string representing the authentication headers. + +If these variables are set, the SDK will initialize a ``Client`` instance with your custom endpoint and headers. + +2. **Hub Clients** + +If a custom client is not configured, the SDK will try to find API keys for one of the supported hub clients. The SDK checks for the following environment variables in order: + +* **Judge0 Cloud**: + * ``JUDGE0_CLOUD_CE_AUTH_HEADERS`` for ``Judge0CloudCE`` + * ``JUDGE0_CLOUD_EXTRA_CE_AUTH_HEADERS` for ``Judge0CloudExtraCE`` + +* **RapidAPI**: + * ``JUDGE0_RAPID_API_KEY`` for both ``RapidJudge0CE`` and ``RapidJudge0ExtraCE`` + +* **AllThingsDev**: + * ``JUDGE0_ATD_API_KEY`` for both ``ATDJudge0CE`` and ``ATDJudge0ExtraCE`` + +The first API key found determines the client that will be used. + +3. **Preview Client** + +If none of the above environment variables are set, the SDK falls back to using a **preview client**. This is an unauthenticated client that connects to the official Judge0 Cloud service. It initializes ``Judge0CloudCE()`` and ``Judge0CloudExtraCE()`` for the CE and Extra CE flavors, respectively. + +When the preview client is used, a warning message is logged to the console, as this option is not recommended for production use. To suppress this warning, you can set the ``JUDGE0_SUPPRESS_PREVIEW_WARNING`` environment variable. + +Example Resolution Flow +----------------------- + +When you call a function like ``judge0.run(..., flavor=judge0.CE)``, the SDK will: + +1. Check if ``JUDGE0_IMPLICIT_CE_CLIENT`` is already initialized. If so, use it. +2. Check for ``JUDGE0_CE_ENDPOINT`` and ``JUDGE0_CE_AUTH_HEADERS`` to configure a ``Client``. +3. Check for ``JUDGE0_CLOUD_CE_AUTH_HEADERS`` to configure a ``Judge0CloudCE`` client. +4. Check for ``JUDGE0_RAPID_API_KEY`` to configure a ``RapidJudge0CE`` client. +5. Check for ``JUDGE0_ATD_API_KEY`` to configure an ``ATDJudge0CE`` client. +6. If none of the above are found, initialize a preview ``Judge0CloudCE`` client and log a warning. + +This implicit client resolution makes it easy to get started with the Judge0 Python SDK while providing the flexibility to configure it for different environments and services. From afb0f24630c361dbc78c8b3bd9f9b085db496086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Karlo=20Do=C5=A1ilovi=C4=87?= Date: Sat, 13 Dec 2025 11:32:38 +0100 Subject: [PATCH 161/161] Add step to generate CNAME file for documentation --- .github/workflows/docs.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index de80d4e6..f368c870 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -56,6 +56,9 @@ jobs: env: latest_release: ${{ env.LATEST_RELEASE }} + - name: Generate CNAME file + run: echo "python.docs.judge0.com" > docs/build/html/CNAME + - name: Upload artifacts uses: actions/upload-artifact@v4 with: