From 556c28a796c746e424a474e07c494f876f024ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frederik=20Hvilsh=C3=B8j?= <93145535+frederik-encord@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:55:45 +0100 Subject: [PATCH] chore: add pre-commit and mypy to the project (#28) * chore: add pre-commit and mypy to the project * fix: refactor base64type to new file to break circular import * chore: add unittests to pre-commit --- .gitignore | 1 + .pre-commit-config.yaml | 31 ++ encord_agents/__init__.py | 2 +- encord_agents/cli/main.py | 2 +- encord_agents/cli/print.py | 4 +- encord_agents/cli/test.py | 6 +- encord_agents/core/data_model.py | 8 +- encord_agents/core/dependencies/__init__.py | 2 +- encord_agents/core/settings.py | 12 +- encord_agents/core/types.py | 5 + encord_agents/core/utils.py | 5 +- encord_agents/core/vision.py | 59 +--- encord_agents/fastapi/__init__.py | 2 +- encord_agents/fastapi/dependencies.py | 4 +- encord_agents/fastapi/utils.py | 2 +- encord_agents/gcp/__init__.py | 2 +- encord_agents/gcp/wrappers.py | 2 +- encord_agents/tasks/__init__.py | 2 +- encord_agents/tasks/runner.py | 28 +- poetry.lock | 301 +++++++++++++++++++- pyproject.toml | 22 +- tests/test_ontology_model.py | 81 +++--- 22 files changed, 453 insertions(+), 130 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 encord_agents/core/types.py diff --git a/.gitignore b/.gitignore index dde2d8d..60b37d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ dist/ +encord-agents-unit-test-report.xml # Created by https://www.toptal.com/developers/gitignore/api/vim,python,macos # Edit at https://www.toptal.com/developers/gitignore?templates=vim,python,macos diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a916df0 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,31 @@ +repos: + - repo: local + hooks: + - id: ruff-fmt + name: ruff-fmt + entry: poetry run ruff format --config=pyproject.toml . + types_or: [python, pyi] + language: system + pass_filenames: false + + - id: ruff-check + name: ruff-check + entry: poetry run ruff check --config=pyproject.toml --fix . + types_or: [python, pyi] + language: system + pass_filenames: false + + - id: mypy + name: mypy + entry: poetry run mypy . --config-file=pyproject.toml + types_or: [python, pyi] + language: system + pass_filenames: false + + - id: unittest + name: unittest + entry: poetry run pytest --cov=encord_agents --junitxml encord-agents-unit-test-report.xml -s tests + types_or: [python, pyi] + language: system + pass_filenames: false +default_stages: [pre-push] diff --git a/encord_agents/__init__.py b/encord_agents/__init__.py index 8a97427..a046ec2 100644 --- a/encord_agents/__init__.py +++ b/encord_agents/__init__.py @@ -1,4 +1,4 @@ from .core.data_model import FrameData __version__ = "v0.1.2" -__ALL__ = ["FrameData"] +__all__ = ["FrameData"] diff --git a/encord_agents/cli/main.py b/encord_agents/cli/main.py index b073a53..1578095 100644 --- a/encord_agents/cli/main.py +++ b/encord_agents/cli/main.py @@ -22,7 +22,7 @@ @app.callback(invoke_without_command=True) def version( version_: bool = typer.Option(False, "--version", "-v", "-V", help="Print the current version of Encord Agents"), -): +) -> None: if version_: import rich diff --git a/encord_agents/cli/print.py b/encord_agents/cli/print.py index f9fc029..054baca 100644 --- a/encord_agents/cli/print.py +++ b/encord_agents/cli/print.py @@ -14,7 +14,7 @@ @app.command(name="agent-nodes") -def print_agent_nodes(project_hash: str): +def print_agent_nodes(project_hash: str) -> None: """ Prints agent nodes from project. @@ -50,7 +50,7 @@ def print_agent_nodes(project_hash: str): @app.command(name="system-info") -def print_system_info(): +def print_system_info() -> None: """ [bold]Prints[/bold] the information of the system for the purpose of bug reporting. """ diff --git a/encord_agents/cli/test.py b/encord_agents/cli/test.py index f91917f..3f1903b 100644 --- a/encord_agents/cli/test.py +++ b/encord_agents/cli/test.py @@ -28,7 +28,7 @@ def local( ], url: Annotated[str, Argument(help="Url copy/pasted from label editor")], port: Annotated[int, Option(help="Local host port to hit")] = 8080, -): +) -> None: """Hit a localhost agents endpoint for testing an agent by copying the url from the Encord Label Editor over. Given @@ -114,8 +114,8 @@ def local( table.add_row("label editor", editor_url) headers = ["'{0}: {1}'".format(k, v) for k, v in prepped.headers.items()] - headers = " -H ".join(headers) - curl_command = f"curl -X {prepped.method} \\{os.linesep} -H {headers} \\{os.linesep} -d '{prepped.body}' \\{os.linesep} '{prepped.url}'" + str_headers = " -H ".join(headers) + curl_command = f"curl -X {prepped.method} \\{os.linesep} -H {str_headers} \\{os.linesep} -d '{prepped.body!r}' \\{os.linesep} '{prepped.url}'" table.add_row("curl", curl_command) rich.print(table) diff --git a/encord_agents/core/data_model.py b/encord_agents/core/data_model.py index 05d2d93..7e8b6a1 100644 --- a/encord_agents/core/data_model.py +++ b/encord_agents/core/data_model.py @@ -9,6 +9,8 @@ from encord_agents.core.vision import DATA_TYPES, b64_encode_image +Base64Formats = Literal[".jpeg", ".jpg", ".png"] + class LabelRowMetadataIncludeArgs(BaseModel): """ @@ -61,7 +63,7 @@ class Frame: @overload def b64_encoding( self, - image_format: Literal[".jpeg", ".jpg", ".png"] = ".jpeg", + image_format: Base64Formats = ".jpeg", output_format: Literal["raw", "url"] = "raw", ) -> str: ... @@ -70,13 +72,13 @@ def b64_encoding( self, image_format: Literal[".jpeg", ".jpg", ".png"] = ".jpeg", output_format: Literal["openai", "anthropic"] = "openai", - ) -> dict: ... + ) -> dict[str, str | dict[str, str]]: ... def b64_encoding( self, image_format: Literal[".jpeg", ".jpg", ".png"] = ".jpeg", output_format: Literal["url", "openai", "anthropic", "raw"] = "url", - ) -> str | dict: + ) -> str | dict[str, str | dict[str, str]]: """ Get a base64 representation of the image content. diff --git a/encord_agents/core/dependencies/__init__.py b/encord_agents/core/dependencies/__init__.py index 82b6784..11bb09f 100644 --- a/encord_agents/core/dependencies/__init__.py +++ b/encord_agents/core/dependencies/__init__.py @@ -1,3 +1,3 @@ from .models import Depends -__ALL__ = ["Depends"] +__all__ = ["Depends"] diff --git a/encord_agents/core/settings.py b/encord_agents/core/settings.py index 65a2d5d..ff2cb28 100644 --- a/encord_agents/core/settings.py +++ b/encord_agents/core/settings.py @@ -35,7 +35,7 @@ class Settings(BaseSettings): @field_validator("ssh_key_content") @classmethod - def check_key_content(cls, content: str | None): + def check_key_content(cls, content: str | None) -> str | None: if content is None: return content @@ -49,7 +49,7 @@ def check_key_content(cls, content: str | None): @field_validator("ssh_key_file") @classmethod - def check_path_expand_and_exists(cls, path: Path | None): + def check_path_expand_and_exists(cls, path: Path | None) -> Path | None: if path is None: return path @@ -63,7 +63,7 @@ def check_path_expand_and_exists(cls, path: Path | None): return path @model_validator(mode="after") - def check_key(self): + def check_key(self: "Settings") -> "Settings": if not any(map(bool, [self.ssh_key_content, self.ssh_key_file])): raise PrintableError( f"Must specify either `[blue]ENCORD_SSH_KEY_FILE[/blue]` or `[blue]ENCORD_SSH_KEY[/blue]` env variables. If you don't have an ssh key, please refere to our docs:{os.linesep}[magenta]https://docs.encord.com/platform-documentation/Annotate/annotate-api-keys#creating-keys-using-terminal-powershell[/magenta]" @@ -80,4 +80,8 @@ def check_key(self): @property def ssh_key(self) -> str: - return self.ssh_key_content if self.ssh_key_content else self.ssh_key_file.read_text() + if self.ssh_key_content is None: + if self.ssh_key_file is None: + raise ValueError("Both ssh key content and ssh key file is None") + self.ssh_key_content = self.ssh_key_file.read_text() + return self.ssh_key_content diff --git a/encord_agents/core/types.py b/encord_agents/core/types.py new file mode 100644 index 0000000..500f47b --- /dev/null +++ b/encord_agents/core/types.py @@ -0,0 +1,5 @@ +from typing import Literal + +Base64Formats = Literal[".jpeg", ".jpg", ".png"] + + diff --git a/encord_agents/core/utils.py b/encord_agents/core/utils.py index cdc54c3..b6f1df6 100644 --- a/encord_agents/core/utils.py +++ b/encord_agents/core/utils.py @@ -25,7 +25,7 @@ def get_user_client() -> EncordUserClient: An EncordUserClient authenticated with the credentials from the encord_agents.core.settings.Settings. """ - settings = Settings() # type: ignore + settings = Settings() kwargs: dict[str, Any] = {"domain": settings.domain} if settings.domain else {} return EncordUserClient.create_with_ssh_private_key(ssh_private_key=settings.ssh_key, **kwargs) @@ -166,4 +166,5 @@ def download_asset(lr: LabelRowV2, frame: int | None) -> Generator[Path, None, N try: yield file_path finally: - [f.unlink(missing_ok=True) for f in files_to_unlink] + for to_unlink in files_to_unlink: + to_unlink.unlink(missing_ok=True) diff --git a/encord_agents/core/vision.py b/encord_agents/core/vision.py index 52ca40e..328ebf6 100644 --- a/encord_agents/core/vision.py +++ b/encord_agents/core/vision.py @@ -1,4 +1,5 @@ import base64 +from typing import TypeAlias import cv2 import numpy as np @@ -6,7 +7,9 @@ from encord.objects.coordinates import BoundingBoxCoordinates, PolygonCoordinates, RotatableBoundingBoxCoordinates from numpy.typing import NDArray -CroppableCoordinates = ( +from .types import Base64Formats + +CroppableCoordinates: TypeAlias = ( BoundingBoxCoordinates | RotatableBoundingBoxCoordinates | BitmaskCoordinates | PolygonCoordinates ) @@ -22,7 +25,7 @@ def rbb_to_poly( rbb: RotatableBoundingBoxCoordinates, img_width: int, img_height: int, -) -> np.ndarray: +) -> NDArray[np.float32]: x = rbb.top_left_x y = rbb.top_left_y w = rbb.width @@ -33,7 +36,8 @@ def rbb_to_poly( [(x + w) * img_width, y * img_height], [(x + w) * img_width, (y + h) * img_height], [x * img_width, (y + h) * img_height], - ] + ], + dtype=np.float32 ) angle = rbb.theta # [0; 360] center = tuple(bbox_not_rotated.mean(0).tolist()) @@ -47,48 +51,11 @@ def rbb_to_poly( mode="constant", constant_values=1, ) - rotated_points: np.ndarray = points @ rotation_matrix.T + rotated_points = points @ rotation_matrix.T.astype(np.float32) return rotated_points -def poly_to_rbb( - poly: np.ndarray, - img_width: int, - img_height: int, -) -> RotatableBoundingBoxCoordinates: - v1 = poly[1] - poly[0] - v1 = v1 / np.linalg.norm(v1, ord=2) - angle = np.degrees(np.arccos(v1[0])) - - if not any( - [poly[0, 0] > poly[3, 0], poly[0, 0] == poly[3, 0] and poly[0, 1] < poly[3, 1]] - ): # Initial points were rotated more than 180 degrees => Rotate backwards - angle = 360 - angle - - center = poly.mean(axis=0) - rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale=1.0) - points = np.pad( - poly, - [ - (0, 0), - (0, 1), - ], - mode="constant", - constant_values=1, - ) - rotated_points = points @ rotation_matrix.T - x, y = rotated_points.min(0) - w, h = rotated_points.max(0) - rotated_points.min(0) - return RotatableBoundingBoxCoordinates( - top_left_x=float(x / img_width), - top_left_y=float(y / img_height), - width=float(w / img_width), - height=float(h / img_height), - theta=float(angle), - ) - - -def crop_to_bbox(image: NDArray, bbox: BoundingBoxCoordinates) -> NDArray: +def crop_to_bbox(image: NDArray[np.uint8], bbox: BoundingBoxCoordinates) -> NDArray[np.uint8]: img_height, img_width = image.shape[:2] from_x = int(img_width * bbox.top_left_x + 0.5) from_y = int(img_height * bbox.top_left_y + 0.5) @@ -97,7 +64,7 @@ def crop_to_bbox(image: NDArray, bbox: BoundingBoxCoordinates) -> NDArray: return image[from_y:to_y, from_x:to_x] -def poly_to_bbox(poly: PolygonCoordinates | NDArray) -> BoundingBoxCoordinates: +def poly_to_bbox(poly: PolygonCoordinates | NDArray[np.float32]) -> BoundingBoxCoordinates: if isinstance(poly, PolygonCoordinates): rel_coords = np.array([[v.x, v.y] for v in poly.values]) else: @@ -111,7 +78,7 @@ def poly_to_bbox(poly: PolygonCoordinates | NDArray) -> BoundingBoxCoordinates: def rbbox_to_surrounding_bbox(rbb: RotatableBoundingBoxCoordinates, img_w: int, img_h: int) -> BoundingBoxCoordinates: abs_coords = rbb_to_poly(rbb, img_width=img_w, img_height=img_h) - rel_coords = abs_coords / np.array([[img_w, img_h]], dtype=float) + rel_coords = abs_coords / np.array([[img_w, img_h]], dtype=np.float32) return poly_to_bbox(rel_coords) @@ -143,6 +110,6 @@ def crop_to_object(image: NDArray[np.uint8], coordinates: CroppableCoordinates) return crop_to_bbox(image, box) -def b64_encode_image(img: NDArray[np.uint8], format=".jpg"): +def b64_encode_image(img: NDArray[np.uint8], format: Base64Formats =".jpg") -> str: _, encoded_image = cv2.imencode(format, img) - return base64.b64encode(encoded_image).decode("utf-8") + return base64.b64encode(encoded_image).decode("utf-8") # type: ignore diff --git a/encord_agents/fastapi/__init__.py b/encord_agents/fastapi/__init__.py index 1a86af5..30579d0 100644 --- a/encord_agents/fastapi/__init__.py +++ b/encord_agents/fastapi/__init__.py @@ -1,7 +1,7 @@ from .dependencies import dep_client, dep_label_row, dep_single_frame from .utils import verify_auth -__ALL__ = [ +__all__ = [ "dep_single_frame", "dep_label_row", "dep_client", diff --git a/encord_agents/fastapi/dependencies.py b/encord_agents/fastapi/dependencies.py index b54f27a..84db5fd 100644 --- a/encord_agents/fastapi/dependencies.py +++ b/encord_agents/fastapi/dependencies.py @@ -147,7 +147,7 @@ def my_route( return get_initialised_label_row(frame_data) -def dep_single_frame(lr: Annotated[LabelRowV2, Depends(dep_label_row)], frame_data: Annotated[FrameData, Form()]): +def dep_single_frame(lr: Annotated[LabelRowV2, Depends(dep_label_row)], frame_data: Annotated[FrameData, Form()]) -> NDArray[np.uint8]: """ Dependency to inject the underlying asset of the frame data. @@ -214,7 +214,7 @@ def my_route( yield iter_video(asset) -def dep_project(frame_data: Annotated[FrameData, Form()], client: Annotated[EncordUserClient, Depends(dep_client)]): +def dep_project(frame_data: Annotated[FrameData, Form()], client: Annotated[EncordUserClient, Depends(dep_client)]) -> Project: r""" Dependency to provide an instantiated [Project](https://docs.encord.com/sdk-documentation/sdk-references/LabelRowV2){ target="\_blank", rel="noopener noreferrer" }. diff --git a/encord_agents/fastapi/utils.py b/encord_agents/fastapi/utils.py index a1493db..b63c34b 100644 --- a/encord_agents/fastapi/utils.py +++ b/encord_agents/fastapi/utils.py @@ -7,7 +7,7 @@ from encord_agents.exceptions import PrintableError -def verify_auth(): +def verify_auth() -> None: """ FastAPI lifecycle start hook to fail early if ssh key is missing. diff --git a/encord_agents/gcp/__init__.py b/encord_agents/gcp/__init__.py index 1f72a71..8c36eb9 100644 --- a/encord_agents/gcp/__init__.py +++ b/encord_agents/gcp/__init__.py @@ -2,4 +2,4 @@ from .wrappers import editor_agent -__ALL__ = ["editor_agent", "Depends"] +__all__ = ["editor_agent", "Depends"] diff --git a/encord_agents/gcp/wrappers.py b/encord_agents/gcp/wrappers.py index 1ff925e..06d80da 100644 --- a/encord_agents/gcp/wrappers.py +++ b/encord_agents/gcp/wrappers.py @@ -43,7 +43,7 @@ def editor_agent( A wrapped function suitable for gcp functions. """ - def context_wrapper_inner(func: AgentFunction) -> Callable: + def context_wrapper_inner(func: AgentFunction) -> Callable[[Request], Response]: dependant = get_dependant(func=func) @wraps(func) diff --git a/encord_agents/tasks/__init__.py b/encord_agents/tasks/__init__.py index af0f487..5c43c50 100644 --- a/encord_agents/tasks/__init__.py +++ b/encord_agents/tasks/__init__.py @@ -2,4 +2,4 @@ from .runner import Runner -__ALL__ = ["Runner", "Depends"] +__all__ = ["Runner", "Depends"] diff --git a/encord_agents/tasks/runner.py b/encord_agents/tasks/runner.py index 8c5bbb4..8f2b042 100644 --- a/encord_agents/tasks/runner.py +++ b/encord_agents/tasks/runner.py @@ -113,7 +113,7 @@ def __init__(self, project_hash: str | None = None): self.was_called_from_cli = False @staticmethod - def validate_project(project: Project | None): + def validate_project(project: Project | None) -> None: if project is None: return PROJECT_MUSTS = "Task agents only work for workflow projects that have agent nodes in the workflow." @@ -130,7 +130,7 @@ def _add_stage_agent( func: Callable[..., TaskAgentReturn], printable_name: str | None, label_row_metadata_include_args: LabelRowMetadataIncludeArgs | None, - ): + ) -> None: self.agents.append( RunnerAgent( identity=identity, @@ -256,7 +256,7 @@ def _execute_tasks( runner_agent: RunnerAgent, # num_threads: int, num_retries: int, - pbar: tqdm | None = None, + pbar_update: Callable[[float | None], bool | None] | None = None, ) -> None: with Bundle() as bundle: for task, label_row in tasks: @@ -277,8 +277,8 @@ def _execute_tasks( except ValueError: task.proceed(pathway_name=next_stage, bundle=bundle) - if pbar is not None: - pbar.update(1) + if pbar_update is not None: + pbar_update(1.) break except KeyboardInterrupt: @@ -288,7 +288,7 @@ def _execute_tasks( traceback.print_exc() @staticmethod - def get_stage_names(valid_stages: list[AgentStage], join_str: str = ", "): + def get_stage_names(valid_stages: list[AgentStage], join_str: str = ", ") -> str: return join_str.join( [f'[magenta]AgentStage(title="{k.title}", uuid="{k.uuid}")[/magenta]' for k in valid_stages] ) @@ -311,7 +311,7 @@ def __call__( project_hash: Annotated[ Optional[str], Option(help="The project hash if not defined at runner instantiation.") ] = None, - ): + ) -> None: """ Run your task agent `runner(...)`. @@ -333,8 +333,11 @@ def __call__( if project_hash is not None: project_hash = self.verify_project_hash(project_hash) project = self.client.get_project(project_hash) - else: + elif self.project is not None: project = self.project + else: + # Should not happen. Validated above but mypy doesn't understand. + raise ValueError("Have no project to execute the runner on. Please specify it.") if project is None: import sys @@ -435,7 +438,6 @@ def {fn_name}(...): data_hashes=[t.data_hash for t in batch], **include_args.model_dump() ) } - print([lr.backing_item_uuid for lr in label_rows.values()]) batch_lrs = [label_rows.get(t.data_hash) for t in batch] with project.create_bundle() as lr_bundle: for lr in batch_lrs: @@ -447,7 +449,7 @@ def {fn_name}(...): zip(batch, batch_lrs), runner_agent, num_retries, - pbar=pbar, + pbar_update=pbar.update, ) batch = [] @@ -467,14 +469,12 @@ def {fn_name}(...): ), ) } - print("I am here") - print([lr.backing_item_uuid for lr in label_rows.values()]) batch_lrs = [label_rows[t.data_hash] for t in batch] with project.create_bundle() as lr_bundle: for lr in batch_lrs: if lr: lr.initialise_labels(bundle=lr_bundle) - self._execute_tasks(project, zip(batch, batch_lrs), runner_agent, num_retries, pbar=pbar) + self._execute_tasks(project, zip(batch, batch_lrs), runner_agent, num_retries, pbar_update=pbar.update) except (PrintableError, AssertionError) as err: if self.was_called_from_cli: panel = Panel(err.args[0], width=None) @@ -488,7 +488,7 @@ def {fn_name}(...): err.args = (plain_text,) raise - def run(self): + def run(self) -> None: """ Execute the runner. diff --git a/poetry.lock b/poetry.lock index 72a454d..416f803 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand. [[package]] name = "annotated-types" @@ -133,6 +133,17 @@ files = [ [package.dependencies] pycparser = "*" +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + [[package]] name = "charset-normalizer" version = "3.4.0" @@ -272,6 +283,83 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.6.8" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"}, + {file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"}, + {file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"}, + {file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"}, + {file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"}, + {file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"}, + {file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"}, + {file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"}, + {file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"}, + {file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"}, + {file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"}, + {file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"}, + {file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"}, + {file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"}, + {file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"}, + {file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"}, + {file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"}, + {file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"}, + {file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"}, + {file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"}, + {file = "coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e"}, + {file = "coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c"}, + {file = "coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0"}, + {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779"}, + {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92"}, + {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4"}, + {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc"}, + {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea"}, + {file = "coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e"}, + {file = "coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076"}, + {file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"}, + {file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + [[package]] name = "cryptography" version = "43.0.1" @@ -332,6 +420,17 @@ files = [ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] +[[package]] +name = "distlib" +version = "0.3.9" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, + {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, +] + [[package]] name = "encord" version = "0.1.152" @@ -379,6 +478,22 @@ files = [ [package.extras] tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] +[[package]] +name = "filelock" +version = "3.16.1" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, + {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] +typing = ["typing-extensions (>=4.12.2)"] + [[package]] name = "ghp-import" version = "2.1.0" @@ -442,6 +557,20 @@ files = [ [package.dependencies] colorama = ">=0.4" +[[package]] +name = "identify" +version = "2.6.3" +description = "File identification library for Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd"}, + {file = "identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02"}, +] + +[package.extras] +license = ["ukkonen"] + [[package]] name = "idna" version = "3.10" @@ -899,6 +1028,81 @@ griffe = ">=0.49" mkdocs-autorefs = ">=1.2" mkdocstrings = ">=0.26" +[[package]] +name = "mypy" +version = "1.13.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + [[package]] name = "numpy" version = "2.1.2" @@ -979,10 +1183,13 @@ files = [ [package.dependencies] numpy = [ + {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, + {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, + {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""}, - {version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""}, + {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, + {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, + {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, ] [[package]] @@ -1148,6 +1355,24 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pre-commit" +version = "4.0.1" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"}, + {file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + [[package]] name = "prompt-toolkit" version = "3.0.48" @@ -1407,6 +1632,24 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "6.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, + {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, +] + +[package.dependencies] +coverage = {version = ">=7.5", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1807,6 +2050,34 @@ rich = ">=10.11.0" shellingham = ">=1.3.0" typing-extensions = ">=3.7.4.3" +[[package]] +name = "types-requests" +version = "2.32.0.20241016" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"}, + {file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"}, +] + +[package.dependencies] +urllib3 = ">=2" + +[[package]] +name = "types-tqdm" +version = "4.67.0.20241119" +description = "Typing stubs for tqdm" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-tqdm-4.67.0.20241119.tar.gz", hash = "sha256:1769e0e94d5e6d8fa814965f9cf3d9928376dd15dabcbcb784bb8769081092b4"}, + {file = "types_tqdm-4.67.0.20241119-py3-none-any.whl", hash = "sha256:a18d4eb62db0d35c52707ae13d821b5a57970755273ecb56e133ccc0ac7e7c79"}, +] + +[package.dependencies] +types-requests = "*" + [[package]] name = "typing-extensions" version = "4.12.2" @@ -1835,6 +2106,26 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "virtualenv" +version = "20.28.0" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.8" +files = [ + {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, + {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + [[package]] name = "watchdog" version = "5.0.3" @@ -1891,4 +2182,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "bbec5a279e82dc8204fe4e4a86850b9ad797e75cbce5f6f011b4847a1e39c81e" +content-hash = "bdd6f7fb4fbba70422ba7436f2c12ddc7b4c9c792bd7daf5608cd96d6c2eb132" diff --git a/pyproject.toml b/pyproject.toml index 4091d21..1535968 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,12 @@ mkdocs-glightbox = "^0.4.0" ruff = "^0.6.9" mkdocs-git-revision-date-localized-plugin = "^1.3.0" pytest = "^8.3.3" -mkdocs-codeinclude-plugin = {git = "https://github.com/frederik-encord/mkdocs-codeinclude-plugin.git"} +mkdocs-codeinclude-plugin = { git = "https://github.com/frederik-encord/mkdocs-codeinclude-plugin.git" } +pre-commit = "^4.0.1" +types-requests = "^2.32.0.20241016" +types-tqdm = "^4.67.0.20241119" +mypy = "^1.13.0" +pytest-cov = "^6.0.0" [tool.poetry.scripts] @@ -85,3 +90,18 @@ line-length = 120 [tool.ruff.lint] extend-select = ["W", "Q", "I"] ignore = ["F401", "E402", "W291"] + +[tool.mypy] +plugins = ['pydantic.mypy'] +exclude = ['docs/', 'encord_agents/core/ontology.py'] +python_version = "3.10" +warn_unused_ignores = true +disallow_untyped_calls = true +disallow_untyped_defs = true +disallow_untyped_decorators = true +strict_equality = true +check_untyped_defs = true +no_implicit_reexport = true +ignore_missing_imports = true +warn_return_any = true +strict = true diff --git a/tests/test_ontology_model.py b/tests/test_ontology_model.py index 3c1f83e..3b47ea8 100644 --- a/tests/test_ontology_model.py +++ b/tests/test_ontology_model.py @@ -1,5 +1,6 @@ from encord.objects.attributes import ChecklistAttribute, RadioAttribute, TextAttribute from encord.objects.common import Shape +from encord.objects.ontology_object import Object from encord.objects.ontology_object_instance import ObjectInstance from encord.objects.ontology_structure import OntologyStructure @@ -38,7 +39,7 @@ ] -def test_simple_radio_attribute(): +def test_simple_radio_attribute() -> None: # Create structure gen = iter(uids) struct = OntologyStructure() @@ -60,7 +61,7 @@ def test_simple_radio_attribute(): assert encord_classification_instances[0].get_answer().value == "blue" # type: ignore -def test_nested_radio_attribute(): +def test_nested_radio_attribute() -> None: gen = iter(uids) # Create structure struct = OntologyStructure() @@ -68,8 +69,8 @@ def test_nested_radio_attribute(): attr = clf.add_attribute(RadioAttribute, name="color", feature_node_hash=next(gen)) for opt in ["red", "blue", "green"]: - opt = attr.add_option(label=opt, feature_node_hash=next(gen)) - nattr = opt.add_nested_attribute(RadioAttribute, "count", feature_node_hash=next(gen)) + ont_opt = attr.add_option(label=opt, feature_node_hash=next(gen)) + nattr = ont_opt.add_nested_attribute(RadioAttribute, "count", feature_node_hash=next(gen)) nattr.add_option("0 to 5", feature_node_hash=next(gen)) nattr.add_option("5 to 10", feature_node_hash=next(gen)) nattr.add_option("10 to 15", feature_node_hash=next(gen)) @@ -82,7 +83,7 @@ def test_nested_radio_attribute(): assert instances[0].get_answer() -def test_simple_text_attribute(): +def test_simple_text_attribute() -> None: # Create structure gen = iter(uids) struct = OntologyStructure() @@ -104,7 +105,7 @@ def test_simple_text_attribute(): assert encord_classification_instances[0].get_answer() == set_value -def test_simple_checklist(): +def test_simple_checklist() -> None: # Create structure gen = iter(uids) struct = OntologyStructure() @@ -127,18 +128,18 @@ def test_simple_checklist(): encord_classification_instances = model.validate_json(answer) assert len(encord_classification_instances) == 1 - answer = encord_classification_instances[0].get_answer() - assert isinstance(answer, list) - assert len(answer) == 2 + encord_answer = encord_classification_instances[0].get_answer() + assert isinstance(encord_answer, list) + assert len(encord_answer) == 2 - names = [a.title for a in answer] + names = [a.title for a in encord_answer] assert "blueberry" in names assert "leaves" in names assert "strawberry" not in names assert "flies" not in names -def test_flat_objects(): +def test_flat_objects() -> None: struct = OntologyStructure() ont_objects = [ struct.add_object(obj_name, Shape.POLYGON) for obj_name in ["strawberry", "apple", "pineapple", "blueberry"] @@ -147,16 +148,16 @@ def test_flat_objects(): answer = """{ "choice": "blueberry" }""" - answer = model.validate_json(answer) - assert isinstance(answer, ObjectInstance) - assert answer.object_name == "blueberry" + encord_answer = model.validate_json(answer) + assert isinstance(encord_answer, ObjectInstance) + assert encord_answer.object_name == "blueberry" -def test_nested_objects(): +def test_nested_objects() -> None: gen = iter(uids) struct = OntologyStructure() - def add_object(obj_name): + def add_object(obj_name: str) -> Object: ont_obj = struct.add_object(obj_name, Shape.POLYGON, feature_node_hash=next(gen)) attr = ont_obj.add_attribute(RadioAttribute, "count", feature_node_hash=next(gen)) attr.add_option("0 to 5", feature_node_hash=next(gen)) @@ -170,16 +171,16 @@ def add_object(obj_name): answer = """{ "choice": { "feature_node_hash": "6666aaaa", "count": { "feature_node_hash": "33337777", "choice": "6 to 10" } } } """ - answer = model.validate_json(answer) - assert isinstance(answer, ObjectInstance) - assert answer.object_name == "blueberry" + encord_answer = model.validate_json(answer) + assert isinstance(encord_answer, ObjectInstance) + assert encord_answer.object_name == "blueberry" - nested_answer = answer.get_answer(answer.ontology_item.attributes[0]) + nested_answer = encord_answer.get_answer(encord_answer.ontology_item.attributes[0]) assert nested_answer assert nested_answer.label == "6 to 10" # type: ignore -def test_only_some_nested_radio_attributes(): +def test_only_some_nested_radio_attributes() -> None: gen = iter(uids) # Create structure struct = OntologyStructure() @@ -187,10 +188,10 @@ def test_only_some_nested_radio_attributes(): attr = clf.add_attribute(RadioAttribute, name="color", feature_node_hash=next(gen)) for opt in ["red", "blue", "green"]: - opt = attr.add_option(label=opt, feature_node_hash=next(gen)) - if opt.label == "red": + ont_opt = attr.add_option(label=opt, feature_node_hash=next(gen)) + if ont_opt.label == "red": continue - nattr = opt.add_nested_attribute(RadioAttribute, "count", feature_node_hash=next(gen)) + nattr = ont_opt.add_nested_attribute(RadioAttribute, "count", feature_node_hash=next(gen)) nattr.add_option("0 to 5", feature_node_hash=next(gen)) nattr.add_option("5 to 10", feature_node_hash=next(gen)) nattr.add_option("10 to 15", feature_node_hash=next(gen)) @@ -214,11 +215,11 @@ def test_only_some_nested_radio_attributes(): assert nested_option.label == "5 to 10" # type: ignore -def test_only_some_nested_objects(): +def test_only_some_nested_objects() -> None: gen = iter(uids) struct = OntologyStructure() - def add_object(obj_name): + def add_object(obj_name: str) -> Object: ont_obj = struct.add_object(obj_name, Shape.POLYGON, feature_node_hash=next(gen)) if obj_name == "blueberry": return ont_obj @@ -235,25 +236,25 @@ def add_object(obj_name): "choice": { "feature_node_hash": "6666aaaa", "count": { "feature_node_hash": "33337777", "choice": "6 to 10" } } } """ - answer = model.validate_json(answer) - assert isinstance(answer, ObjectInstance) - assert answer.object_name == "blueberry" - assert len(answer.ontology_item.attributes) == 0 + encord_answer = model.validate_json(answer) + assert isinstance(encord_answer, ObjectInstance) + assert encord_answer.object_name == "blueberry" + assert len(encord_answer.ontology_item.attributes) == 0 answer = f"""{{ "choice": {{ "feature_node_hash": "{uids[0]}", "count": {{ "feature_node_hash": "{uids[1]}", "choice": "6 to 10" }} }} }} """ - answer = model.validate_json(answer) - nested_answer = answer.get_answer(answer.ontology_item.attributes[0]) + encord_answer = model.validate_json(answer) + nested_answer = encord_answer.get_answer(encord_answer.ontology_item.attributes[0]) assert nested_answer assert nested_answer.label == "6 to 10" # type: ignore -def test_just_one_nested_object(): +def test_just_one_nested_object() -> None: gen = iter(uids) struct = OntologyStructure() - def add_object(obj_name): + def add_object(obj_name: str) -> Object: ont_obj = struct.add_object(obj_name, Shape.POLYGON, feature_node_hash=next(gen)) if obj_name == "blueberry": return ont_obj @@ -278,10 +279,10 @@ def add_object(obj_name): } """ - answer = model.validate_json(answer) - assert isinstance(answer, ObjectInstance) - assert answer.object_name == "my_single_object" - assert len(answer.ontology_item.attributes) == 1 - attr = answer.ontology_item.attributes[0] - nested_answer = answer.get_answer(attr) + encord_answer = model.validate_json(answer) + assert isinstance(encord_answer, ObjectInstance) + assert encord_answer.object_name == "my_single_object" + assert len(encord_answer.ontology_item.attributes) == 1 + attr = encord_answer.ontology_item.attributes[0] + nested_answer = encord_answer.get_answer(attr) assert nested_answer.label == "10 to 15" # type: ignore