diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dc2c374..c9c3839 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: "check-json" - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.7.4 + rev: v0.8.0 hooks: - id: ruff args: [ "--fix" ] diff --git a/pyproject.toml b/pyproject.toml index b0d78b9..37be7f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,9 +14,10 @@ dependencies = [ [project.optional-dependencies] dev = [ + "fluent-runtime==0.4.0", "isort==5.13.2", "pre-commit==4.0.1", - "ruff==0.7.4", + "ruff==0.8.0", "mypy==1.13.0", "typing-extensions==4.12.2", ] @@ -94,7 +95,7 @@ exclude = [ select = ["ALL"] ignore = [ "A003", - "ANN002", "ANN003", "ANN101", "ANN102", "ANN401", + "ANN002", "ANN003", "ANN401", "C901", "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D203", "D205", "D212", "ERA001", diff --git a/src/ftl_extract/__init__.py b/src/ftl_extract/__init__.py index fc4760a..102d77f 100644 --- a/src/ftl_extract/__init__.py +++ b/src/ftl_extract/__init__.py @@ -1,4 +1,4 @@ from .__version__ import __version__ from .code_extractor import extract_fluent_keys -__all__ = ("extract_fluent_keys", "__version__") +__all__ = ("__version__", "extract_fluent_keys") diff --git a/src/ftl_extract/code_extractor.py b/src/ftl_extract/code_extractor.py index f8bffb7..a91d290 100644 --- a/src/ftl_extract/code_extractor.py +++ b/src/ftl_extract/code_extractor.py @@ -18,7 +18,7 @@ from ftl_extract.matcher import FluentKey -def find_py_files(path: Path) -> Iterator[Path]: +def find_py_files(*, path: Path) -> Iterator[Path]: """ First step: find all .py files in given path. @@ -31,6 +31,7 @@ def find_py_files(path: Path) -> Iterator[Path]: def parse_file( + *, path: Path, i18n_keys: str | Iterable[str], ignore_attributes: str | Iterable[str], @@ -66,7 +67,7 @@ def parse_file( return matcher.fluent_keys -def post_process_fluent_keys(fluent_keys: dict[str, FluentKey], default_ftl_file: Path) -> None: +def post_process_fluent_keys(*, fluent_keys: dict[str, FluentKey], default_ftl_file: Path) -> None: """ Third step: post-process parsed `FluentKey`. @@ -84,6 +85,7 @@ def post_process_fluent_keys(fluent_keys: dict[str, FluentKey], default_ftl_file def find_conflicts( + *, current_fluent_keys: dict[str, FluentKey], new_fluent_keys: dict[str, FluentKey], ) -> None: @@ -117,6 +119,7 @@ def find_conflicts( def extract_fluent_keys( + *, path: Path, i18n_keys: str | Iterable[str], ignore_attributes: str | Iterable[str], @@ -143,7 +146,7 @@ def extract_fluent_keys( """ fluent_keys: dict[str, FluentKey] = {} - for file in find_py_files(path): + for file in find_py_files(path=path): keys = parse_file( path=file, i18n_keys=i18n_keys, @@ -151,14 +154,14 @@ def extract_fluent_keys( ignore_kwargs=ignore_kwargs, default_ftl_file=default_ftl_file, ) - post_process_fluent_keys(keys, default_ftl_file) - find_conflicts(fluent_keys, keys) + post_process_fluent_keys(fluent_keys=keys, default_ftl_file=default_ftl_file) + find_conflicts(current_fluent_keys=fluent_keys, new_fluent_keys=keys) fluent_keys.update(keys) return fluent_keys -def sort_fluent_keys_by_path(fluent_keys: dict[str, FluentKey]) -> dict[Path, list[FluentKey]]: +def sort_fluent_keys_by_path(*, fluent_keys: dict[str, FluentKey]) -> dict[Path, list[FluentKey]]: """ Sort `FluentKey`s by their paths. diff --git a/src/ftl_extract/const.py b/src/ftl_extract/const.py index ac03ee8..945bcbf 100644 --- a/src/ftl_extract/const.py +++ b/src/ftl_extract/const.py @@ -4,12 +4,12 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Final, Literal + from typing import Final -I18N_LITERAL: Final[Literal["i18n"]] = "i18n" -GET_LITERAL: Final[Literal["get"]] = "get" -PATH_LITERAL: Final[Literal["_path"]] = "_path" +I18N_LITERAL: Final = "i18n" +GET_LITERAL: Final = "get" +PATH_LITERAL: Final = "_path" IGNORE_ATTRIBUTES: Final[frozenset[str]] = frozenset( {"set_locale", "use_locale", "use_context", "set_context"}, ) diff --git a/src/ftl_extract/exceptions.py b/src/ftl_extract/exceptions.py index a12684b..d7fbead 100644 --- a/src/ftl_extract/exceptions.py +++ b/src/ftl_extract/exceptions.py @@ -35,3 +35,19 @@ def __init__( f"Translation {key!r} already exists with different elements: " f"{self.current_translation} != {self.new_translation}", ) + + +class FTLExtractorCantFindReferenceError(FTLExtractorError): + def __init__(self, key: str, key_path: Path, reference_key: str) -> None: + self.key = key + self.key_path = key_path + self.reference_key = reference_key + super().__init__(f"Can't find reference {reference_key!r} for key {key!r} at {key_path}") + + +class FTLExtractorCantFindTermError(FTLExtractorError): + def __init__(self, key: str, key_path: Path, term_key: str) -> None: + self.key = key + self.key_path = key_path + self.term_key = term_key + super().__init__(f"Can't find term {term_key!r} for key {key!r} at {key_path}") diff --git a/src/ftl_extract/ftl_extractor.py b/src/ftl_extract/ftl_extractor.py index e0c95ae..ece21b6 100644 --- a/src/ftl_extract/ftl_extractor.py +++ b/src/ftl_extract/ftl_extractor.py @@ -1,5 +1,6 @@ from __future__ import annotations +from copy import deepcopy from typing import TYPE_CHECKING from click import echo @@ -22,6 +23,7 @@ def extract( + *, code_path: Path, output_path: Path, language: Iterable[str], @@ -49,11 +51,17 @@ def extract( ) for lang in language: - # Import fluent keys from existing FTL files - stored_fluent_keys, leave_as_is = import_ftl_from_dir(output_path, lang) + # Import fluent keys and terms from existing FTL files + stored_fluent_keys, stored_terms, leave_as_is = import_ftl_from_dir( + path=output_path, + locale=lang, + ) for fluent_key in stored_fluent_keys.values(): fluent_key.path = fluent_key.path.relative_to(output_path / lang) + for term in stored_terms.values(): + term.path = term.path.relative_to(output_path / lang) + keys_to_comment: dict[str, FluentKey] = {} keys_to_add: dict[str, FluentKey] = {} @@ -72,12 +80,24 @@ def extract( stored_fluent_keys[key].code_path = fluent_key.code_path # Second step: find keys that have different kwargs + # Make copy of in_code_fluent_keys and stored_fluent_keys to check references + in_code_fluent_keys_copy = deepcopy(in_code_fluent_keys) + stored_fluent_keys_copy = deepcopy(stored_fluent_keys) + for key, fluent_key in in_code_fluent_keys.items(): if key not in stored_fluent_keys: continue - fluent_key_placeable_set = extract_kwargs(fluent_key) - stored_fluent_key_placeable_set = extract_kwargs(stored_fluent_keys[key]) + fluent_key_placeable_set = extract_kwargs( + key=fluent_key, + terms=stored_terms, + all_fluent_keys=in_code_fluent_keys_copy, + ) + stored_fluent_key_placeable_set = extract_kwargs( + key=stored_fluent_keys[key], + terms=stored_terms, + all_fluent_keys=stored_fluent_keys_copy, + ) if fluent_key_placeable_set != stored_fluent_key_placeable_set: keys_to_comment[key] = stored_fluent_keys.pop(key) @@ -88,20 +108,23 @@ def extract( keys_to_comment[key] = stored_fluent_keys.pop(key) for fluent_key in keys_to_comment.values(): - comment_ftl_key(fluent_key, serializer) + comment_ftl_key(key=fluent_key, serializer=serializer) # Comment Junk elements if needed if comment_junks is True: for fluent_key in leave_as_is: if isinstance(fluent_key.translation, fl_ast.Junk): - comment_ftl_key(fluent_key, serializer) + comment_ftl_key(key=fluent_key, serializer=serializer) + + sorted_fluent_keys = sort_fluent_keys_by_path(fluent_keys=stored_fluent_keys) - sorted_fluent_keys = sort_fluent_keys_by_path(stored_fluent_keys) + for path, keys in sort_fluent_keys_by_path(fluent_keys=keys_to_add).items(): + sorted_fluent_keys.setdefault(path, []).extend(keys) - for path, keys in sort_fluent_keys_by_path(keys_to_add).items(): + for path, keys in sort_fluent_keys_by_path(fluent_keys=keys_to_comment).items(): sorted_fluent_keys.setdefault(path, []).extend(keys) - for path, keys in sort_fluent_keys_by_path(keys_to_comment).items(): + for path, keys in sort_fluent_keys_by_path(fluent_keys=stored_terms).items(): sorted_fluent_keys.setdefault(path, []).extend(keys) leave_as_is_with_path: dict[Path, list[FluentKey]] = {} @@ -114,7 +137,7 @@ def extract( for path, keys in sorted_fluent_keys.items(): ftl, _ = generate_ftl( - keys, + fluent_keys=keys, serializer=serializer, leave_as_is=leave_as_is_with_path.get(path, []), ) diff --git a/src/ftl_extract/ftl_importer.py b/src/ftl_extract/ftl_importer.py index cbe8008..26a2019 100644 --- a/src/ftl_extract/ftl_importer.py +++ b/src/ftl_extract/ftl_importer.py @@ -12,11 +12,13 @@ def import_from_ftl( + *, path: Path, locale: str, -) -> tuple[dict[str, FluentKey], Resource, list[FluentKey]]: +) -> tuple[dict[str, FluentKey], dict[str, FluentKey], Resource, list[FluentKey]]: """Import `FluentKey`s from FTL.""" - ftl_keys = {} + ftl_keys: dict[str, FluentKey] = {} + terms: dict[str, FluentKey] = {} leave_as_is = [] resource = parse(path.read_text(encoding="utf-8"), with_spans=True) @@ -31,6 +33,15 @@ def import_from_ftl( locale=locale, position=position, ) + elif isinstance(entry, ast.Term): + terms[entry.id.name] = FluentKey( + code_path=Path(), + key=entry.id.name, + translation=entry, + path=path, + locale=locale, + position=position, + ) else: leave_as_is.append( FluentKey( @@ -43,18 +54,23 @@ def import_from_ftl( ), ) - return ftl_keys, resource, leave_as_is + return ftl_keys, terms, resource, leave_as_is -def import_ftl_from_dir(path: Path, locale: str) -> tuple[dict[str, FluentKey], list[FluentKey]]: +def import_ftl_from_dir( + *, + path: Path, + locale: str, +) -> tuple[dict[str, FluentKey], dict[str, FluentKey], list[FluentKey]]: """Import `FluentKey`s from directory of FTL files.""" ftl_files = (path / locale).rglob("*.ftl") if path.is_dir() else [path] - ftl_keys = {} + ftl_keys: dict[str, FluentKey] = {} + terms: dict[str, FluentKey] = {} leave_as_is = [] for ftl_file in ftl_files: - keys, _, as_is_keys = import_from_ftl(ftl_file, locale) + keys, terms, _, as_is_keys = import_from_ftl(path=ftl_file, locale=locale) ftl_keys.update(keys) leave_as_is.extend(as_is_keys) - return ftl_keys, leave_as_is + return ftl_keys, terms, leave_as_is diff --git a/src/ftl_extract/matcher.py b/src/ftl_extract/matcher.py index ea961fe..58cf731 100644 --- a/src/ftl_extract/matcher.py +++ b/src/ftl_extract/matcher.py @@ -19,6 +19,7 @@ FTLExtractorDifferentPathsError, FTLExtractorDifferentTranslationError, ) +from ftl_extract.utils import to_json_no_span if TYPE_CHECKING: from collections.abc import Iterable @@ -48,6 +49,30 @@ class FluentKey: locale: str | None = field(default=None) position: int | float = field(default=inf) + def __repr__(self) -> str: + return ( + f"FluentKey(" + f"code_path={self.code_path}," + f"key={self.key}," + f"path={self.path}," + f"locale={self.locale}," + f"position={self.position}," + f"translation={self.translation.to_json(fn=to_json_no_span)}" + f")" + ) + + def __str__(self) -> str: + return ( + f"FluentKey(\n" + f"\tcode_path={self.code_path},\n" + f"\tkey={self.key},\n" + f"\tpath={self.path},\n" + f"\tlocale={self.locale},\n" + f"\tposition={self.position},\n" + f"\ttranslation={self.translation.to_json(fn=to_json_no_span)}\n" + f")" + ) + class I18nMatcher(ast.NodeVisitor): def __init__( diff --git a/src/ftl_extract/process/commentator.py b/src/ftl_extract/process/commentator.py index d98746e..90f173a 100644 --- a/src/ftl_extract/process/commentator.py +++ b/src/ftl_extract/process/commentator.py @@ -8,6 +8,6 @@ from ftl_extract.matcher import FluentKey -def comment_ftl_key(key: FluentKey, serializer: FluentSerializer) -> None: +def comment_ftl_key(*, key: FluentKey, serializer: FluentSerializer) -> None: raw_entry = serializer.serialize_entry(key.translation) key.translation = ast.Comment(content=raw_entry.strip()) diff --git a/src/ftl_extract/process/kwargs_extractor.py b/src/ftl_extract/process/kwargs_extractor.py index e2c835e..ca62db1 100644 --- a/src/ftl_extract/process/kwargs_extractor.py +++ b/src/ftl_extract/process/kwargs_extractor.py @@ -4,11 +4,14 @@ from fluent.syntax import ast +from ftl_extract.exceptions import FTLExtractorCantFindReferenceError, FTLExtractorCantFindTermError + if TYPE_CHECKING: from ftl_extract.matcher import FluentKey def _extract_kwargs_from_variable_reference( + *, variable_reference: ast.VariableReference, kwargs: set[str], ) -> None: @@ -16,39 +19,136 @@ def _extract_kwargs_from_variable_reference( def _extract_kwargs_from_selector_expression( + *, + key: FluentKey, selector_expression: ast.SelectExpression, kwargs: set[str], + terms: dict[str, FluentKey], + all_fluent_keys: dict[str, FluentKey], ) -> None: if isinstance(selector_expression.selector, ast.VariableReference): - _extract_kwargs_from_variable_reference(selector_expression.selector, kwargs) + _extract_kwargs_from_variable_reference( + variable_reference=selector_expression.selector, + kwargs=kwargs, + ) for variant in selector_expression.variants: - for placeable in variant.value.elements: - if isinstance(placeable, ast.Placeable): - _extract_kwargs_from_placeable(placeable, kwargs) + for element in variant.value.elements: + if isinstance(element, ast.Placeable): + _extract_kwargs_from_placeable( + key=key, + placeable=element, + kwargs=kwargs, + terms=terms, + all_fluent_keys=all_fluent_keys, + ) + + +def _extract_kwargs_from_message_reference( + *, + key: FluentKey, + message_reference: ast.MessageReference, + kwargs: set[str], + terms: dict[str, FluentKey], + all_fluent_keys: dict[str, FluentKey], +) -> None: + reference_key = all_fluent_keys.get(message_reference.id.name, None) + + if not reference_key: + raise FTLExtractorCantFindReferenceError( + key=key.key, + key_path=key.path, + reference_key=message_reference.id.name, + ) + kwargs.update(extract_kwargs(key=reference_key, terms=terms, all_fluent_keys=all_fluent_keys)) + + +def _extract_kwargs_from_term_reference( + *, + key: FluentKey, + term_expression: ast.TermReference, + kwargs: set[str], + terms: dict[str, FluentKey], + all_fluent_keys: dict[str, FluentKey], +) -> None: + term = terms.get(term_expression.id.name, None) -def _extract_kwargs_from_placeable(placeable: ast.Placeable, kwargs: set[str]) -> None: + if not term: + raise FTLExtractorCantFindTermError( + key=key.key, + key_path=key.path, + term_key=term_expression.id.name, + ) + + kwargs.update(extract_kwargs(key=term, terms=terms, all_fluent_keys=all_fluent_keys)) + + +def _extract_kwargs_from_placeable( + *, + key: FluentKey, + placeable: ast.Placeable, + kwargs: set[str], + terms: dict[str, FluentKey], + all_fluent_keys: dict[str, FluentKey], +) -> None: expression = placeable.expression if isinstance(expression, ast.VariableReference): - _extract_kwargs_from_variable_reference(expression, kwargs) + _extract_kwargs_from_variable_reference(variable_reference=expression, kwargs=kwargs) elif isinstance(expression, ast.SelectExpression): - _extract_kwargs_from_selector_expression(expression, kwargs) - - -def extract_kwargs(key: FluentKey) -> set[str]: + _extract_kwargs_from_selector_expression( + key=key, + selector_expression=expression, + kwargs=kwargs, + terms=terms, + all_fluent_keys=all_fluent_keys, + ) + + elif isinstance(expression, ast.MessageReference): + _extract_kwargs_from_message_reference( + key=key, + message_reference=expression, + kwargs=kwargs, + terms=terms, + all_fluent_keys=all_fluent_keys, + ) + + elif isinstance(expression, ast.TermReference): + _extract_kwargs_from_term_reference( + key=key, + term_expression=expression, + kwargs=kwargs, + terms=terms, + all_fluent_keys=all_fluent_keys, + ) + + +def extract_kwargs( + *, + key: FluentKey, + terms: dict[str, FluentKey] | None = None, + all_fluent_keys: dict[str, FluentKey] | None = None, +) -> set[str]: kwargs: set[str] = set() + terms = terms or {} + all_fluent_keys = all_fluent_keys or {} - if not isinstance(key.translation, ast.Message): + if not isinstance(key.translation, (ast.Message, ast.Term)): return kwargs if not key.translation.value: return kwargs - for placeable in key.translation.value.elements: - if isinstance(placeable, ast.Placeable): - _extract_kwargs_from_placeable(placeable, kwargs) + for element in key.translation.value.elements: + if isinstance(element, ast.Placeable): + _extract_kwargs_from_placeable( + key=key, + placeable=element, + kwargs=kwargs, + terms=terms, + all_fluent_keys=all_fluent_keys, + ) return kwargs diff --git a/src/ftl_extract/process/serializer.py b/src/ftl_extract/process/serializer.py index d8c21f7..1e23b6f 100644 --- a/src/ftl_extract/process/serializer.py +++ b/src/ftl_extract/process/serializer.py @@ -14,6 +14,7 @@ def generate_ftl( + *, fluent_keys: Iterable[FluentKey], serializer: FluentSerializer, leave_as_is: Iterable[FluentKey], diff --git a/src/ftl_extract/utils.py b/src/ftl_extract/utils.py new file mode 100644 index 0000000..ed90b39 --- /dev/null +++ b/src/ftl_extract/utils.py @@ -0,0 +1,6 @@ +from typing import Any + + +def to_json_no_span(value: dict[str, Any]) -> Any: + value.pop("span", None) + return value diff --git a/tests/test_extract/test_cli_extractor.py b/tests/test_extract/test_cli_extractor.py index f1fbbd3..5371fe9 100644 --- a/tests/test_extract/test_cli_extractor.py +++ b/tests/test_extract/test_cli_extractor.py @@ -85,7 +85,11 @@ def test_extract_with_keys_to_comment_and_add( ), patch( "ftl_extract.ftl_extractor.import_ftl_from_dir", - return_value=({"key-1": MagicMock(spec=FluentKey, path=stored_fluent_key_path)}, []), + return_value=( + {"key-1": MagicMock(spec=FluentKey, path=stored_fluent_key_path)}, + {}, + [], + ), ), patch("ftl_extract.ftl_extractor.comment_ftl_key") as mock_comment_ftl_key, patch( @@ -93,7 +97,7 @@ def test_extract_with_keys_to_comment_and_add( return_value=("generated ftl", None), ) as mock_generate_ftl, ): - extract(code_path, output_path, ("en",), ("i18n",)) + extract(code_path=code_path, output_path=output_path, language=("en",), i18n_keys=("i18n",)) mock_comment_ftl_key.assert_called() mock_generate_ftl.assert_called() @@ -114,14 +118,14 @@ def test_extract_with_keys_only_to_add( ), patch( "ftl_extract.ftl_extractor.import_ftl_from_dir", - return_value=({"key-1": mock_fluent_key}, []), + return_value=({"key-1": mock_fluent_key}, {}, []), ), patch( "ftl_extract.ftl_extractor.generate_ftl", return_value=("generated ftl", None), ) as mock_generate_ftl, ): - extract(code_path, output_path, ("en",), ("i18n",)) + extract(code_path=code_path, output_path=output_path, language=("en",), i18n_keys=("i18n",)) mock_generate_ftl.assert_called() @@ -193,19 +197,22 @@ def test_comment_junk_elements_if_needed(setup_environment: tuple[Path, Path]) - with ( patch("ftl_extract.ftl_extractor.extract_fluent_keys", return_value={}), - patch("ftl_extract.ftl_extractor.import_ftl_from_dir", return_value=({}, [mock_junk_key])), + patch( + "ftl_extract.ftl_extractor.import_ftl_from_dir", + return_value=({}, {}, [mock_junk_key]), + ), patch("ftl_extract.ftl_extractor.comment_ftl_key") as mock_comment_ftl_key, patch("fluent.syntax.serializer.FluentSerializer", return_value=mock_serializer), ): extract( - code_path, - output_path, - ("en",), - ("i18n",), + code_path=code_path, + output_path=output_path, + language=("en",), + i18n_keys=("i18n",), comment_junks=True, serializer=mock_serializer, ) - mock_comment_ftl_key.assert_called_once_with(mock_junk_key, mock_serializer) + mock_comment_ftl_key.assert_called_once_with(key=mock_junk_key, serializer=mock_serializer) def test_expand_ignore_attributes_updates_ignore_attributes( @@ -218,15 +225,15 @@ def test_expand_ignore_attributes_updates_ignore_attributes( with ( patch("ftl_extract.ftl_extractor.extract_fluent_keys", return_value={}), - patch("ftl_extract.ftl_extractor.import_ftl_from_dir", return_value=({}, [])), + patch("ftl_extract.ftl_extractor.import_ftl_from_dir", return_value=({}, {}, [])), patch("ftl_extract.ftl_extractor.comment_ftl_key"), patch("ftl_extract.ftl_extractor.generate_ftl", return_value=("generated ftl", None)), ): extract( - code_path, - output_path, - ("en",), - ("i18n",), + code_path=code_path, + output_path=output_path, + language=("en",), + i18n_keys=("i18n",), ignore_attributes=initial_ignore_attributes, expand_ignore_attributes=expand_ignore_attributes, ) @@ -254,17 +261,17 @@ def test_stored_fluent_keys_code_path_update(setup_environment: tuple[Path, Path patch("ftl_extract.ftl_extractor.extract_fluent_keys", return_value=in_code_fluent_keys), patch( "ftl_extract.ftl_extractor.import_ftl_from_dir", - return_value=(stored_fluent_keys, []), + return_value=(stored_fluent_keys, {}, []), ), patch("ftl_extract.ftl_extractor.extract_kwargs", return_value=set()), patch("ftl_extract.ftl_extractor.comment_ftl_key"), patch("ftl_extract.ftl_extractor.generate_ftl", return_value=("generated ftl", None)), ): extract( - code_path, - output_path, - ("en",), - ("i18n",), + code_path=code_path, + output_path=output_path, + language=("en",), + i18n_keys=("i18n",), ) assert stored_fluent_keys["key-1"].code_path == mock_fluent_key.code_path @@ -287,17 +294,17 @@ def test_keys_to_comment_and_add_on_different_kwargs(setup_environment: tuple[Pa patch("ftl_extract.ftl_extractor.extract_fluent_keys", return_value=in_code_fluent_keys), patch( "ftl_extract.ftl_extractor.import_ftl_from_dir", - return_value=(stored_fluent_keys, []), + return_value=(stored_fluent_keys, {}, []), ), patch("ftl_extract.ftl_extractor.extract_kwargs", side_effect=[{"arg1"}, {"arg2"}]), patch("ftl_extract.ftl_extractor.comment_ftl_key"), patch("ftl_extract.ftl_extractor.generate_ftl", return_value=("generated ftl", None)), ): extract( - code_path, - output_path, - ("en",), - ("i18n",), + code_path=code_path, + output_path=output_path, + language=("en",), + i18n_keys=("i18n",), ) assert "key-1" not in stored_fluent_keys diff --git a/tests/test_extract/test_ftl_comment.py b/tests/test_extract/test_ftl_comment.py index 25d38bc..85ac035 100644 --- a/tests/test_extract/test_ftl_comment.py +++ b/tests/test_extract/test_ftl_comment.py @@ -34,17 +34,21 @@ def test_ftl_comment(tmp_path: Path) -> None: (tmp_path / "test.ftl").write_text(CONTENT, encoding="utf-8") - ftl_keys, resource, leave_as_is = import_from_ftl(tmp_path / "test.ftl", "en") + ftl_keys, _, _, leave_as_is = import_from_ftl(path=tmp_path / "test.ftl", locale="en") serializer = FluentSerializer(with_junk=True) - comment_ftl_key(ftl_keys["key-1"], serializer=serializer) - comment_ftl_key(ftl_keys["key-2"], serializer=serializer) - comment_ftl_key(ftl_keys["key-3"], serializer=serializer) - comment_ftl_key(ftl_keys["key-4"], serializer=serializer) - comment_ftl_key(ftl_keys["key-5"], serializer=serializer) - - ftl, _ = generate_ftl(ftl_keys.values(), serializer=serializer, leave_as_is=leave_as_is) + comment_ftl_key(key=ftl_keys["key-1"], serializer=serializer) + comment_ftl_key(key=ftl_keys["key-2"], serializer=serializer) + comment_ftl_key(key=ftl_keys["key-3"], serializer=serializer) + comment_ftl_key(key=ftl_keys["key-4"], serializer=serializer) + comment_ftl_key(key=ftl_keys["key-5"], serializer=serializer) + + ftl, _ = generate_ftl( + fluent_keys=ftl_keys.values(), + serializer=serializer, + leave_as_is=leave_as_is, + ) (tmp_path / "test.ftl").write_text(ftl, encoding="utf-8") ftl = (tmp_path / "test.ftl").read_text(encoding="utf-8") diff --git a/tests/test_ftl_import.py b/tests/test_ftl_import.py index b54a48b..276e444 100644 --- a/tests/test_ftl_import.py +++ b/tests/test_ftl_import.py @@ -22,7 +22,10 @@ def mock_ftl_content() -> str: def test_import_from_ftl_with_valid_ftl_file(mock_ftl_content: str) -> None: with patch("pathlib.Path.read_text", return_value=mock_ftl_content): - keys, resource, leave_as_is = import_from_ftl(Path("/path/to/locale/en/example.ftl"), "en") + keys, _, resource, _ = import_from_ftl( + path=Path("/path/to/locale/en/example.ftl"), + locale="en", + ) assert "hello" in keys assert "welcome" in keys assert len(resource.body) == 7 # noqa: PLR2004 @@ -30,7 +33,10 @@ def test_import_from_ftl_with_valid_ftl_file(mock_ftl_content: str) -> None: def test_import_from_ftl_with_empty_ftl_file() -> None: with patch("pathlib.Path.read_text", return_value=""): - keys, resource, leave_as_is = import_from_ftl(Path("/path/to/locale/en/empty.ftl"), "en") + keys, _, resource, _ = import_from_ftl( + path=Path("/path/to/locale/en/empty.ftl"), + locale="en", + ) assert len(keys) == 0 assert len(resource.body) == 0 @@ -42,27 +48,29 @@ def test_import_ftl_from_dir_with_multiple_files(tmp_path: Path, mock_ftl_conten file1.write_text(mock_ftl_content) file2.write_text(mock_ftl_content) - keys, leave_as_is = import_ftl_from_dir(tmp_path, "en") + keys, _, _ = import_ftl_from_dir(path=tmp_path, locale="en") assert len(keys) == 2 # noqa: PLR2004 def test_import_ftl_from_dir_with_no_ftl_files(tmp_path: Path) -> None: (tmp_path / "en").mkdir(parents=True) - keys, leave_as_is = import_ftl_from_dir(tmp_path, "en") + keys, _, _ = import_ftl_from_dir(path=tmp_path, locale="en") assert len(keys) == 0 def test_import_ftl_from_dir_with_nonexistent_directory() -> None: with pytest.raises(FileNotFoundError): - import_ftl_from_dir(Path("/path/to/nonexistent/dir"), "en") + import_ftl_from_dir(path=Path("/path/to/nonexistent/dir"), locale="en") def test_import_from_ftl_appends_non_message_entries_correctly(mock_ftl_content: str) -> None: with patch("pathlib.Path.read_text", return_value=mock_ftl_content): - _, _, leave_as_is = import_from_ftl(Path("/path/to/locale/en/various_entries.ftl"), "en") - assert len(leave_as_is) == 5 # noqa: PLR2004 + _, _, _, leave_as_is = import_from_ftl( + path=Path("/path/to/locale/en/various_entries.ftl"), + locale="en", + ) + assert len(leave_as_is) == 4 # noqa: PLR2004 assert isinstance(leave_as_is[0].translation, ast.Comment) assert isinstance(leave_as_is[1].translation, ast.GroupComment) assert isinstance(leave_as_is[2].translation, ast.ResourceComment) - assert isinstance(leave_as_is[3].translation, ast.Term) - assert isinstance(leave_as_is[4].translation, ast.Junk) + assert isinstance(leave_as_is[3].translation, ast.Junk) diff --git a/tests/test_kwargs_extractor.py b/tests/test_kwargs_extractor.py index 4cb4952..e7b1f17 100644 --- a/tests/test_kwargs_extractor.py +++ b/tests/test_kwargs_extractor.py @@ -9,7 +9,7 @@ def extracts_variable_names_from_simple_variable() -> None: kwargs = extract_kwargs( - FluentKey( + key=FluentKey( code_path=Path("test.py"), key="key-1", translation=ast.Message( @@ -31,7 +31,7 @@ def extracts_variable_names_from_simple_variable() -> None: def extracts_variable_names_from_select_expression() -> None: kwargs = extract_kwargs( - FluentKey( + key=FluentKey( code_path=Path("test.py"), key="key-1", translation=ast.Message( @@ -66,7 +66,7 @@ def extracts_variable_names_from_select_expression() -> None: def returns_empty_set_for_messages_without_variables() -> None: kwargs = extract_kwargs( - FluentKey( + key=FluentKey( code_path=Path("test.py"), key="key-1", translation=ast.Message( @@ -81,7 +81,7 @@ def returns_empty_set_for_messages_without_variables() -> None: def test_returns_empty_set_for_comment_translation() -> None: kwargs = extract_kwargs( - FluentKey( + key=FluentKey( code_path=Path("test.py"), key="key-1", translation=ast.Comment(content="This is a comment"), @@ -93,7 +93,7 @@ def test_returns_empty_set_for_comment_translation() -> None: def test_returns_empty_set_for_translation_without_value() -> None: kwargs = extract_kwargs( - FluentKey( + key=FluentKey( code_path=Path("test.py"), key="key-1", translation=ast.Message( @@ -108,7 +108,7 @@ def test_returns_empty_set_for_translation_without_value() -> None: def test_extracts_variable_names_from_mixed_elements() -> None: kwargs = extract_kwargs( - FluentKey( + key=FluentKey( code_path=Path("test.py"), key="key-1", translation=ast.Message( @@ -134,7 +134,7 @@ def test_extracts_variable_names_from_mixed_elements() -> None: def test_extracts_selector_variable_name_from_select_expression() -> None: kwargs = extract_kwargs( - FluentKey( + key=FluentKey( code_path=Path("test.py"), key="key_select_expression", translation=ast.Message( @@ -173,7 +173,7 @@ def test_extracts_selector_variable_name_from_select_expression() -> None: def test_nested_extraction() -> None: kwargs = extract_kwargs( - FluentKey( + key=FluentKey( code_path=Path("test.py"), key="trade-waiting_for_answer", translation=ast.Message( diff --git a/tests/test_post_process_fluent_keys.py b/tests/test_post_process_fluent_keys.py index 1e656d9..5ab4b17 100644 --- a/tests/test_post_process_fluent_keys.py +++ b/tests/test_post_process_fluent_keys.py @@ -11,7 +11,7 @@ def test_process_fluent_key() -> None: fluent_mock.path = "test.ftl" fluent_keys = {"key-1": fluent_mock} - post_process_fluent_keys(fluent_keys, default_ftl_file=DEFAULT_FTL_FILE) + post_process_fluent_keys(fluent_keys=fluent_keys, default_ftl_file=DEFAULT_FTL_FILE) assert fluent_mock.path == Path("test.ftl") @@ -20,5 +20,5 @@ def test_process_fluent_key_default() -> None: fluent_mock.path = Path("test") fluent_keys = {"key-1": fluent_mock} - post_process_fluent_keys(fluent_keys, default_ftl_file=DEFAULT_FTL_FILE) + post_process_fluent_keys(fluent_keys=fluent_keys, default_ftl_file=DEFAULT_FTL_FILE) assert fluent_mock.path == Path("test/_default.ftl") diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 4eba00a..be2ac89 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -56,7 +56,7 @@ def test_custom_serializer_produces_correct_ftl_for_single_key( single_fluent_key: list[FluentKey], ) -> None: ftl_string, resource = generate_ftl( - single_fluent_key, + fluent_keys=single_fluent_key, serializer=FluentSerializer(), leave_as_is=[], ) @@ -68,7 +68,7 @@ def test_custom_serializer_produces_correct_ftl_for_multiple_keys( multiple_fluent_keys: list[FluentKey], ) -> None: ftl_string, resource = generate_ftl( - multiple_fluent_keys, + fluent_keys=multiple_fluent_keys, serializer=FluentSerializer(), leave_as_is=[], ) @@ -81,7 +81,7 @@ def test_custom_serializer_handles_empty_fluent_keys_list_properly( empty_fluent_keys: list[FluentKey], ) -> None: ftl_string, resource = generate_ftl( - empty_fluent_keys, + fluent_keys=empty_fluent_keys, serializer=FluentSerializer(), leave_as_is=[], ) @@ -91,7 +91,7 @@ def test_custom_serializer_handles_empty_fluent_keys_list_properly( def test_generate_ftl_includes_leave_as_is_elements() -> None: ftl_string, resource = generate_ftl( - [ + fluent_keys=[ FluentKey( code_path=Path("test.py"), key="test_key", diff --git a/tests/test_sort_fluent_keys.py b/tests/test_sort_fluent_keys.py index 3786ef3..cdd5ac1 100644 --- a/tests/test_sort_fluent_keys.py +++ b/tests/test_sort_fluent_keys.py @@ -18,7 +18,7 @@ def test_sort_fluent_keys_by_single_path() -> None: expected = { Path("path/to/file1.ftl"): [fluent_keys["key-1"]], } - assert sort_fluent_keys_by_path(fluent_keys) == expected + assert sort_fluent_keys_by_path(fluent_keys=fluent_keys) == expected def test_sort_fluent_keys_by_multiple_paths() -> None: @@ -30,7 +30,7 @@ def test_sort_fluent_keys_by_multiple_paths() -> None: Path("path/to/file1.ftl"): [fluent_keys["key-1"]], Path("path/to/file2.ftl"): [fluent_keys["key-2"]], } - assert sort_fluent_keys_by_path(fluent_keys) == expected + assert sort_fluent_keys_by_path(fluent_keys=fluent_keys) == expected def test_sort_fluent_keys_by_same_path() -> None: @@ -41,13 +41,13 @@ def test_sort_fluent_keys_by_same_path() -> None: expected = { Path("path/to/file.ftl"): [fluent_keys["key-1"], fluent_keys["key-2"]], } - assert sort_fluent_keys_by_path(fluent_keys) == expected + assert sort_fluent_keys_by_path(fluent_keys=fluent_keys) == expected def test_sort_fluent_keys_empty_dict() -> None: fluent_keys = {} expected = {} - assert sort_fluent_keys_by_path(fluent_keys) == expected + assert sort_fluent_keys_by_path(fluent_keys=fluent_keys) == expected def test_sort_fluent_keys_with_nonexistent_path() -> None: @@ -57,4 +57,4 @@ def test_sort_fluent_keys_with_nonexistent_path() -> None: expected = { Path("nonexistent/path.ftl"): [fluent_keys["key-1"]], } - assert sort_fluent_keys_by_path(fluent_keys) == expected + assert sort_fluent_keys_by_path(fluent_keys=fluent_keys) == expected diff --git a/uv.lock b/uv.lock index d080752..bb95797 100644 --- a/uv.lock +++ b/uv.lock @@ -25,6 +25,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 }, ] +[[package]] +name = "attrs" +version = "24.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 }, +] + [[package]] name = "babel" version = "2.16.0" @@ -279,6 +288,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, ] +[[package]] +name = "fluent-runtime" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "babel" }, + { name = "fluent-syntax" }, + { name = "pytz" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/54/94bbd857443565a5776027165fe1e2634d836681d7d9c8992a2fd0ce8302/fluent.runtime-0.4.0.tar.gz", hash = "sha256:cb5ef96a58a3f67acaaca046d1201d202a1ebf2823e5e664370f359bae20574d", size = 17591 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/89/a19b0bdfc74977b44aca2ad2e0600fd89c778184fcbb7fc44ba1002cc7b5/fluent.runtime-0.4.0-py2.py3-none-any.whl", hash = "sha256:51fd02582c2363e1106d7051642967a1b7f406dd9c317bd4600a73ede40c5146", size = 15627 }, +] + [[package]] name = "fluent-syntax" version = "0.19.0" @@ -302,6 +327,7 @@ dependencies = [ [package.optional-dependencies] dev = [ + { name = "fluent-runtime" }, { name = "isort" }, { name = "mypy" }, { name = "pre-commit" }, @@ -327,6 +353,7 @@ tests = [ requires-dist = [ { name = "click", specifier = "==8.*" }, { name = "coverage", marker = "extra == 'tests'", specifier = "==7.6.7" }, + { name = "fluent-runtime", marker = "extra == 'dev'", specifier = "==0.4.0" }, { name = "fluent-syntax", specifier = ">=0.19" }, { name = "furo", marker = "extra == 'docs'", specifier = "==2024.8.6" }, { name = "isort", marker = "extra == 'dev'", specifier = "==5.13.2" }, @@ -337,7 +364,7 @@ requires-dist = [ { name = "pytest-html", marker = "extra == 'tests'", specifier = "==4.1.1" }, { name = "pytest-mock", marker = "extra == 'tests'", specifier = "==3.14.0" }, { name = "pytz", marker = "extra == 'docs'", specifier = "==2024.2" }, - { name = "ruff", marker = "extra == 'dev'", specifier = "==0.7.4" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.8.0" }, { name = "sphinx", marker = "extra == 'docs'", specifier = "==7.4.7" }, { name = "sphinx-autobuild", marker = "extra == 'docs'", specifier = "==2024.10.3" }, { name = "sphinx-rtd-theme", marker = "extra == 'docs'", specifier = "==3.0.2" }, @@ -761,27 +788,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.7.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/8b/bc4e0dfa1245b07cf14300e10319b98e958a53ff074c1dd86b35253a8c2a/ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2", size = 3275547 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/4b/f5094719e254829766b807dadb766841124daba75a37da83e292ae5ad12f/ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478", size = 10447512 }, - { url = "https://files.pythonhosted.org/packages/9e/1d/3d2d2c9f601cf6044799c5349ff5267467224cefed9b35edf5f1f36486e9/ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63", size = 10235436 }, - { url = "https://files.pythonhosted.org/packages/62/83/42a6ec6216ded30b354b13e0e9327ef75a3c147751aaf10443756cb690e9/ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20", size = 9888936 }, - { url = "https://files.pythonhosted.org/packages/4d/26/e1e54893b13046a6ad05ee9b89ee6f71542ba250f72b4c7a7d17c3dbf73d/ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109", size = 10697353 }, - { url = "https://files.pythonhosted.org/packages/21/24/98d2e109c4efc02bfef144ec6ea2c3e1217e7ce0cfddda8361d268dfd499/ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452", size = 10228078 }, - { url = "https://files.pythonhosted.org/packages/ad/b7/964c75be9bc2945fc3172241b371197bb6d948cc69e28bc4518448c368f3/ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea", size = 11264823 }, - { url = "https://files.pythonhosted.org/packages/12/8d/20abdbf705969914ce40988fe71a554a918deaab62c38ec07483e77866f6/ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7", size = 11951855 }, - { url = "https://files.pythonhosted.org/packages/b8/fc/6519ce58c57b4edafcdf40920b7273dfbba64fc6ebcaae7b88e4dc1bf0a8/ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05", size = 11516580 }, - { url = "https://files.pythonhosted.org/packages/37/1a/5ec1844e993e376a86eb2456496831ed91b4398c434d8244f89094758940/ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06", size = 12692057 }, - { url = "https://files.pythonhosted.org/packages/50/90/76867152b0d3c05df29a74bb028413e90f704f0f6701c4801174ba47f959/ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc", size = 11085137 }, - { url = "https://files.pythonhosted.org/packages/c8/eb/0a7cb6059ac3555243bd026bb21785bbc812f7bbfa95a36c101bd72b47ae/ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172", size = 10681243 }, - { url = "https://files.pythonhosted.org/packages/5e/76/2270719dbee0fd35780b05c08a07b7a726c3da9f67d9ae89ef21fc18e2e5/ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a", size = 10319187 }, - { url = "https://files.pythonhosted.org/packages/9f/e5/39100f72f8ba70bec1bd329efc880dea8b6c1765ea1cb9d0c1c5f18b8d7f/ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd", size = 10803715 }, - { url = "https://files.pythonhosted.org/packages/a5/89/40e904784f305fb56850063f70a998a64ebba68796d823dde67e89a24691/ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a", size = 11162912 }, - { url = "https://files.pythonhosted.org/packages/8d/1b/dd77503b3875c51e3dbc053fd8367b845ab8b01c9ca6d0c237082732856c/ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac", size = 8702767 }, - { url = "https://files.pythonhosted.org/packages/63/76/253ddc3e89e70165bba952ecca424b980b8d3c2598ceb4fc47904f424953/ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6", size = 9497534 }, - { url = "https://files.pythonhosted.org/packages/aa/70/f8724f31abc0b329ca98b33d73c14020168babcf71b0cba3cded5d9d0e66/ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f", size = 8851590 }, +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/d6/a2373f3ba7180ddb44420d2a9d1f1510e1a4d162b3d27282bedcb09c8da9/ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44", size = 3276537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/77/e889ee3ce7fd8baa3ed1b77a03b9fb8ec1be68be1418261522fd6a5405e0/ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea", size = 10518283 }, + { url = "https://files.pythonhosted.org/packages/da/c8/0a47de01edf19fb22f5f9b7964f46a68d0bdff20144d134556ffd1ba9154/ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b", size = 10317691 }, + { url = "https://files.pythonhosted.org/packages/41/17/9885e4a0eeae07abd2a4ebabc3246f556719f24efa477ba2739146c4635a/ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a", size = 9940999 }, + { url = "https://files.pythonhosted.org/packages/3e/cd/46b6f7043597eb318b5f5482c8ae8f5491cccce771e85f59d23106f2d179/ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99", size = 10772437 }, + { url = "https://files.pythonhosted.org/packages/5d/87/afc95aeb8bc78b1d8a3461717a4419c05aa8aa943d4c9cbd441630f85584/ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c", size = 10299156 }, + { url = "https://files.pythonhosted.org/packages/65/fa/04c647bb809c4d65e8eae1ed1c654d9481b21dd942e743cd33511687b9f9/ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9", size = 11325819 }, + { url = "https://files.pythonhosted.org/packages/90/26/7dad6e7d833d391a8a1afe4ee70ca6f36c4a297d3cca83ef10e83e9aacf3/ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362", size = 12023927 }, + { url = "https://files.pythonhosted.org/packages/24/a0/be5296dda6428ba8a13bda8d09fbc0e14c810b485478733886e61597ae2b/ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df", size = 11589702 }, + { url = "https://files.pythonhosted.org/packages/26/3f/7602eb11d2886db545834182a9dbe500b8211fcbc9b4064bf9d358bbbbb4/ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3", size = 12782936 }, + { url = "https://files.pythonhosted.org/packages/4c/5d/083181bdec4ec92a431c1291d3fff65eef3ded630a4b55eb735000ef5f3b/ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c", size = 11138488 }, + { url = "https://files.pythonhosted.org/packages/b7/23/c12cdef58413cee2436d6a177aa06f7a366ebbca916cf10820706f632459/ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2", size = 10744474 }, + { url = "https://files.pythonhosted.org/packages/29/61/a12f3b81520083cd7c5caa24ba61bb99fd1060256482eff0ef04cc5ccd1b/ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70", size = 10369029 }, + { url = "https://files.pythonhosted.org/packages/08/2a/c013f4f3e4a54596c369cee74c24870ed1d534f31a35504908b1fc97017a/ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd", size = 10867481 }, + { url = "https://files.pythonhosted.org/packages/d5/f7/685b1e1d42a3e94ceb25eab23c70bdd8c0ab66a43121ef83fe6db5a58756/ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426", size = 11237117 }, + { url = "https://files.pythonhosted.org/packages/03/20/401132c0908e8837625e3b7e32df9962e7cd681a4df1e16a10e2a5b4ecda/ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468", size = 8783511 }, + { url = "https://files.pythonhosted.org/packages/1d/5c/4d800fca7854f62ad77f2c0d99b4b585f03e2d87a6ec1ecea85543a14a3c/ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f", size = 9559876 }, + { url = "https://files.pythonhosted.org/packages/5b/bc/cc8a6a5ca4960b226dc15dd8fb511dd11f2014ff89d325c0b9b9faa9871f/ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6", size = 8939733 }, ] [[package]] @@ -951,15 +978,15 @@ wheels = [ [[package]] name = "starlette" -version = "0.41.2" +version = "0.41.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3e/da/1fb4bdb72ae12b834becd7e1e7e47001d32f91ec0ce8d7bc1b618d9f0bd9/starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62", size = 2573867 } +sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/9b5764bd22eec91c4039ef4c55334e9187085da2d8a2df7bd570869aae18/starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", size = 2574159 } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/43/f185bfd0ca1d213beb4293bed51d92254df23d8ceaf6c0e17146d508a776/starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d", size = 73259 }, + { url = "https://files.pythonhosted.org/packages/96/00/2b325970b3060c7cecebab6d295afe763365822b1306a12eeab198f74323/starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7", size = 73225 }, ] [[package]] @@ -991,16 +1018,16 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.32.0" +version = "0.32.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/fc/1d785078eefd6945f3e5bab5c076e4230698046231eb0f3747bc5c8fa992/uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e", size = 77564 } +sdist = { url = "https://files.pythonhosted.org/packages/6a/3c/21dba3e7d76138725ef307e3d7ddd29b763119b3aa459d02cc05fefcff75/uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175", size = 77630 } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/14/78bd0e95dd2444b6caacbca2b730671d4295ccb628ef58b81bee903629df/uvicorn-0.32.0-py3-none-any.whl", hash = "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82", size = 63723 }, + { url = "https://files.pythonhosted.org/packages/50/c1/2d27b0a15826c2b71dcf6e2f5402181ef85acf439617bb2f1453125ce1f3/uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e", size = 63828 }, ] [[package]]