From b524e377fe37014ccee0110904a54a46fb95d034 Mon Sep 17 00:00:00 2001 From: Sina Atalay <79940989+sinaatalay@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:54:22 +0300 Subject: [PATCH] Switch from pyright to ty (https://github.com/astral-sh/ty) --- .pre-commit-config.yaml | 20 +++-- docs/docs_templating.py | 5 +- justfile | 2 +- pyproject.toml | 17 +--- scripts/update_entry_figures.py | 2 +- .../cli/render_command/render_command.py | 2 +- .../cli/render_command/run_rendercv.py | 2 +- src/rendercv/cli/render_command/watcher.py | 5 +- src/rendercv/exception.py | 2 +- src/rendercv/renderer/path_resolver.py | 3 + .../renderer/templater/connections.py | 2 +- .../templater/entry_templates_from_input.py | 12 +-- .../renderer/templater/model_processor.py | 8 +- .../renderer/templater/string_processor.py | 6 +- src/rendercv/schema/models/cv/section.py | 6 +- .../schema/models/cv/social_network.py | 7 +- .../schema/models/design/built_in_design.py | 2 +- .../schema/models/design/font_family.py | 2 +- src/rendercv/schema/models/locale/locale.py | 2 +- src/rendercv/schema/override_dictionary.py | 31 ++----- .../schema/pydantic_error_handling.py | 33 ++++---- src/rendercv/schema/rendercv_model_builder.py | 4 +- .../variant_pydantic_model_generator.py | 2 +- tests/renderer/templater/test_connections.py | 4 +- .../test_entry_templates_from_input.py | 19 ++--- .../templater/test_model_processor.py | 14 ++-- tests/schema/models/cv/conftest.py | 17 ++-- tests/schema/models/cv/test_cv.py | 14 ++-- tests/schema/models/cv/test_social_network.py | 2 +- tests/schema/test_override_dictionary.py | 6 +- .../test_variant_pydantic_model_generator.py | 16 ++-- uv.lock | 84 +++++++++++-------- 32 files changed, 180 insertions(+), 173 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a4312113..430d5905 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,20 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.8 + # Ruff version. + rev: v0.14.10 hooks: - - id: ruff - # - repo: https://github.com/RobertCraigie/pyright-python - # rev: v1.1.407 - # hooks: - # - id: pyright + # Run the linter. + - id: ruff-check + # Run the formatter. + - id: ruff-format + - repo: local + hooks: + - id: ty-check + name: ty-check + language: python + entry: ty check src tests + pass_filenames: false + additional_dependencies: [ty] - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: diff --git a/docs/docs_templating.py b/docs/docs_templating.py index 1adf64a1..4e239420 100644 --- a/docs/docs_templating.py +++ b/docs/docs_templating.py @@ -99,7 +99,10 @@ def define_env(env): ] # Available themes strings (put available themes between ``) - themes = [f"`{theme}`" if theme != "classic" else "`classic` (default)" for theme in available_themes] + themes = [ + f"`{theme}`" if theme != "classic" else "`classic` (default)" + for theme in available_themes + ] env.variables["available_themes"] = ", ".join(themes) env.variables["theme_count"] = len(available_themes) diff --git a/justfile b/justfile index 0bb10f9d..42940cba 100644 --- a/justfile +++ b/justfile @@ -17,7 +17,7 @@ format-file target: check: uv run --frozen --all-extras ruff check src tests - uv run --frozen --all-extras pyright src tests + uv run --frozen --all-extras ty check src tests uv run --frozen --all-extras pre-commit run --all-files # Testing: diff --git a/pyproject.toml b/pyproject.toml index 4f541275..8446ffa7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,9 +89,9 @@ rendercv = 'rendercv.cli.entry_point:entry_point' # Virtual Environment Dependencies: [dependency-groups] dev = [ - 'ruff>=0.14.8', # Lint and format the code + 'ruff>=0.14.10', # Lint and format the code 'black>=25.12.0', # Format the code - 'pyright>=1.1.407', # Type checking + 'ty>=0.0.5', # Type checking 'pre-commit>=4.5.0', # Run checks before committing 'pytest>=9.0.2', # Run tests 'pytest-cov>=7.0.0', # Coverage plugin for pytest with xdist support @@ -170,17 +170,8 @@ enable-unstable-feature = [ 'string_processing', ] # Break strings into multiple lines -[tool.pyright] -venvPath = "." -venv = ".venv" -typeCheckingMode = 'standard' -enableTypeIgnoreComments = false -reportUnnecessaryTypeIgnoreComment = true -reportIncompatibleVariableOverride = false -reportIncompatibleMethodOverride = false -reportMissingTypeStubs = false -reportPrivateUsage = false -exclude = ['docs/**/*'] +[tool.ty.rules] +unused-ignore-comment = "error" [tool.coverage.run] source = ['src/rendercv'] # Measure coverage in this source diff --git a/scripts/update_entry_figures.py b/scripts/update_entry_figures.py index 993e1c1b..7a7cc8dd 100644 --- a/scripts/update_entry_figures.py +++ b/scripts/update_entry_figures.py @@ -29,7 +29,7 @@ def pdf_to_png(pdf_file_path: pathlib.Path) -> list[pathlib.Path]: png_files = [] pdf = fitz.open(pdf_file_path) # open the PDF file for page in pdf: # iterate the pages - image = page.get_pixmap(dpi=300) # type: ignore + image = page.get_pixmap(dpi=300) assert page.number is not None png_file_path = png_directory / f"{png_file_name}_{page.number + 1}.png" image.save(png_file_path) diff --git a/src/rendercv/cli/render_command/render_command.py b/src/rendercv/cli/render_command/render_command.py index c8c488f0..393bde09 100644 --- a/src/rendercv/cli/render_command/render_command.py +++ b/src/rendercv/cli/render_command/render_command.py @@ -184,7 +184,7 @@ def cli_command_render( ' [cyan bold]--cv.phone "123-456-7890"[/cyan bold].', ), ] = None, - extra_data_model_override_arguments: typer.Context = None, # pyright: ignore[reportArgumentType] + extra_data_model_override_arguments: typer.Context = None, # ty: ignore[invalid-parameter-default] ): arguments: BuildRendercvModelArguments = { "design_file_path_or_contents": design if design else None, diff --git a/src/rendercv/cli/render_command/run_rendercv.py b/src/rendercv/cli/render_command/run_rendercv.py index 82db5a03..7b6de657 100644 --- a/src/rendercv/cli/render_command/run_rendercv.py +++ b/src/rendercv/cli/render_command/run_rendercv.py @@ -61,7 +61,7 @@ def timed_step[T, **P]( elif isinstance(result, list) and result: if len(result) > 1: message = f"{message}s" - paths = result + paths = result # ty: ignore[invalid-assignment] if paths: progress_panel.update_progress( diff --git a/src/rendercv/cli/render_command/watcher.py b/src/rendercv/cli/render_command/watcher.py index 21911f3b..23f0cecb 100644 --- a/src/rendercv/cli/render_command/watcher.py +++ b/src/rendercv/cli/render_command/watcher.py @@ -27,7 +27,10 @@ def run_function_if_file_changes(file_path: pathlib.Path, function: Callable): super().__init__() self.function = function - def on_modified(self, event: watchdog.events.FileModifiedEvent) -> None: + def on_modified( + self, + event: watchdog.events.DirModifiedEvent | watchdog.events.FileModifiedEvent, + ) -> None: if event.src_path != str(file_path.absolute()): return diff --git a/src/rendercv/exception.py b/src/rendercv/exception.py index 8dd9cbdb..7951d0c7 100644 --- a/src/rendercv/exception.py +++ b/src/rendercv/exception.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, field @dataclass class RenderCVValidationError: location: tuple[str, ...] - yaml_location: tuple[tuple[int, int], tuple[int, int]] + yaml_location: tuple[tuple[int, int], tuple[int, int]] | None message: str input: str diff --git a/src/rendercv/renderer/path_resolver.py b/src/rendercv/renderer/path_resolver.py index abe754c9..2637afcb 100644 --- a/src/rendercv/renderer/path_resolver.py +++ b/src/rendercv/renderer/path_resolver.py @@ -70,6 +70,9 @@ def resolve_rendercv_file_path( else None ), } + file_path_placeholders = { + k: v for k, v in file_path_placeholders.items() if v is not None + } file_name = substitute_placeholders(file_path.name, file_path_placeholders) resolved_file_path = file_path.parent / file_name resolved_file_path.parent.mkdir(parents=True, exist_ok=True) diff --git a/src/rendercv/renderer/templater/connections.py b/src/rendercv/renderer/templater/connections.py index c47b3829..428eb2cf 100644 --- a/src/rendercv/renderer/templater/connections.py +++ b/src/rendercv/renderer/templater/connections.py @@ -117,7 +117,7 @@ def parse_connections(rendercv_model: RenderCVModel) -> list[Connection]: for website in websites: url = str(website) - body = clean_url(website) + body = clean_url(url) connections.append( Connection( fontawesome_icon=fontawesome_icons[key], url=url, body=body diff --git a/src/rendercv/renderer/templater/entry_templates_from_input.py b/src/rendercv/renderer/templater/entry_templates_from_input.py index 06dbbc95..ecc846e3 100644 --- a/src/rendercv/renderer/templater/entry_templates_from_input.py +++ b/src/rendercv/renderer/templater/entry_templates_from_input.py @@ -98,11 +98,11 @@ def render_entry_templates[EntryType: Entry]( ) if "URL" in entry_fields: - entry_fields["URL"] = process_url(entry) + entry_fields["URL"] = process_url(entry) # ty: ignore[invalid-argument-type] if "DOI" in entry_fields: - entry_fields["URL"] = process_url(entry) - entry_fields["DOI"] = process_doi(entry) + entry_fields["URL"] = process_url(entry) # ty: ignore[invalid-argument-type] + entry_fields["DOI"] = process_doi(entry) # ty: ignore[invalid-argument-type] if "SUMMARY" in entry_fields: entry_fields["SUMMARY"] = process_summary(entry_fields["SUMMARY"]) @@ -268,9 +268,9 @@ def process_url(entry: Entry) -> str: """ if isinstance(entry, PublicationEntry) and entry.doi: return process_doi(entry) - if hasattr(entry, "url") and entry.url: # pyright: ignore[reportAttributeAccessIssue] - url = entry.url # pyright: ignore[reportAttributeAccessIssue] - return f"[{clean_url(url)}]({url})" + if hasattr(entry, "url") and entry.url: + url = entry.url + return f"[{clean_url(url)}]({url})" # ty: ignore[invalid-argument-type] raise RenderCVInternalError("URL is not provided for this entry.") diff --git a/src/rendercv/renderer/templater/model_processor.py b/src/rendercv/renderer/templater/model_processor.py index eeb9b911..9df5229c 100644 --- a/src/rendercv/renderer/templater/model_processor.py +++ b/src/rendercv/renderer/templater/model_processor.py @@ -36,15 +36,15 @@ def process_model( if file_type == "typst": string_processors.extend([markdown_to_typst]) - rendercv_model.cv.plain_name = rendercv_model.cv.name # pyright: ignore[reportAttributeAccessIssue] + rendercv_model.cv.plain_name = rendercv_model.cv.name # ty: ignore[unresolved-attribute] rendercv_model.cv.name = apply_string_processors( rendercv_model.cv.name, string_processors ) rendercv_model.cv.headline = apply_string_processors( rendercv_model.cv.headline, string_processors ) - rendercv_model.cv.connections = compute_connections(rendercv_model, file_type) # pyright: ignore[reportAttributeAccessIssue] - rendercv_model.cv.top_note = render_top_note_template( # pyright: ignore[reportAttributeAccessIssue] + rendercv_model.cv.connections = compute_connections(rendercv_model, file_type) # ty: ignore[unresolved-attribute] + rendercv_model.cv.top_note = render_top_note_template( # ty: ignore[unresolved-attribute] rendercv_model.design.templates.top_note, locale=rendercv_model.locale, current_date=rendercv_model.settings.current_date, @@ -53,7 +53,7 @@ def process_model( string_processors=string_processors, ) - rendercv_model.cv.footer = render_footer_template( # pyright: ignore[reportAttributeAccessIssue] + rendercv_model.cv.footer = render_footer_template( # ty: ignore[unresolved-attribute] rendercv_model.design.templates.footer, locale=rendercv_model.locale, current_date=rendercv_model.settings.current_date, diff --git a/src/rendercv/renderer/templater/string_processor.py b/src/rendercv/renderer/templater/string_processor.py index c784b3a6..5de4ee2c 100644 --- a/src/rendercv/renderer/templater/string_processor.py +++ b/src/rendercv/renderer/templater/string_processor.py @@ -57,9 +57,9 @@ def build_keyword_matcher_pattern(keywords: frozenset[str]) -> re.Pattern: message = "Keywords cannot be empty" raise RenderCVInternalError(message) - pattern = ( - "(" + "|".join(sorted(map(re.escape, keywords), key=len, reverse=True)) + ")" - ) + escaped: list[str] = [re.escape(k) for k in keywords] + escaped.sort(key=len, reverse=True) + pattern = "(" + "|".join(escaped) + ")" return re.compile(pattern) diff --git a/src/rendercv/schema/models/cv/section.py b/src/rendercv/schema/models/cv/section.py index fe43a431..20b811eb 100644 --- a/src/rendercv/schema/models/cv/section.py +++ b/src/rendercv/schema/models/cv/section.py @@ -37,7 +37,7 @@ available_entry_models: tuple[type[EntryModel], ...] = get_args(EntryModel.__val available_entry_type_names: tuple[str, ...] = tuple( [entry_type.__name__ for entry_type in available_entry_models] + ["TextEntry"] ) -type ListOfEntries = list[str] | reduce( # pyright: ignore[reportInvalidTypeForm] +type ListOfEntries = list[str] | reduce( # ty: ignore[invalid-type-form] or_, [list[entry_type] for entry_type in available_entry_models] ) @@ -112,8 +112,8 @@ def create_section_models( return pydantic.create_model( model_name, - entry_type=(Literal[entry_type_name], ...), - entries=(list[entry_type], ...), + entry_type=(Literal[entry_type_name], ...), # ty: ignore[invalid-type-form] + entries=(list[entry_type], ...), # ty: ignore[invalid-type-form] __base__=BaseRenderCVSection, ) diff --git a/src/rendercv/schema/models/cv/social_network.py b/src/rendercv/schema/models/cv/social_network.py index 11383533..b9023404 100644 --- a/src/rendercv/schema/models/cv/social_network.py +++ b/src/rendercv/schema/models/cv/social_network.py @@ -26,7 +26,7 @@ type SocialNetworkName = Literal[ "WhatsApp", "Leetcode", "X", - "Bluesky" + "Bluesky", ] available_social_networks = get_args(SocialNetworkName.__value__) url_dictionary: dict[SocialNetworkName, str] = { @@ -44,7 +44,7 @@ url_dictionary: dict[SocialNetworkName, str] = { "WhatsApp": "https://wa.me/", "Leetcode": "https://leetcode.com/u/", "X": "https://x.com/", - "Bluesky": "https://bsky.app/profile/" + "Bluesky": "https://bsky.app/profile/", } @@ -121,7 +121,8 @@ class SocialNetwork(BaseModelWithoutExtraKeys): if not re.fullmatch(bluesky_username_pattern, username): raise pydantic_core.PydanticCustomError( CustomPydanticErrorTypes.other.value, - "Bluesky username should be a valid handle with no '@' (e.g., 'username.bsky.social' or 'domain.com').", + "Bluesky username should be a valid handle with no '@' (e.g.," + " 'username.bsky.social' or 'domain.com').", ) case "WhatsApp": phone_validator = pydantic.TypeAdapter( diff --git a/src/rendercv/schema/models/design/built_in_design.py b/src/rendercv/schema/models/design/built_in_design.py index c3cfa254..ce3b12b2 100644 --- a/src/rendercv/schema/models/design/built_in_design.py +++ b/src/rendercv/schema/models/design/built_in_design.py @@ -40,7 +40,7 @@ def discover_other_themes() -> list[type[ClassicTheme]]: # Build discriminated union dynamically type BuiltInDesign = Annotated[ - ClassicTheme | reduce(or_, discover_other_themes()), # pyright: ignore[reportInvalidTypeForm] + ClassicTheme | reduce(or_, discover_other_themes()), # ty: ignore[invalid-type-form] pydantic.Field(discriminator="theme"), ] available_themes: list[str] = [ diff --git a/src/rendercv/schema/models/design/font_family.py b/src/rendercv/schema/models/design/font_family.py index 5ba60858..f490c3cb 100644 --- a/src/rendercv/schema/models/design/font_family.py +++ b/src/rendercv/schema/models/design/font_family.py @@ -50,4 +50,4 @@ available_font_families = sorted( ) -type FontFamily = SkipJsonSchema[str] | Literal[*tuple(available_font_families)] # pyright: ignore[reportInvalidTypeForm] +type FontFamily = SkipJsonSchema[str] | Literal[*tuple(available_font_families)] # ty: ignore[invalid-type-form] diff --git a/src/rendercv/schema/models/locale/locale.py b/src/rendercv/schema/models/locale/locale.py index 23c0d928..97444fae 100644 --- a/src/rendercv/schema/models/locale/locale.py +++ b/src/rendercv/schema/models/locale/locale.py @@ -40,7 +40,7 @@ def discover_other_locales() -> list[type[EnglishLocale]]: # Build discriminated union dynamically type Locale = Annotated[ - EnglishLocale | reduce(or_, discover_other_locales()), # pyright: ignore[reportInvalidTypeForm] + EnglishLocale | reduce(or_, discover_other_locales()), # ty: ignore[invalid-type-form] pydantic.Field(discriminator="language"), ] available_locales = [ diff --git a/src/rendercv/schema/override_dictionary.py b/src/rendercv/schema/override_dictionary.py index a32c5329..7dd38cae 100644 --- a/src/rendercv/schema/override_dictionary.py +++ b/src/rendercv/schema/override_dictionary.py @@ -1,29 +1,14 @@ import copy -from typing import overload from rendercv.exception import RenderCVUserError -@overload -def update_value_by_location( - dict_or_list: dict, +def update_value_by_location[T: dict | list]( + dict_or_list: T, key: str, value: str, full_key: str, -) -> dict: ... -@overload -def update_value_by_location( - dict_or_list: list, - key: str, - value: str, - full_key: str, -) -> list: ... -def update_value_by_location( - dict_or_list: dict | list, - key: str, - value: str, - full_key: str, -) -> dict | list: +) -> T: """Navigate nested structure via dotted path and update value. Why: @@ -89,21 +74,21 @@ def update_value_by_location( new_value = value else: new_value = update_value_by_location( - dict_or_list[first_key], # pyright: ignore[reportArgumentType, reportCallIssue] + dict_or_list[first_key], remaining_key, value, full_key=full_key, ) - dict_or_list[first_key] = new_value # pyright: ignore[reportArgumentType, reportCallIssue] + dict_or_list[first_key] = new_value return dict_or_list -def apply_overrides_to_dictionary( - dictionary: dict, +def apply_overrides_to_dictionary[T: dict]( + dictionary: T, overrides: dict[str, str], -) -> dict: +) -> T: """Apply multiple CLI overrides to dictionary. Why: diff --git a/src/rendercv/schema/pydantic_error_handling.py b/src/rendercv/schema/pydantic_error_handling.py index 885f49d6..39b5ce4c 100644 --- a/src/rendercv/schema/pydantic_error_handling.py +++ b/src/rendercv/schema/pydantic_error_handling.py @@ -1,9 +1,8 @@ import pathlib -from typing import cast +from typing import Any, cast import pydantic import pydantic_core -import ruamel.yaml from ruamel.yaml.comments import CommentedMap from rendercv.exception import RenderCVInternalError, RenderCVValidationError @@ -27,7 +26,8 @@ unwanted_locations = ( def parse_plain_pydantic_error( - plain_error: pydantic_core.ErrorDetails, user_input_as_commented_map: CommentedMap + plain_error: pydantic_core.ErrorDetails, + input_dictionary: CommentedMap | dict[str, Any], ) -> RenderCVValidationError: """Transform raw Pydantic error into user-friendly validation error with YAML coordinates. @@ -86,16 +86,20 @@ def parse_plain_pydantic_error( if not isinstance(plain_error["input"], dict | list) else "..." ), - yaml_location=get_coordinates_of_a_key_in_a_yaml_object( - user_input_as_commented_map, - location if plain_error["type"] != "missing" else location[:-1], + yaml_location=( + get_coordinates_of_a_key_in_a_yaml_object( + input_dictionary, + location if plain_error["type"] != "missing" else location[:-1], + ) + if isinstance(input_dictionary, CommentedMap) + else None ), ) def parse_validation_errors( exception: pydantic.ValidationError, - rendercv_dictionary_as_commented_map: CommentedMap, + input_dictionary: CommentedMap | dict[str, Any], ) -> list[RenderCVValidationError]: """Extract all validation errors from Pydantic exception with deduplication. @@ -117,9 +121,7 @@ def parse_validation_errors( for plain_error in all_plain_errors: all_final_errors.append( - parse_plain_pydantic_error( - plain_error, rendercv_dictionary_as_commented_map - ) + parse_plain_pydantic_error(plain_error, input_dictionary) ) if plain_error["type"] == CustomPydanticErrorTypes.entry_validation.value: @@ -129,9 +131,7 @@ def parse_validation_errors( loc = plain_cause_error["loc"][1:] # Omit `entries` location plain_cause_error["loc"] = plain_error["loc"] + loc all_final_errors.append( - parse_plain_pydantic_error( - plain_cause_error, rendercv_dictionary_as_commented_map - ) + parse_plain_pydantic_error(plain_cause_error, input_dictionary) ) # Remove duplicates from all_final_errors: @@ -147,7 +147,8 @@ def parse_validation_errors( def get_inner_yaml_object_from_its_key( - yaml_object: CommentedMap, location_key: str + yaml_object: CommentedMap, + location_key: str, ) -> tuple[CommentedMap, tuple[tuple[int, int], tuple[int, int]]]: """Navigate one level into YAML structure and extract coordinates. @@ -189,7 +190,7 @@ def get_inner_yaml_object_from_its_key( def get_coordinates_of_a_key_in_a_yaml_object( - yaml_object: ruamel.yaml.YAML, location: tuple[str, ...] + yaml_object: CommentedMap, location: tuple[str, ...] ) -> tuple[tuple[int, int], tuple[int, int]]: """Resolve dotted location path to exact YAML source coordinates. @@ -215,7 +216,7 @@ def get_coordinates_of_a_key_in_a_yaml_object( ((start_line, start_col), (end_line, end_col)) in 1-indexed coordinates. """ - current_yaml_object: ruamel.yaml.YAML = yaml_object + current_yaml_object = yaml_object coordinates = ((0, 0), (0, 0)) # start from the first key and move forward: for location_key in location: diff --git a/src/rendercv/schema/rendercv_model_builder.py b/src/rendercv/schema/rendercv_model_builder.py index 346e0dff..eca79c82 100644 --- a/src/rendercv/schema/rendercv_model_builder.py +++ b/src/rendercv/schema/rendercv_model_builder.py @@ -1,5 +1,5 @@ import pathlib -from typing import TypedDict, Unpack +from typing import Any, TypedDict, Unpack import pydantic from ruamel.yaml.comments import CommentedMap @@ -101,7 +101,7 @@ def build_rendercv_dictionary( def build_rendercv_model_from_commented_map( - commented_map: CommentedMap, + commented_map: CommentedMap | dict[str, Any], input_file_path: pathlib.Path | None = None, ) -> RenderCVModel: """Validate merged dictionary and build Pydantic model with error mapping. diff --git a/src/rendercv/schema/variant_pydantic_model_generator.py b/src/rendercv/schema/variant_pydantic_model_generator.py index 16bbb71e..7580e2d2 100644 --- a/src/rendercv/schema/variant_pydantic_model_generator.py +++ b/src/rendercv/schema/variant_pydantic_model_generator.py @@ -170,7 +170,7 @@ def create_discriminator_field_spec( Returns: Tuple of Literal type annotation and Field with default value. """ - field_annotation = Literal[discriminator_value] + field_annotation = Literal[discriminator_value] # ty: ignore[invalid-type-form] # Update description with new default value updated_description = update_description_with_new_default( diff --git a/tests/renderer/templater/test_connections.py b/tests/renderer/templater/test_connections.py index baf8ab4e..8a489102 100644 --- a/tests/renderer/templater/test_connections.py +++ b/tests/renderer/templater/test_connections.py @@ -187,7 +187,7 @@ class TestParseConnections: connections = parse_connections(model) - icons = [c.fontawesome_icon for c in connections] # type: ignore + icons = [c.fontawesome_icon for c in connections] assert icons == [ fontawesome_icons["location"], fontawesome_icons["email"], @@ -208,7 +208,7 @@ class TestParseConnections: connections = parse_connections(model) - icons = [c.fontawesome_icon for c in connections] # type: ignore + icons = [c.fontawesome_icon for c in connections] assert icons == [ fontawesome_icons["email"], fontawesome_icons["LinkedIn"], diff --git a/tests/renderer/templater/test_entry_templates_from_input.py b/tests/renderer/templater/test_entry_templates_from_input.py index b907b86f..fb25ea14 100644 --- a/tests/renderer/templater/test_entry_templates_from_input.py +++ b/tests/renderer/templater/test_entry_templates_from_input.py @@ -194,13 +194,8 @@ class TestRenderEntryTemplates: current_date=Date(2024, 1, 1), ) - assert ( - entry.main_column == "**Solo**" # pyright: ignore [reportAttributeAccessIssue] - ) - assert ( - entry.date_and_location_column # pyright: ignore [reportAttributeAccessIssue] - == "" - ) + assert entry.main_column == "**Solo**" # ty: ignore[unresolved-attribute] + assert entry.date_and_location_column == "" # ty: ignore[unresolved-attribute] def test_populates_highlights_and_date_placeholders(self): entry = NormalEntry( @@ -218,8 +213,8 @@ class TestRenderEntryTemplates: current_date=Date(2024, 1, 1), ) - assert entry.main_column == "**Project**\n- Alpha\n- Beta" # pyright: ignore [reportAttributeAccessIssue] - assert entry.date_and_location_column == "Remote\nMay 2023" # pyright: ignore [reportAttributeAccessIssue] + assert entry.main_column == "**Project**\n- Alpha\n- Beta" # ty: ignore[unresolved-attribute] + assert entry.date_and_location_column == "Remote\nMay 2023" # ty: ignore[unresolved-attribute] def test_formats_start_and_end_dates_in_custom_template(self): entry = NormalEntry( @@ -240,7 +235,7 @@ class TestRenderEntryTemplates: current_date=Date(2024, 1, 1), ) - assert entry.main_column == "Jan 2020 / Mar 2021 / / Jan 2020 – Mar 2021" # pyright: ignore [reportAttributeAccessIssue] + assert entry.main_column == "Jan 2020 / Mar 2021 / / Jan 2020 – Mar 2021" # ty: ignore[unresolved-attribute] def test_handles_authors_doi_and_date_placeholders(self): entry = PublicationEntry( @@ -263,7 +258,7 @@ class TestRenderEntryTemplates: ) assert ( - entry.main_column # pyright: ignore [reportAttributeAccessIssue] + entry.main_column # ty: ignore[unresolved-attribute] == "Alice, Bob | [10.1000/xyz123](https://doi.org/10.1000/xyz123) | Feb" " 2024" ) @@ -289,7 +284,7 @@ class TestRenderEntryTemplates: ) assert ( - entry.main_column # pyright: ignore [reportAttributeAccessIssue] + entry.main_column # ty: ignore[unresolved-attribute] == "Linked Item [example.com/page](https://example.com/page/)" ) diff --git a/tests/renderer/templater/test_model_processor.py b/tests/renderer/templater/test_model_processor.py index 0ebbf186..71bd3d28 100644 --- a/tests/renderer/templater/test_model_processor.py +++ b/tests/renderer/templater/test_model_processor.py @@ -68,7 +68,7 @@ class TestProcessFields: fn, seen = recorder entry = EntryWithInt(name="Test", count=42) - process_fields(entry, [fn]) # pyright: ignore[reportArgumentType] + process_fields(entry, [fn]) # ty: ignore[invalid-argument-type] assert "Test" in seen assert "42" in seen @@ -118,12 +118,12 @@ class TestProcessModel: assert result.cv.headline == "Software Engineer @" # Connections and last updated date are added to cv - assert result.cv.connections == [ # pyright: ignore[reportAttributeAccessIssue] + assert result.cv.connections == [ # ty: ignore[unresolved-attribute] "[jane@example.com](mailto:jane@example.com)", "[janedoe.dev](https://janedoe.dev/)", ] assert ( - result.cv.top_note == "*Last updated in Feb 2024*" # pyright: ignore[reportAttributeAccessIssue] + result.cv.top_note == "*Last updated in Feb 2024*" # ty: ignore[unresolved-attribute] ) entry = result.cv.rendercv_sections[0].entries[0] @@ -157,13 +157,13 @@ class TestProcessModel: entry.date_and_location_column == "#strong[Remote]\nJan 2022 – Feb 2023" ) # Connections rendered as Typst links with icons by default - assert result.cv.connections[0].startswith("#link(") # pyright: ignore[reportAttributeAccessIssue] - assert "#connection-with-icon" in result.cv.connections[0] # pyright: ignore[reportAttributeAccessIssue] + assert result.cv.connections[0].startswith("#link(") # ty: ignore[unresolved-attribute] + assert "#connection-with-icon" in result.cv.connections[0] # ty: ignore[unresolved-attribute] else: assert "- Improved Python performance" in entry.main_column assert entry.date_and_location_column == "Remote\nJan 2022 – Feb 2023" - assert result.cv.connections[0].startswith("#link(") # pyright: ignore[reportAttributeAccessIssue] - assert "jane@example.com" in result.cv.connections[0] # pyright: ignore[reportAttributeAccessIssue] + assert result.cv.connections[0].startswith("#link(") # ty: ignore[unresolved-attribute] + assert "jane@example.com" in result.cv.connections[0] # ty: ignore[unresolved-attribute] def test_handles_cv_with_no_sections(self): cv_data = { diff --git a/tests/schema/models/cv/conftest.py b/tests/schema/models/cv/conftest.py index 1b4b18b7..ff9896bf 100644 --- a/tests/schema/models/cv/conftest.py +++ b/tests/schema/models/cv/conftest.py @@ -1,4 +1,5 @@ import copy +from typing import Any import pytest @@ -70,49 +71,49 @@ reversed_numbered_entry_dictionary = { @pytest.fixture -def publication_entry() -> dict[str, str | list[str]]: +def publication_entry() -> dict[str, Any]: """Return a sample publication entry.""" return copy.deepcopy(publication_entry_dictionary) @pytest.fixture -def experience_entry() -> dict[str, str]: +def experience_entry() -> dict[str, Any]: """Return a sample experience entry.""" return copy.deepcopy(experience_entry_dictionary) @pytest.fixture -def education_entry() -> dict[str, str]: +def education_entry() -> dict[str, Any]: """Return a sample education entry.""" return copy.deepcopy(education_entry_dictionary) @pytest.fixture -def normal_entry() -> dict[str, str]: +def normal_entry() -> dict[str, Any]: """Return a sample normal entry.""" return copy.deepcopy(normal_entry_dictionary) @pytest.fixture -def one_line_entry() -> dict[str, str]: +def one_line_entry() -> dict[str, Any]: """Return a sample one line entry.""" return copy.deepcopy(one_line_entry_dictionary) @pytest.fixture -def bullet_entry() -> dict[str, str]: +def bullet_entry() -> dict[str, Any]: """Return a sample bullet entry.""" return copy.deepcopy(bullet_entry_dictionary) @pytest.fixture -def numbered_entry() -> dict[str, str]: +def numbered_entry() -> dict[str, Any]: """Return a sample numbered entry.""" return copy.deepcopy(numbered_entry_dictionary) @pytest.fixture -def reversed_numbered_entry() -> dict[str, str]: +def reversed_numbered_entry() -> dict[str, Any]: """Return a sample reversed numbered entry.""" return copy.deepcopy(reversed_numbered_entry_dictionary) diff --git a/tests/schema/models/cv/test_cv.py b/tests/schema/models/cv/test_cv.py index 6dac3f47..8d9ca6b4 100644 --- a/tests/schema/models/cv/test_cv.py +++ b/tests/schema/models/cv/test_cv.py @@ -1,3 +1,5 @@ +from typing import Any + import pydantic import pytest @@ -25,7 +27,7 @@ class TestCv: "sections": sections, } - cv = Cv(**input) + cv = Cv.model_validate(input) assert len(cv.rendercv_sections) == len(available_entry_type_names) for section in cv.rendercv_sections: @@ -45,10 +47,10 @@ class TestCv: } with pytest.raises(pydantic.ValidationError): - Cv(**input) + Cv.model_validate(input) def test_rejects_invalid_entries(self): - input = {"name": "John Doe", "sections": {}} + input: dict[str, Any] = {"name": "John Doe", "sections": {}} input["sections"]["section_title"] = [ { "this": "is", @@ -58,16 +60,16 @@ class TestCv: ] with pytest.raises(pydantic.ValidationError): - Cv(**input) + Cv.model_validate(input) def test_rejects_section_without_list(self): - input = {"name": "John Doe", "sections": {}} + input: dict[str, Any] = {"name": "John Doe", "sections": {}} input["sections"]["section_title"] = { "this section": "does not have a list of entries but a single entry." } with pytest.raises(pydantic.ValidationError): - Cv(**input) + Cv.model_validate(input) def test_phone_serialization(self): input_data = {"name": "John Doe", "phone": "+905419999999"} diff --git a/tests/schema/models/cv/test_social_network.py b/tests/schema/models/cv/test_social_network.py index 4eaf831b..ab144603 100644 --- a/tests/schema/models/cv/test_social_network.py +++ b/tests/schema/models/cv/test_social_network.py @@ -86,7 +86,7 @@ class TestSocialNetwork: ( "Bluesky", "myusername.bsky.social", - "https://bsky.app/profile/myusername.bsky.social" + "https://bsky.app/profile/myusername.bsky.social", ), ], ) diff --git a/tests/schema/test_override_dictionary.py b/tests/schema/test_override_dictionary.py index 99d9782b..80a685d1 100644 --- a/tests/schema/test_override_dictionary.py +++ b/tests/schema/test_override_dictionary.py @@ -1,3 +1,5 @@ +from typing import Any + import pytest from rendercv.exception import RenderCVUserError @@ -136,7 +138,7 @@ class TestUpdateValueByLocation: assert original == {"name": "Jane"} def test_deeply_nested_structure(self): - initial = { + initial: dict[str, Any] = { "cv": { "sections": { "education": [ @@ -230,7 +232,7 @@ class TestApplyOverridesToDictionary: assert result is not original def test_complex_cv_scenario(self): - initial = { + initial: dict[str, Any] = { "cv": { "name": "John Doe", "sections": { diff --git a/tests/schema/test_variant_pydantic_model_generator.py b/tests/schema/test_variant_pydantic_model_generator.py index ddd278fe..84845f58 100644 --- a/tests/schema/test_variant_pydantic_model_generator.py +++ b/tests/schema/test_variant_pydantic_model_generator.py @@ -289,7 +289,7 @@ class TestCreateNestedFieldSpec: # Check that a variant class was created with default_factory assert field.default_factory is not None - assert issubclass(field.default_factory, pydantic.BaseModel) # pyright: ignore[reportArgumentType] + assert issubclass(field.default_factory, pydantic.BaseModel) # ty: ignore[invalid-argument-type] # Instantiate to check default values instance = field.default_factory() @@ -311,7 +311,7 @@ class TestCreateNestedFieldSpec: # Check that a variant class was created with default_factory assert field.default_factory is not None - assert issubclass(field.default_factory, pydantic.BaseModel) # pyright: ignore[reportArgumentType] + assert issubclass(field.default_factory, pydantic.BaseModel) # ty: ignore[invalid-argument-type] # Instantiate to check default values instance = field.default_factory() @@ -352,7 +352,7 @@ class TestCreateNestedFieldSpec: # Check that a variant class was created with default_factory assert field.default_factory is not None - assert issubclass(field.default_factory, pydantic.BaseModel) # pyright: ignore[reportArgumentType] + assert issubclass(field.default_factory, pydantic.BaseModel) # ty: ignore[invalid-argument-type] # Instantiate to check default values instance = field.default_factory() @@ -754,7 +754,7 @@ class TestCreateVariantPydanticModel: pydantic.Field(default=old_default, description=base_description), ), } - Base = pydantic.create_model("Base", **base_fields) + Base = pydantic.create_model("Base", **base_fields) # ty: ignore[no-matching-overload] VariantClass = create_variant_pydantic_model( variant_name="custom", @@ -1045,8 +1045,8 @@ class TestCreateNestedModelVariantModel: # The variant should be created without errors instance = variant_class() - assert instance.x == 100 # pyright: ignore[reportAttributeAccessIssue] - assert instance.y == 2 # pyright: ignore[reportAttributeAccessIssue] + assert instance.x == 100 # ty: ignore[unresolved-attribute] + assert instance.y == 2 # ty: ignore[unresolved-attribute] # nonexistent_field should not be in the instance assert not hasattr(instance, "nonexistent_field") @@ -1065,5 +1065,5 @@ class TestCreateNestedModelVariantModel: variant_class = create_nested_model_variant_model(ModelWithPlainDict, updates) instance = variant_class() - assert instance.metadata == {"new_key": "new_value"} # pyright: ignore[reportAttributeAccessIssue] - assert instance.count == 10 # pyright: ignore[reportAttributeAccessIssue] + assert instance.metadata == {"new_key": "new_value"} # ty: ignore[unresolved-attribute] + assert instance.count == 10 # ty: ignore[unresolved-attribute] diff --git a/uv.lock b/uv.lock index 0e2609fa..dc54a2b2 100644 --- a/uv.lock +++ b/uv.lock @@ -979,19 +979,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/96/fd59c1532891762ea4815e73956c532053d5e26d56969e1e5d1e4ca4b207/pymupdf-1.26.5-cp39-abi3-win_amd64.whl", hash = "sha256:39a6fb58182b27b51ea8150a0cd2e4ee7e0cf71e9d6723978f28699b42ee61ae", size = 18747258, upload-time = "2025-10-10T14:01:37.346Z" }, ] -[[package]] -name = "pyright" -version = "1.1.407" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nodeenv" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a6/1b/0aa08ee42948b61745ac5b5b5ccaec4669e8884b53d31c8ec20b2fcd6b6f/pyright-1.1.407.tar.gz", hash = "sha256:099674dba5c10489832d4a4b2d302636152a9a42d317986c38474c76fe562262", size = 4122872, upload-time = "2025-10-24T23:17:15.145Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/93/b69052907d032b00c40cb656d21438ec00b3a471733de137a3f65a49a0a0/pyright-1.1.407-py3-none-any.whl", hash = "sha256:6dd419f54fcc13f03b52285796d65e639786373f433e243f8b94cf93a7444d21", size = 5997008, upload-time = "2025-10-24T23:17:13.159Z" }, -] - [[package]] name = "pytest" version = "9.0.2" @@ -1152,11 +1139,11 @@ create-executable = [ dev = [ { name = "black" }, { name = "pre-commit" }, - { name = "pyright" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-xdist" }, { name = "ruff" }, + { name = "ty" }, ] docs = [ { name = "markdown-callouts" }, @@ -1193,11 +1180,11 @@ create-executable = [{ name = "pyinstaller", specifier = ">=6.17.0" }] dev = [ { name = "black", specifier = ">=25.12.0" }, { name = "pre-commit", specifier = ">=4.5.0" }, - { name = "pyright", specifier = ">=1.1.407" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-cov", specifier = ">=7.0.0" }, { name = "pytest-xdist", specifier = ">=3.8.0" }, - { name = "ruff", specifier = ">=0.14.8" }, + { name = "ruff", specifier = ">=0.14.10" }, + { name = "ty", specifier = ">=0.0.5" }, ] docs = [ { name = "markdown-callouts", specifier = ">=0.4.0" }, @@ -1302,28 +1289,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.8" +version = "0.14.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ed/d9/f7a0c4b3a2bf2556cd5d99b05372c29980249ef71e8e32669ba77428c82c/ruff-0.14.8.tar.gz", hash = "sha256:774ed0dd87d6ce925e3b8496feb3a00ac564bea52b9feb551ecd17e0a23d1eed", size = 5765385, upload-time = "2025-12-04T15:06:17.669Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/b8/9537b52010134b1d2b72870cc3f92d5fb759394094741b09ceccae183fbe/ruff-0.14.8-py3-none-linux_armv6l.whl", hash = "sha256:ec071e9c82eca417f6111fd39f7043acb53cd3fde9b1f95bbed745962e345afb", size = 13441540, upload-time = "2025-12-04T15:06:14.896Z" }, - { url = "https://files.pythonhosted.org/packages/24/00/99031684efb025829713682012b6dd37279b1f695ed1b01725f85fd94b38/ruff-0.14.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8cdb162a7159f4ca36ce980a18c43d8f036966e7f73f866ac8f493b75e0c27e9", size = 13669384, upload-time = "2025-12-04T15:06:51.809Z" }, - { url = "https://files.pythonhosted.org/packages/72/64/3eb5949169fc19c50c04f28ece2c189d3b6edd57e5b533649dae6ca484fe/ruff-0.14.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e2fcbefe91f9fad0916850edf0854530c15bd1926b6b779de47e9ab619ea38f", size = 12806917, upload-time = "2025-12-04T15:06:08.925Z" }, - { url = "https://files.pythonhosted.org/packages/c4/08/5250babb0b1b11910f470370ec0cbc67470231f7cdc033cee57d4976f941/ruff-0.14.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d70721066a296f45786ec31916dc287b44040f553da21564de0ab4d45a869b", size = 13256112, upload-time = "2025-12-04T15:06:23.498Z" }, - { url = "https://files.pythonhosted.org/packages/78/4c/6c588e97a8e8c2d4b522c31a579e1df2b4d003eddfbe23d1f262b1a431ff/ruff-0.14.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c87e09b3cd9d126fc67a9ecd3b5b1d3ded2b9c7fce3f16e315346b9d05cfb52", size = 13227559, upload-time = "2025-12-04T15:06:33.432Z" }, - { url = "https://files.pythonhosted.org/packages/23/ce/5f78cea13eda8eceac71b5f6fa6e9223df9b87bb2c1891c166d1f0dce9f1/ruff-0.14.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d62cb310c4fbcb9ee4ac023fe17f984ae1e12b8a4a02e3d21489f9a2a5f730c", size = 13896379, upload-time = "2025-12-04T15:06:02.687Z" }, - { url = "https://files.pythonhosted.org/packages/cf/79/13de4517c4dadce9218a20035b21212a4c180e009507731f0d3b3f5df85a/ruff-0.14.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1af35c2d62633d4da0521178e8a2641c636d2a7153da0bac1b30cfd4ccd91344", size = 15372786, upload-time = "2025-12-04T15:06:29.828Z" }, - { url = "https://files.pythonhosted.org/packages/00/06/33df72b3bb42be8a1c3815fd4fae83fa2945fc725a25d87ba3e42d1cc108/ruff-0.14.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25add4575ffecc53d60eed3f24b1e934493631b48ebbc6ebaf9d8517924aca4b", size = 14990029, upload-time = "2025-12-04T15:06:36.812Z" }, - { url = "https://files.pythonhosted.org/packages/64/61/0f34927bd90925880394de0e081ce1afab66d7b3525336f5771dcf0cb46c/ruff-0.14.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c943d847b7f02f7db4201a0600ea7d244d8a404fbb639b439e987edcf2baf9a", size = 14407037, upload-time = "2025-12-04T15:06:39.979Z" }, - { url = "https://files.pythonhosted.org/packages/96/bc/058fe0aefc0fbf0d19614cb6d1a3e2c048f7dc77ca64957f33b12cfdc5ef/ruff-0.14.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6e8bf7b4f627548daa1b69283dac5a296bfe9ce856703b03130732e20ddfe2", size = 14102390, upload-time = "2025-12-04T15:06:46.372Z" }, - { url = "https://files.pythonhosted.org/packages/af/a4/e4f77b02b804546f4c17e8b37a524c27012dd6ff05855d2243b49a7d3cb9/ruff-0.14.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7aaf2974f378e6b01d1e257c6948207aec6a9b5ba53fab23d0182efb887a0e4a", size = 14230793, upload-time = "2025-12-04T15:06:20.497Z" }, - { url = "https://files.pythonhosted.org/packages/3f/52/bb8c02373f79552e8d087cedaffad76b8892033d2876c2498a2582f09dcf/ruff-0.14.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e5758ca513c43ad8a4ef13f0f081f80f08008f410790f3611a21a92421ab045b", size = 13160039, upload-time = "2025-12-04T15:06:49.06Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ad/b69d6962e477842e25c0b11622548df746290cc6d76f9e0f4ed7456c2c31/ruff-0.14.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f74f7ba163b6e85a8d81a590363bf71618847e5078d90827749bfda1d88c9cdf", size = 13205158, upload-time = "2025-12-04T15:06:54.574Z" }, - { url = "https://files.pythonhosted.org/packages/06/63/54f23da1315c0b3dfc1bc03fbc34e10378918a20c0b0f086418734e57e74/ruff-0.14.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eed28f6fafcc9591994c42254f5a5c5ca40e69a30721d2ab18bb0bb3baac3ab6", size = 13469550, upload-time = "2025-12-04T15:05:59.209Z" }, - { url = "https://files.pythonhosted.org/packages/70/7d/a4d7b1961e4903bc37fffb7ddcfaa7beb250f67d97cfd1ee1d5cddb1ec90/ruff-0.14.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:21d48fa744c9d1cb8d71eb0a740c4dd02751a5de9db9a730a8ef75ca34cf138e", size = 14211332, upload-time = "2025-12-04T15:06:06.027Z" }, - { url = "https://files.pythonhosted.org/packages/5d/93/2a5063341fa17054e5c86582136e9895db773e3c2ffb770dde50a09f35f0/ruff-0.14.8-py3-none-win32.whl", hash = "sha256:15f04cb45c051159baebb0f0037f404f1dc2f15a927418f29730f411a79bc4e7", size = 13151890, upload-time = "2025-12-04T15:06:11.668Z" }, - { url = "https://files.pythonhosted.org/packages/02/1c/65c61a0859c0add13a3e1cbb6024b42de587456a43006ca2d4fd3d1618fe/ruff-0.14.8-py3-none-win_amd64.whl", hash = "sha256:9eeb0b24242b5bbff3011409a739929f497f3fb5fe3b5698aba5e77e8c833097", size = 14537826, upload-time = "2025-12-04T15:06:26.409Z" }, - { url = "https://files.pythonhosted.org/packages/6d/63/8b41cea3afd7f58eb64ac9251668ee0073789a3bc9ac6f816c8c6fef986d/ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99", size = 13634522, upload-time = "2025-12-04T15:06:43.212Z" }, + { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" }, + { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" }, + { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" }, + { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" }, + { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" }, + { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" }, + { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" }, + { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" }, + { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" }, + { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" }, + { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" }, + { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, ] [[package]] @@ -1374,6 +1361,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/d5/141f53d7c1eb2a80e6d3e9a390228c3222c27705cbe7f048d3623053f3ca/termcolor-3.2.0-py3-none-any.whl", hash = "sha256:a10343879eba4da819353c55cb8049b0933890c2ebf9ad5d3ecd2bb32ea96ea6", size = 7698, upload-time = "2025-10-25T19:11:41.536Z" }, ] +[[package]] +name = "ty" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/db/6299d478000f4f1c6f9bf2af749359381610ffc4cbe6713b66e436ecf6e7/ty-0.0.5.tar.gz", hash = "sha256:983da6330773ff71e2b249810a19c689f9a0372f6e21bbf7cde37839d05b4346", size = 4806218, upload-time = "2025-12-20T21:19:17.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/98/c1f61ba378b4191e641bb36c07b7fcc70ff844d61be7a4bf2fea7472b4a9/ty-0.0.5-py3-none-linux_armv6l.whl", hash = "sha256:1594cd9bb68015eb2f5a3c68a040860f3c9306dc6667d7a0e5f4df9967b460e2", size = 9785554, upload-time = "2025-12-20T21:19:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f9/b37b77c03396bd779c1397dae4279b7ad79315e005b3412feed8812a4256/ty-0.0.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7c0140ba980233d28699d9ddfe8f43d0b3535d6a3bbff9935df625a78332a3cf", size = 9603995, upload-time = "2025-12-20T21:19:15.256Z" }, + { url = "https://files.pythonhosted.org/packages/7d/70/4e75c11903b0e986c0203040472627cb61d6a709e1797fb08cdf9d565743/ty-0.0.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:15de414712cde92048ae4b1a77c4dc22920bd23653fe42acaf73028bad88f6b9", size = 9145815, upload-time = "2025-12-20T21:19:36.481Z" }, + { url = "https://files.pythonhosted.org/packages/89/05/93983dfcf871a41dfe58e5511d28e6aa332a1f826cc67333f77ae41a2f8a/ty-0.0.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:438aa51ad6c5fae64191f8d58876266e26f9250cf09f6624b6af47a22fa88618", size = 9619849, upload-time = "2025-12-20T21:19:19.084Z" }, + { url = "https://files.pythonhosted.org/packages/82/b6/896ab3aad59f846823f202e94be6016fb3f72434d999d2ae9bd0f28b3af9/ty-0.0.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b3d373fd96af1564380caf153600481c676f5002ee76ba8a7c3508cdff82ee0", size = 9606611, upload-time = "2025-12-20T21:19:24.583Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ae/098e33fc92330285ed843e2750127e896140c4ebd2d73df7732ea496f588/ty-0.0.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8453692503212ad316cf8b99efbe85a91e5f63769c43be5345e435a1b16cba5a", size = 10029523, upload-time = "2025-12-20T21:19:07.055Z" }, + { url = "https://files.pythonhosted.org/packages/04/5a/f4b4c33758b9295e9aca0de9645deca0f4addd21d38847228723a6e780fc/ty-0.0.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2e4c454139473abbd529767b0df7a795ed828f780aef8d0d4b144558c0dc4446", size = 10870892, upload-time = "2025-12-20T21:19:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c5/4e3e7e88389365aa1e631c99378711cf0c9d35a67478cb4720584314cf44/ty-0.0.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:426d4f3b82475b1ec75f3cc9ee5a667c8a4ae8441a09fcd8e823a53b706d00c7", size = 10599291, upload-time = "2025-12-20T21:19:26.557Z" }, + { url = "https://files.pythonhosted.org/packages/c1/5d/138f859ea87bd95e17b9818e386ae25a910e46521c41d516bf230ed83ffc/ty-0.0.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5710817b67c6b2e4c0224e4f319b7decdff550886e9020f6d46aa1ce8f89a609", size = 10413515, upload-time = "2025-12-20T21:19:11.094Z" }, + { url = "https://files.pythonhosted.org/packages/27/21/1cbcd0d3b1182172f099e88218137943e0970603492fb10c7c9342369d9a/ty-0.0.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23c55ef08882c7c5ced1ccb90b4eeefa97f690aea254f58ac0987896c590f76", size = 10144992, upload-time = "2025-12-20T21:19:13.225Z" }, + { url = "https://files.pythonhosted.org/packages/ad/30/fdac06a5470c09ad2659a0806497b71f338b395d59e92611f71b623d05a0/ty-0.0.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b9e4c1a28a23b14cf8f4f793f4da396939f16c30bfa7323477c8cc234e352ac4", size = 9606408, upload-time = "2025-12-20T21:19:09.212Z" }, + { url = "https://files.pythonhosted.org/packages/09/93/e99dcd7f53295192d03efd9cbcec089a916f49cad4935c0160ea9adbd53d/ty-0.0.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4e9ebb61529b9745af662e37c37a01ad743cdd2c95f0d1421705672874d806cd", size = 9630040, upload-time = "2025-12-20T21:19:38.165Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f8/6d1e87186e4c35eb64f28000c1df8fd5f73167ce126c5e3dd21fd1204a23/ty-0.0.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5eb191a8e332f50f56dfe45391bdd7d43dd4ef6e60884710fd7ce84c5d8c1eb5", size = 9754016, upload-time = "2025-12-20T21:19:32.79Z" }, + { url = "https://files.pythonhosted.org/packages/28/e6/20f989342cb3115852dda404f1d89a10a3ce93f14f42b23f095a3d1a00c9/ty-0.0.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:92ed7451a1e82ee134a2c24ca43b74dd31e946dff2b08e5c34473e6b051de542", size = 10252877, upload-time = "2025-12-20T21:19:20.787Z" }, + { url = "https://files.pythonhosted.org/packages/57/9d/fc66fa557443233dfad9ae197ff3deb70ae0efcfb71d11b30ef62f5cdcc3/ty-0.0.5-py3-none-win32.whl", hash = "sha256:71f6707e4c1c010c158029a688a498220f28bb22fdb6707e5c20e09f11a5e4f2", size = 9212640, upload-time = "2025-12-20T21:19:30.817Z" }, + { url = "https://files.pythonhosted.org/packages/68/b6/05c35f6dea29122e54af0e9f8dfedd0a100c721affc8cc801ebe2bc2ed13/ty-0.0.5-py3-none-win_amd64.whl", hash = "sha256:2b8b754a0d7191e94acdf0c322747fec34371a4d0669f5b4e89549aef28814ae", size = 10034701, upload-time = "2025-12-20T21:19:28.311Z" }, + { url = "https://files.pythonhosted.org/packages/df/ca/4201ed5cb2af73912663d0c6ded927c28c28b3c921c9348aa8d2cfef4853/ty-0.0.5-py3-none-win_arm64.whl", hash = "sha256:83bea5a5296caac20d52b790ded2b830a7ff91c4ed9f36730fe1f393ceed6654", size = 9566474, upload-time = "2025-12-20T21:19:22.518Z" }, +] + [[package]] name = "typer" version = "0.20.0"