Skip to content

503 typechecking fails with ty and pyrefly - #526

Merged
cleder merged 7 commits into
developfrom
503-typechecking-fails-with-ty-and-pyrefly
Jul 5, 2026
Merged

503 typechecking fails with ty and pyrefly#526
cleder merged 7 commits into
developfrom
503-typechecking-fails-with-ty-and-pyrefly

Conversation

@cleder

@cleder cleder commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added a migration guide for moving from one type checker to newer alternatives.
    • Updated example and documentation install commands for a newer workflow.
  • Bug Fixes

    • Improved compatibility with Python 3.10+ across the library and examples.
    • Refined XML and geometry handling to better support static analysis and edge cases.
  • Chores

    • Dropped Python 3.9 from supported test and CI matrices.
    • Replaced the previous type-checking setup with newer checkers in CI and pre-commit.

cleder and others added 2 commits July 5, 2026 16:45
- Updated KML parsing to remove unnecessary encoding.
- Added type hints to function parameters and return types in various modules.
- Improved error handling and assertions in KML document processing.
- Refined type casting and removed redundant casts in geometry handling.
- Enhanced type checking for XML elements and attributes.
- Updated project dependencies and Python version requirements in pyproject.toml.
- Improved test cases for geometry and KML handling to ensure type safety.
Raise the minimum supported version to 3.10, drop it from CI matrices,
tox.ini, and .sourcery.yaml, and let ruff migrate Optional[X]/Union[X, Y]
annotations to the X | None / X | Y syntax across the codebase and tests.
Pin hypothesis to <6.156 in the tests extra since newer releases ship no
wheel for Python 3.15 yet.

Swap the mirrors-mypy pre-commit hook for local ty/pyrefly hooks, matching
the type checkers already used in CI and completing the migration away
from mypy. Reword two comments that false-positived on pygrep's
type-annotations-not-comments check.

Also split coordinates_subelement and create_multigeometry's complex
blocks into private helpers to bring their cyclomatic complexity back
under the complexipy threshold.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 5, 2026 17:28
@cleder cleder linked an issue Jul 5, 2026 that may be closed by this pull request
@semanticdiff-com

semanticdiff-com Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review changes with  SemanticDiff

Changed Files
File Status
  tests/hypothesis/network_link_control_test.py  69% smaller
  tests/overlays_test.py  63% smaller
  tests/hypothesis/feature_test.py  61% smaller
  tests/hypothesis/style_test.py  61% smaller
  .github/workflows/run-all-tests.yml  61% smaller
  fastkml/base.py  60% smaller
  examples/simple_example.py  59% smaller
  fastkml/features.py  58% smaller
  fastkml/network_link_control.py  57% smaller
  tests/hypothesis/overlay_test.py  54% smaller
  tests/hypothesis/geometry_test.py  53% smaller
  tests/geometries/geometry_test.py  53% smaller
  tests/geometries/boundaries_test.py  52% smaller
  fastkml/config.py  52% smaller
  fastkml/kml.py  49% smaller
  tests/config_test.py  49% smaller
  fastkml/validator.py  44% smaller
  fastkml/styles.py  44% smaller
  fastkml/times.py  43% smaller
  fastkml/model.py  43% smaller
  fastkml/views.py  42% smaller
  fastkml/overlays.py  42% smaller
  fastkml/gx/track.py  41% smaller
  fastkml/abstract_geometry.py  41% smaller
  fastkml/mixins.py  40% smaller
  fastkml/containers.py  40% smaller
  fastkml/gx/data.py  39% smaller
  fastkml/links.py  38% smaller
  fastkml/data.py  38% smaller
  fastkml/geometry.py  36% smaller
  fastkml/helpers.py  35% smaller
  tests/hypothesis/views_test.py  34% smaller
  fastkml/atom.py  33% smaller
  tests/hypothesis/model_test.py  31% smaller
  fastkml/registry.py  30% smaller
  tests/hypothesis/gx/data_test.py  28% smaller
  tests/hypothesis/atom_test.py  28% smaller
  tests/hypothesis/data_test.py  25% smaller
  tests/hypothesis/links_test.py  25% smaller
  fastkml/kml_base.py  24% smaller
  fastkml/types.py  23% smaller
  tests/hypothesis/gx/track_test.py  22% smaller
  tests/hypothesis/times_test.py  21% smaller
  examples/transform_cascading_style.py  20% smaller
  tests/registry_test.py  20% smaller
  examples/read_kml.py  14% smaller
  fastkml/utils.py  5% smaller
  .pre-commit-config.yaml  0% smaller
  .sourcery.yaml  0% smaller
  README.rst Unsupported file format
  docs/HISTORY.rst Unsupported file format
  docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md Unsupported file format
  pyproject.toml Unsupported file format
  tests/base.py  0% smaller
  tests/data_test.py  0% smaller
  tests/geometries/functions_test.py  0% smaller
  tests/geometries/point_test.py  0% smaller
  tests/gx/data_test.py  0% smaller
  tests/helper_test.py  0% smaller
  tests/hypothesis/kml_test.py  0% smaller
  tests/hypothesis/multi_geometry_test.py  0% smaller
  tests/times_test.py  0% smaller
  tox.ini Unsupported file format

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @cleder, your pull request is larger than the review limit of 150000 diff characters

@kody-ai

This comment has been minimized.

@mergify

mergify Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@cleder, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 96786da0-94a0-439b-b469-d7cbd80ecd4b

📥 Commits

Reviewing files that changed from the base of the PR and between 29bc15c and 48390cc.

📒 Files selected for processing (7)
  • .pre-commit-config.yaml
  • fastkml/base.py
  • fastkml/gx/track.py
  • fastkml/helpers.py
  • fastkml/types.py
  • fastkml/validator.py
  • pyproject.toml

Walkthrough

This PR raises the minimum supported Python version to 3.10, replaces mypy with ty and pyrefly type checkers across CI, pre-commit, and pyproject configuration, and modernizes type annotations throughout fastkml, examples, and tests to PEP 604 union syntax (T | None). It also adds a migration guide, adjusts a few runtime code paths (etree/config handling, KML parse/validate root usage, gx Track item construction, geometry coordinate formatting), and updates numerous type-ignore comments in tests.

Changes

Python 3.10 / typing modernization

Layer / File(s) Summary
CI, pre-commit, and pyproject tooling
.github/workflows/run-all-tests.yml, .pre-commit-config.yaml, .sourcery.yaml, tox.ini, pyproject.toml
Drops Python 3.9 from CI matrices, replaces mypy with ty/pyrefly in pre-commit and CI typecheck steps, and updates pyproject.toml classifiers, requires-python, dependency groups, and adds [tool.ty]/[tool.pyrefly] config while removing [tool.mypy].
README, changelog, and migration guide
README.rst, docs/HISTORY.rst, docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md
Updates compatibility/install docs to Python 3.10+ and uv, adds a changelog entry for dropping 3.9, and introduces a new migration guide document.
Examples type-hint modernization
examples/read_kml.py, examples/simple_example.py, examples/transform_cascading_style.py
Adds type annotations, switches from_string input to str, and adds a runtime assert after locating Document.
Core infrastructure typing
fastkml/base.py, fastkml/config.py, fastkml/types.py, fastkml/registry.py, fastkml/helpers.py, fastkml/kml_base.py, fastkml/mixins.py, fastkml/validator.py, fastkml/utils.py, tests/base.py, tests/config_test.py, tests/helper_test.py
Modernizes _XMLObject, etree selection, Element protocol, registry, XML helpers, validator, and find/find_all overload typing; removes now-redundant casts and type-ignore comments.
KML entry point
fastkml/kml.py, tests/hypothesis/kml_test.py
Updates KML type hints and reworks lxml_parse_and_validate/KML.parse to validate and return the parsed root directly.
Geometry and gx.Track
fastkml/abstract_geometry.py, fastkml/geometry.py, fastkml/gx/track.py, tests/geometries/*, tests/hypothesis/geometry_test.py, tests/hypothesis/multi_geometry_test.py, tests/hypothesis/gx/track_test.py
Modernizes geometry typing, adds _format_coordinates/_create_homogeneous_multigeometry helpers, and reworks Track item construction to use zip(strict=False) with default Angle().
Feature, data, container, model, atom, times, links, network-link-control
fastkml/data.py, fastkml/gx/data.py, fastkml/features.py, fastkml/containers.py, fastkml/model.py, fastkml/network_link_control.py, fastkml/times.py, fastkml/links.py, fastkml/atom.py, related tests
Modernizes type annotations across these classes and updates corresponding hypothesis/unit tests and type-ignore comments.
Style, view, and overlay typing
fastkml/styles.py, fastkml/overlays.py, fastkml/views.py, related tests
Updates type annotations across style, view, and overlay classes and adjusts a test to use the AltitudeMode enum instead of a raw string.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Possibly related PRs

  • cleder/fastkml#358: Both PRs modify the static-tests typechecking step in run-all-tests.yml.
  • cleder/fastkml#453: Overlapping typing/tooling setup changes to the same modules (fastkml/atom.py, fastkml/base.py) and CI/pre-commit configuration.
  • cleder/fastkml#466: Both modify fastkml/abstract_geometry.py's _Geometry class, with this PR updating its typing to PEP 604 unions.

Suggested labels: Configuration changes, Review effort 4/5

Poem

A rabbit hopped through code so wide,
Swapped Optional for | None with pride,
Waved goodbye to mypy's old den,
Welcomed ty and pyrefly in,
Python 3.10 now the floor —
🐇 hop, hop, ever more!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the PR’s main theme: fixing typechecking by introducing ty and pyrefly support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 503-typechecking-fails-with-ty-and-pyrefly

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@what-the-diff

what-the-diff Bot commented Jul 5, 2026

Copy link
Copy Markdown

PR Summary

  • Enhanced Type Checking: GitHub actions have been updated to replace the mypy type checking tool with ty and pyrefly. This new approach allows for more powerful and extended type checking methods.

  • Python Version Update: The PR enforces a new minimal requirement for Python version, from 3.9 to 3.10. This version is more compatible with the updated elements in the code.

  • Type Hinting Refactor: The code has been refactored to replace Optional with union types, improving clarity for type declarations. Union types are also used in class constructor parameters providing an accurate description of input data types.

  • Code Readability: This includes the removal of unnecessary imports, change in code from Optional to union types (| None) in multiple files. There is an adoption of | syntax replacing Union for readability enhancement.

  • KML parsing method: KML parsing method in the example code base has been modified, reflecting updates in the code.

  • Installation Updates: The installation instructions have updated uv as an alternative to pip, providing an additional avenue for package installation.

  • Class Refactor: Class constructors are refactored to adopt union types, providing the same value but with a simplified and cleaner implementation.

  • Comment Improvement: Comments are enhanced to increase code readability and provide a more comprehensive understanding for other developers.

  • Tox File Update: In line with the Python version requirement change, the minimum Python version requirement in tox.ini has also been adjusted from 3.9 to 3.10.

  • Added Tool Configurations: New configurations have been established for ty and pyrefly in pyproject.toml, this ensures strict adherence to type checks and improved detection of missing attributes.

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

Failed to generate code suggestions for PR

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request drops support for Python 3.9, updates the codebase to Python 3.10+, and migrates the static type checking toolchain from mypy to ty and pyrefly. It also modernizes type annotations across the project by replacing Union and Optional with the pipe (|) syntax. The review feedback identifies a potential runtime TypeError in fastkml/gx/track.py when handling the angles iterable, and points out a configuration error in pyproject.toml where the kebab-case key ignore-missing-imports is used instead of the snake_case ignore_missing_imports required by pyrefly.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

I am having trouble creating individual review comments. Click here to see my feedback.

fastkml/gx/track.py (192-203)

high

The parameter angles is typed as Iterable[PointType] | None = None. If angles is not provided (defaulting to None) or is passed as a non-sequence iterable (such as a generator), calling len(angles) and indexing angles[i] will raise a TypeError at runtime.

To prevent runtime crashes, convert angles to a list if it is not None, and safely check the index against its length.

        if not track_items and whens and coords:
            # `whens`/`coords` form mandatory pairs (a track item always
            # needs a timestamp and a coordinate); `angles` may be shorter
            # and defaults to Angle()'s all-zero heading/tilt/roll.
            angles_list = list(angles) if angles is not None else []
            track_items = [
                TrackItem(
                    when=when,
                    coord=geo.Point(*coord),
                    angle=Angle(*angles_list[i]) if i < len(angles_list) else Angle(),
                )
                for i, (when, coord) in enumerate(zip(whens, coords, strict=False))
            ]

pyproject.toml (241)

medium

According to the migration guide added in this PR (docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md), pyrefly's TOML keys are snake_case even though its CLI flags are kebab-case.

Therefore, the key ignore-missing-imports should be written as ignore_missing_imports to ensure it is correctly parsed and not silently ignored by pyrefly.

ignore_missing_imports = ["*"]

Comment thread examples/read_kml.py
# Create the KML object to store the parsed result
# Read in the KML string
k = kml.KML.from_string(doc.encode("utf-8"))
k = kml.KML.from_string(doc)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lack of error handling for KML parsing

The code directly parses the KML string using kml.KML.from_string(doc) without any error handling. If the input string is malformed or contains invalid XML, this may raise an exception and terminate execution unexpectedly.

Recommended solution:
Wrap the parsing operation in a try-except block to handle potential parsing errors gracefully:

try:
    k = kml.KML.from_string(doc)
except Exception as e:
    print(f"Failed to parse KML: {e}")
    # Optionally handle or re-raise

Comment on lines 69 to 77

cs_kml = KML.parse(examples_dir / "gx_cascading_style.kml", validate=False)
document = find(cs_kml, of_type=Document)
assert document is not None # noqa: S101
# gx_cascading_style is a dynamic attribute added to Document by the
# registry.register() call above; Document's type doesn't declare it.
for cascading_style in document.gx_cascading_style:
kml_style = cascading_style.style
kml_style.id = cascading_style.id

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code assumes that document.gx_cascading_style exists and is iterable, and that each cascading_style has a style attribute. If the KML file is malformed or the registry registration fails, this could raise an AttributeError or TypeError at runtime.

Recommended solution:
Add explicit error handling to check for the existence and type of document.gx_cascading_style and the presence of the style attribute before iterating:

if hasattr(document, 'gx_cascading_style') and isinstance(document.gx_cascading_style, list):
    for cascading_style in document.gx_cascading_style:
        if hasattr(cascading_style, 'style') and cascading_style.style is not None:
            kml_style = cascading_style.style
            kml_style.id = getattr(cascading_style, 'id', None)
            document.styles.append(kml_style)
else:
    raise RuntimeError('gx_cascading_style attribute missing or invalid on Document')

Comment thread fastkml/atom.py
Comment on lines 101 to 103
**kwargs: Any,
) -> None:
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The length parameter is assigned directly to self.length without type validation or conversion. If a non-integer value is passed, this could lead to runtime errors or inconsistent behavior elsewhere in the codebase.

Recommendation:
Add explicit type checking or conversion for length:

if length is not None:
    try:
        self.length = int(length)
    except (TypeError, ValueError):
        raise ValueError(f"length must be an integer, got {length!r}")
else:
    self.length = None

This ensures that self.length is always an integer or None, improving robustness.

Comment thread fastkml/base.py
Comment on lines 476 to 480
ns=ns,
name_spaces=name_spaces,
strict=strict,
element=cast(
"Element",
config.etree.fromstring(string),
),
element=config.etree.fromstring(string),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call to config.etree.fromstring(string) does not include any error handling. If the input string is not valid XML, this will raise an exception (such as XMLSyntaxError), which will propagate up and may cause the application to crash or behave unpredictably.

Recommendation:
Wrap the call in a try-except block to catch XML parsing errors and raise a more descriptive exception or handle the error gracefully. For example:

try:
    element = config.etree.fromstring(string)
except Exception as exc:
    raise ValueError(f"Failed to parse XML string: {exc}") from exc

Comment thread fastkml/base.py
Comment on lines 174 to 180
def populate_element(
self,
element: Element,
precision: Optional[int] = None,
precision: int | None = None,
verbosity: Verbosity = Verbosity.normal,
) -> None:
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The populate_element method iterates over registry items and calls item.set_element for each. If any registry item is misconfigured or if set_element raises an exception, there is no error handling or logging, which could make debugging difficult.

Recommendation:
Consider adding error handling or logging around the call to item.set_element to improve maintainability and debuggability, especially if the registry is extended or used incorrectly. For example:

for item in registry.get(self.__class__):
    try:
        item.set_element(...)
    except Exception as exc:
        logger.error(f"Error setting element for {item.attr_name}: {exc}")
        raise

Comment thread fastkml/mixins.py
Comment on lines +35 to 47
def time_stamp(self) -> KmlDateTime | None:
"""Return the timestamp."""
return self.times.timestamp if isinstance(self.times, TimeStamp) else None

@property
def begin(self) -> Optional[KmlDateTime]:
def begin(self) -> KmlDateTime | None:
"""Return the start time of a time span."""
return self.times.begin if isinstance(self.times, TimeSpan) else None

@property
def end(self) -> Optional[KmlDateTime]:
def end(self) -> KmlDateTime | None:
"""Return the end time of a time span."""
return self.times.end if isinstance(self.times, TimeSpan) else None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The property methods (time_stamp, begin, end) rely on self.times being either TimeSpan, TimeStamp, or None. If self.times is set to an unexpected type, these properties will silently return None, potentially masking bugs and making debugging difficult.

Recommendation: Consider adding explicit type validation for self.times in the class initializer or setter, or raise an exception in the property methods if self.times is not None and not an expected type. This will make errors more visible and improve robustness.

Comment thread fastkml/model.py
Comment on lines 101 to 107
)

@property
def geometry(self) -> Optional[Point]:
def geometry(self) -> Point | None:
"""Return a Point representation of the geometry."""
if not self:
return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-robust error handling in Location.geometry property

The use of assertions (assert self.longitude is not None, assert self.latitude is not None) in the geometry property is not robust. If these values are None, an AssertionError will be raised, which is not user-friendly and may not be caught in production environments. Consider raising a ValueError with a clear message or handling the case gracefully:

if self.longitude is None or self.latitude is None:
    raise ValueError("Longitude and latitude must be set to compute geometry.")

This approach improves error reporting and maintainability.

Comment thread fastkml/model.py
Comment on lines 333 to 349

_default_nsid = config.KML

target_href: Optional[str]
source_href: Optional[str]
target_href: str | None
source_href: str | None

def __init__(
self,
ns: Optional[str] = None,
name_spaces: Optional[dict[str, str]] = None,
id: Optional[str] = None,
target_id: Optional[str] = None,
target_href: Optional[str] = None,
source_href: Optional[str] = None,
ns: str | None = None,
name_spaces: dict[str, str] | None = None,
id: str | None = None,
target_id: str | None = None,
target_href: str | None = None,
source_href: str | None = None,
**kwargs: Any,
) -> None:
"""Create a new Alias."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential issue with empty string values in Alias constructor

The constructor uses clean_string for target_href and source_href, but does not check if the cleaned values are empty strings. This could result in storing empty strings instead of None, which may affect downstream logic that expects None for unset values. Consider normalizing empty strings to None after cleaning:

self.target_href = clean_string(target_href) or None
self.source_href = clean_string(source_href) or None

This ensures unset values are consistently represented as None.

Comment on lines 68 to 76

def __init__(
self,
ns: Optional[str] = None,
name_spaces: Optional[dict[str, str]] = None,
objects: Optional[Iterable[_XMLObject]] = None,
ns: str | None = None,
name_spaces: dict[str, str] | None = None,
objects: Iterable[_XMLObject] | None = None,
**kwargs: Any,
) -> None:
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential Type Safety Issue in _UpdateAction __init__

The objects parameter is accepted as an Iterable[_XMLObject] | None, but there is no validation to ensure that all items in the iterable are instances of _XMLObject. Passing an incorrect type could lead to runtime errors later when the objects are used.

Recommendation:
Add explicit type validation for the objects parameter:

if objects:
    for obj in objects:
        if not isinstance(obj, _XMLObject):
            raise TypeError(f"All objects must be instances of _XMLObject, got {type(obj)}")
    self.objects = list(objects)
else:
    self.objects = []

This will ensure only valid objects are accepted and errors are caught early.

Comment on lines 295 to 309

_default_nsid = config.KML

target_href: Optional[str]
operations: list[Union[Create, Delete, Change]]
target_href: str | None
operations: list[Create | Delete | Change]

def __init__(
self,
ns: Optional[str] = None,
name_spaces: Optional[dict[str, str]] = None,
target_href: Optional[str] = None,
operations: Optional[Iterable[Union[Create, Delete, Change]]] = None,
ns: str | None = None,
name_spaces: dict[str, str] | None = None,
target_href: str | None = None,
operations: Iterable[Create | Delete | Change] | None = None,
**kwargs: Any,
) -> None:
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lack of Type Validation for operations in Update __init__

The operations parameter is expected to be an iterable of Create, Delete, or Change instances, but there is no validation to enforce this. If an incorrect type is passed, it may cause runtime errors when the operations are processed.

Recommendation:
Add type validation for the operations parameter:

if operations:
    for op in operations:
        if not isinstance(op, (Create, Delete, Change)):
            raise TypeError(f"All operations must be Create, Delete, or Change instances, got {type(op)}")
    self.operations = list(operations)
else:
    self.operations = []

This will ensure only valid operations are accepted.

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@codacy-production

codacy-production Bot commented Jul 5, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 31 high · 1 medium · 3 minor

Alerts:
⚠ 35 issues (≤ 0 issues of at least minor severity)

Results:
35 new issues

Category Results
BestPractice 3 minor
ErrorProne 30 high
Security 1 high
Complexity 1 medium

View in Codacy

🟢 Metrics 9 complexity · -12 duplication

Metric Results
Complexity 9
Duplication -12

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix ty/pyrefly typechecking; drop Python 3.9; harden etree typing

✨ Enhancement ⚙️ Configuration changes 📝 Documentation 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Drop Python 3.9 support and update CI/tooling to Python 3.10+.
• Replace mypy with ty + pyrefly in CI and pre-commit, adding strict configs.
• Refine etree/lxml typing and adjust code/tests to satisfy both checkers.
Diagram

graph TD
  Dev([Developer]) --> PC[".pre-commit-config.yaml"] --> TC[["ty + pyrefly"]] --> Code["fastkml + tests"] --> Etree[("etree backend")]
  CI["GitHub Actions"] --> TC
  PYP["pyproject.toml"] --> TC
  subgraph Legend
    direction LR
    _actor(["Human"]) ~~~ _tool[["Tooling"]] ~~~ _code["Code"] ~~~ _dep[("Backend")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Standardize on a single checker (ty only)
  • ➕ Simpler CI/pre-commit; fewer tool-specific suppressions
  • ➕ Less time spent reconciling differing diagnostics
  • ➖ Loses complementary coverage where ty and pyrefly catch different issues
  • ➖ May regress confidence vs current dual-checker CI intent
2. Use pyright instead of pyrefly as the second checker
  • ➕ Very common in Python ecosystems; strong editor integration
  • ➕ Often good third-party stub compatibility
  • ➖ Additional migration and config churn
  • ➖ Diverges from the CI/tooling direction chosen in this PR
3. Avoid TYPE_CHECKING aliasing; rely on Protocol-only Element typing
  • ➕ Keeps runtime modules free of checker-specific conditionals
  • ➖ Does not address optional-backend mismatch that drives many diagnostics
  • ➖ Typically pushes casts/ignores out to many call sites instead

Recommendation: Keep the PR’s approach: run both ty and pyrefly, and fix the systemic etree/lxml typing mismatch at the import/type-definition level (TYPE_CHECKING aliasing + narrowly-scoped ignores). This minimizes widespread casts while keeping strictness high and suppressions localized to tests/examples where dynamic patterns are intentional.

Files changed (63) +1868 / -1536

Enhancement (4) +59 / -13
read_kml.pyPass str directly to KML.from_string +1/-1

Pass str directly to KML.from_string

• Removes unnecessary UTF-8 encoding and passes a str into the parser, matching the updated typing expectations.

examples/read_kml.py

simple_example.pyAdd basic annotations to example recursion helper +1/-1

Add basic annotations to example recursion helper

• Adds parameter/return type hints to the example helper to satisfy strict type checking.

examples/simple_example.py

transform_cascading_style.pyNarrow optional values and document dynamic registry attributes +8/-6

Narrow optional values and document dynamic registry attributes

• Updates typing to PEP 604 unions and adds narrowing asserts where needed. Documents and works around registry-injected dynamic attributes in a type-checker-friendly way.

examples/transform_cascading_style.py

utils.pyAdd overloads for find/find_all for precise return typing +49/-5

Add overloads for find/find_all for precise return typing

• Introduces TypeVar-based overloads so callers get typed results when passing of_type. Keeps runtime behavior while improving checker inference.

fastkml/utils.py

Bug fix (4) +85 / -65
config.pyMake etree backend type-checker aware (lxml-first) +11/-5

Make etree backend type-checker aware (lxml-first)

• Uses TYPE_CHECKING to expose lxml as the reference backend to static analyzers while keeping runtime try/except fallback to ElementTree. Adds a targeted ignore around setting the global etree module.

fastkml/config.py

kml.pyFix parse/validate typing around lxml roots and stub gaps +25/-26

Fix parse/validate typing around lxml roots and stub gaps

• Ensures validation operates on the root element and returns the root without unnecessary casting. Adds targeted casts/ignores to work around lxml-stubs limitations (e.g., default namespace nsmap and tag typing).

fastkml/kml.py

types.pyAlias Element to lxml under TYPE_CHECKING; keep runtime Protocol +27/-20

Alias Element to lxml under TYPE_CHECKING; keep runtime Protocol

• Uses TYPE_CHECKING to bind Element to lxml.etree._Element for static analysis while retaining a Protocol fallback at runtime. Adjusts Protocol members to better match etree reality (e.g., text being nullable).

fastkml/types.py

validator.pyWork around lxml-stubs gaps in validation/error handling +22/-14

Work around lxml-stubs gaps in validation/error handling

• Adds TYPE_CHECKING-only Protocols/casts for lxml error log entries and xpath results. Ensures validation uses the root element when validating a parsed document.

fastkml/validator.py

Refactor (20) +967 / -977
abstract_geometry.pyRefine abstract geometry typing for strict checkers +6/-7

Refine abstract geometry typing for strict checkers

• Adjusts annotations/casts around abstract geometry handling to reduce ty/pyrefly errors and improve inferred types.

fastkml/abstract_geometry.py

atom.pyModernize Atom types to PEP 604 unions +22/-23

Modernize Atom types to PEP 604 unions

• Converts Optional[...] fields/params to X | None across Atom models. Aligns attribute types with real runtime behavior for stricter checking.

fastkml/atom.py

base.pyRemove redundant etree casts and modernize signatures +19/-30

Remove redundant etree casts and modernize signatures

• Converts core XML object APIs to PEP 604 unions and removes unnecessary casts around etree serialization/parsing. Keeps behavior while making types acceptable to ty/pyrefly.

fastkml/base.py

containers.pyUpdate container constructors to modern union types +43/-45

Update container constructors to modern union types

• Migrates many container and Document/Folder constructor parameters from Optional/Union to PEP 604 unions, improving strict-checker compatibility.

fastkml/containers.py

data.pyTighten schema/data model annotations +45/-47

Tighten schema/data model annotations

• Modernizes Schema/Data-related annotations to PEP 604 unions and clarifies method signatures to satisfy strict typing.

fastkml/data.py

features.pyModernize feature/geometry unions and attributes +97/-99

Modernize feature/geometry unions and attributes

• Rewrites type aliases and many feature fields/constructors using PEP 604 unions for compatibility with ty/pyrefly strict mode.

fastkml/features.py

geometry.pyExtract helpers and adjust geometry typing/casts +163/-132

Extract helpers and adjust geometry typing/casts

• Introduces small helpers (e.g., coordinate formatting, homogeneous multigeometry creation) to reduce complexity and improve type inference. Replaces mypy-era ignores with targeted casts and type-checker-specific suppressions where structural limitations exist.

fastkml/geometry.py

data.pyUpdate gx data typing for strict checking +16/-17

Update gx data typing for strict checking

• Modernizes annotations and narrows types in gx data handling to align with ty/pyrefly expectations.

fastkml/gx/data.py

track.pyUpdate gx track typing for strict checking +27/-31

Update gx track typing for strict checking

• Refines annotations and narrowing/casts in gx track parsing/serialization paths for ty/pyrefly.

fastkml/gx/track.py

helpers.pyModernize helper signatures and add tool-specific ignores +50/-52

Modernize helper signatures and add tool-specific ignores

• Migrates helper APIs to PEP 604 unions and adds explicit ty/pyrefly ignore comments for intentional dynamic patterns. Tightens default/precision typing across XML helper utilities.

fastkml/helpers.py

kml_base.pyAnnotate base IDs and modernize constructor typing +6/-7

Annotate base IDs and modernize constructor typing

• Adds explicit typing for id/target_id and migrates constructor parameters to PEP 604 unions.

fastkml/kml_base.py

links.pyModernize link model typing +20/-21

Modernize link model typing

• Converts Optional/Union annotations to PEP 604 union syntax and tightens signatures for strict checker compatibility.

fastkml/links.py

mixins.pyAdjust mixin typing for ty/pyrefly +4/-6

Adjust mixin typing for ty/pyrefly

• Refines mixin annotations to reduce strict-checker noise while preserving runtime behavior.

fastkml/mixins.py

model.pyModernize model typing and signatures +61/-62

Modernize model typing and signatures

• Updates annotations (Optionals/unions) and related casts/narrowing to satisfy ty/pyrefly strict mode.

fastkml/model.py

network_link_control.pyUpdate NetworkLinkControl typing +31/-33

Update NetworkLinkControl typing

• Modernizes Optional/Union annotations to PEP 604 unions and refines signatures for strict checkers.

fastkml/network_link_control.py

overlays.pyModernize overlay typing +162/-164

Modernize overlay typing

• Updates overlay classes and constructors to PEP 604 unions and tightens related typing for strict checking.

fastkml/overlays.py

registry.pyModernize registry Protocols and defaults +3/-4

Modernize registry Protocols and defaults

• Updates registry Protocol signatures to PEP 604 unions and refines optional/default fields to satisfy strict type checking.

fastkml/registry.py

styles.pyModernize style typing +97/-99

Modernize style typing

• Converts Optional/Union usage across styles to PEP 604 unions and applies minor adjustments to satisfy strict checkers.

fastkml/styles.py

times.pyModernize time typing +17/-19

Modernize time typing

• Updates time-related classes and signatures to PEP 604 unions for ty/pyrefly strict mode.

fastkml/times.py

views.pyModernize view typing +78/-79

Modernize view typing

• Updates view classes and signatures to PEP 604 unions and refines annotations for strict checker compatibility.

fastkml/views.py

Tests (27) +384 / -427
base.pyUpdate test typing to PEP 604 unions +1/-1

Update test typing to PEP 604 unions

• Migrates legacy Optional/Union annotations to X | None / X | Y syntax to align with the new typing baseline.

tests/base.py

config_test.pyAdjust config tests for stricter typing +3/-2

Adjust config tests for stricter typing

• Updates test annotations and minor constructs to satisfy ty/pyrefly strict checking expectations.

tests/config_test.py

data_test.pyAdjust data tests for stricter typing +2/-2

Adjust data tests for stricter typing

• Modernizes typing annotations and minor test code to align with the refactored library types.

tests/data_test.py

boundaries_test.pyModernize boundary test type annotations +1/-3

Modernize boundary test type annotations

• Replaces Union[...] typing with PEP 604 unions for boundary class parameters.

tests/geometries/boundaries_test.py

functions_test.pyModernize geometry function tests typing +1/-1

Modernize geometry function tests typing

• Updates test annotations to modern union syntax for strict checker compatibility.

tests/geometries/functions_test.py

geometry_test.pyAlign geometry unit tests with updated typing +9/-5

Align geometry unit tests with updated typing

• Updates annotations and minor test code paths to reflect refactored geometry typing and stricter checking.

tests/geometries/geometry_test.py

point_test.pyModernize point test typing +1/-1

Modernize point test typing

• Migrates Optional/Union annotations to PEP 604 unions and adjusts tests for strict checking.

tests/geometries/point_test.py

data_test.pyModernize gx data tests typing +2/-2

Modernize gx data tests typing

• Updates annotations to modern union syntax to match gx typing changes.

tests/gx/data_test.py

helper_test.pyModernize helper tests typing +1/-1

Modernize helper tests typing

• Updates typing annotations and minor patterns to satisfy ty/pyrefly strict mode.

tests/helper_test.py

atom_test.pyModernize Atom property tests typing +9/-11

Modernize Atom property tests typing

• Migrates hypothesis test annotations to PEP 604 unions and adjusts signatures to match updated library types.

tests/hypothesis/atom_test.py

data_test.pyModernize data property tests typing +20/-22

Modernize data property tests typing

• Updates hypothesis tests to modern union syntax and adapts patterns that are more strictly checked under ty/pyrefly.

tests/hypothesis/data_test.py

feature_test.pyModernize feature property tests typing +24/-33

Modernize feature property tests typing

• Migrates annotations to PEP 604 unions and adds narrowing/typing adjustments as needed for strict checkers.

tests/hypothesis/feature_test.py

geometry_test.pyModernize geometry property tests typing +81/-89

Modernize geometry property tests typing

• Converts many Union/Optional annotations to PEP 604 unions and aligns generated inputs with updated geometry typing constraints.

tests/hypothesis/geometry_test.py

data_test.pyModernize gx data property tests typing +7/-8

Modernize gx data property tests typing

• Updates hypothesis tests to PEP 604 unions for strict checker compatibility.

tests/hypothesis/gx/data_test.py

track_test.pyModernize gx track property tests typing +10/-11

Modernize gx track property tests typing

• Migrates annotations to modern unions and adjusts tests to match stricter typing behavior.

tests/hypothesis/gx/track_test.py

kml_test.pyModernize KML property tests typing +1/-1

Modernize KML property tests typing

• Updates typing annotations to match the updated parse/validate APIs under strict checking.

tests/hypothesis/kml_test.py

links_test.pyModernize links property tests typing +11/-13

Modernize links property tests typing

• Migrates Optional/Union annotations to PEP 604 unions to match updated link types.

tests/hypothesis/links_test.py

model_test.pyModernize model property tests typing +30/-31

Modernize model property tests typing

• Updates typing annotations and minor test code to align with model typing refactors.

tests/hypothesis/model_test.py

multi_geometry_test.pyModernize multi-geometry property tests typing +3/-3

Modernize multi-geometry property tests typing

• Updates test annotations to PEP 604 unions and aligns with refactored multigeometry typing.

tests/hypothesis/multi_geometry_test.py

network_link_control_test.pyModernize NetworkLinkControl property tests typing +11/-14

Modernize NetworkLinkControl property tests typing

• Migrates annotations to modern unions and adjusts tests for strict type checking.

tests/hypothesis/network_link_control_test.py

overlay_test.pyModernize overlay property tests typing +41/-46

Modernize overlay property tests typing

• Updates hypothesis tests to PEP 604 unions to match overlay typing changes.

tests/hypothesis/overlay_test.py

style_test.pyModernize style property tests typing +64/-72

Modernize style property tests typing

• Migrates annotations to modern unions and adjusts tests to satisfy strict typing expectations.

tests/hypothesis/style_test.py

times_test.pyModernize time property tests typing +7/-9

Modernize time property tests typing

• Updates Optional/Union annotations to PEP 604 unions and aligns with time typing refactors.

tests/hypothesis/times_test.py

views_test.pyModernize view property tests typing +37/-39

Modernize view property tests typing

• Migrates annotations to PEP 604 unions and aligns tests with updated view typing.

tests/hypothesis/views_test.py

overlays_test.pyModernize overlay unit tests typing +2/-2

Modernize overlay unit tests typing

• Updates typing annotations to modern unions to match overlay typing changes.

tests/overlays_test.py

registry_test.pyFix get_kwarg stub and modernize registry test typing +4/-4

Fix get_kwarg stub and modernize registry test typing

• Updates signatures to PEP 604 unions and replaces an empty-body placeholder with a real return, satisfying stricter checkers and lint rules.

tests/registry_test.py

times_test.pyModernize time unit tests typing +1/-1

Modernize time unit tests typing

• Updates typing annotations to PEP 604 unions for strict checker compatibility.

tests/times_test.py

Documentation (3) +290 / -4
README.rstDocument Python 3.10+ support and modern install examples +13/-4

Document Python 3.10+ support and modern install examples

• Updates stated minimum Python version to 3.10+. Refreshes installation instructions, including uv examples alongside existing guidance.

README.rst

HISTORY.rstChangelog entry for dropping Python 3.9 +2/-0

Changelog entry for dropping Python 3.9

• Adds a note under the unreleased section that Python 3.9 support is removed.

docs/HISTORY.rst

mypy-to-ty-and-pyrefly-migration.mdAdd internal migration guide for mypy → ty/pyrefly +275/-0

Add internal migration guide for mypy → ty/pyrefly

• Adds a detailed playbook describing the migration process, key pitfalls, and recommended configuration patterns. Captures lessons learned about optional backends and per-checker stub quirks.

docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md

Other (5) +83 / -50
run-all-tests.ymlUpdate CI to Python 3.10+ and run ty/pyrefly +5/-4

Update CI to Python 3.10+ and run ty/pyrefly

• Removes Python 3.9 from CPython/PyPy matrices. Replaces the mypy step with ty and pyrefly checks in CI.

.github/workflows/run-all-tests.yml

.pre-commit-config.yamlReplace mypy hook with local ty/pyrefly hooks +13/-4

Replace mypy hook with local ty/pyrefly hooks

• Drops mirrors-mypy and adds system-language local hooks for ty and pyrefly. Runs both checkers over fastkml and tests without filename passing.

.pre-commit-config.yaml

.sourcery.yamlRaise configured Python version to 3.10 +1/-1

Raise configured Python version to 3.10

• Updates Sourcery’s configured Python version to match the new supported baseline.

.sourcery.yaml

pyproject.tomlDrop mypy; add ty/pyrefly + config; bump Python; pin hypothesis +63/-40

Drop mypy; add ty/pyrefly + config; bump Python; pin hypothesis

• Raises requires-python to >=3.10 and removes mypy configuration. Adds ty and pyrefly (and lxml-stubs) to typing extras and introduces strict tool configuration with scoped overrides for tests/examples; pins hypothesis <6.156 due to wheel availability constraints for newer Python versions.

pyproject.toml

tox.iniRaise flake8 min_python_version to 3.10 +1/-1

Raise flake8 min_python_version to 3.10

• Updates the configured minimum Python version for flake8 tooling to reflect Python 3.10+ support.

tox.ini

@qodo-code-review

qodo-code-review Bot commented Jul 5, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Track truncates mismatched inputs ✓ Resolved 🐞 Bug ≡ Correctness
Description
fastkml.gx.Track.__init__ builds track_items using zip(whens, coords, strict=False), which
truncates to the shorter iterable and silently drops extra timestamps or coordinates. This
contradicts the stated invariant that whens/coords are mandatory pairs and can cause silent data
loss.
Code

fastkml/gx/track.py[R192-203]

        if not track_items and whens and coords:
+            # `whens`/`coords` form mandatory pairs (a track item always
+            # needs a timestamp and a coordinate); `angles` may be shorter
+            # and defaults to Angle()'s all-zero heading/tilt/roll.
            track_items = [
                TrackItem(
-                    when=cast("KmlDateTime", when),
+                    when=when,
                    coord=geo.Point(*coord),
-                    angle=Angle(*angle),
-                )
-                for when, coord, angle in zip_longest(
-                    whens,
-                    coords,
-                    angles,
-                    fillvalue=(),
+                    angle=Angle(*angles[i]) if i < len(angles) else Angle(),
                )
+                for i, (when, coord) in enumerate(zip(whens, coords, strict=False))
            ]
Evidence
The implementation explicitly uses zip(..., strict=False) and contains a comment asserting
whens/coords are mandatory pairs; since zip truncates, any mismatch will be silently ignored
rather than surfaced to the caller.

fastkml/gx/track.py[188-203]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Track item construction uses `zip(whens, coords, strict=False)`, which truncates to the shortest iterable and silently discards unmatched items. For mandatory pairs, mismatched lengths should be rejected explicitly.

## Issue Context
The code comment states `whens`/`coords` are mandatory pairs, but `strict=False` does not enforce that.

## Fix Focus Areas
- fastkml/gx/track.py[192-203]

### Suggested change
- Prefer `zip(whens, coords, strict=True)` (Python 3.10+) to raise on length mismatch, or
- Convert to lists and compare lengths, raising `ValueError` with a clear message before constructing `TrackItem`s.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Typecheck command mismatch ✓ Resolved 🐞 Bug ☼ Reliability
Description
The GitHub Actions typecheck step invokes ty/pyrefly without the check subcommand, which is
inconsistent with the repo’s own documented/pre-commit invocation and may run the wrong command or
fail depending on the CLI defaults. This risks making the static-tests job unreliable and drifting
CI behavior from local checks.
Code

.github/workflows/run-all-tests.yml[R98-101]

      - name: Typecheck
        run: |
-          mypy fastkml tests
+          ty fastkml tests
+          pyrefly fastkml tests
Evidence
The workflow currently runs ty fastkml tests and pyrefly fastkml tests, while both the repo’s
migration guide and the pre-commit hooks explicitly use ty check ... and pyrefly check ...,
demonstrating an inconsistency introduced in CI command wiring.

.github/workflows/run-all-tests.yml[98-101]
.pre-commit-config.yaml[65-78]
docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md[36-40]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflow runs:
- `ty fastkml tests`
- `pyrefly fastkml tests`
…but the repo documentation and pre-commit hooks use `ty check ...` / `pyrefly check ...`. Align CI with the documented invocation to avoid CI drift and potential failures.

## Issue Context
- Pre-commit hooks already enforce `ty check` and `pyrefly check`.
- The migration guide in this repo also documents `ty check`/`pyrefly check`.

## Fix Focus Areas
- .github/workflows/run-all-tests.yml[98-101]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread .github/workflows/run-all-tests.yml Outdated
Comment thread fastkml/gx/track.py

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates FastKML’s supported/tooling baseline to resolve typechecking failures by migrating from mypy to Astral’s ty and Meta’s pyrefly, while also dropping Python 3.9 support. It modernizes typing across the library/tests to Python 3.10+ syntax, updates CI/pre-commit integration, and adds migration documentation for future maintenance.

Changes:

  • Drop Python 3.9 support and move the minimum supported version to Python 3.10 across tooling/config/docs.
  • Replace mypy configuration and CI checks with ty + pyrefly, plus targeted ignore/comment updates for the new checkers.
  • Refactor/modernize many type annotations (PEP 604 unions, overloads, casts) to satisfy the new typecheckers.

Reviewed changes

Copilot reviewed 63 out of 63 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tox.ini Bumps Flake8 min Python version to 3.10.
pyproject.toml Drops mypy config; adds ty/pyrefly config and typing deps; raises requires-python to 3.10.
.github/workflows/run-all-tests.yml Removes 3.9 from test matrices; replaces mypy step with ty + pyrefly.
.pre-commit-config.yaml Replaces mypy hook with local ty + pyrefly hooks.
.sourcery.yaml Updates Sourcery Python version to 3.10.
README.rst Updates stated Python support and refreshes installation instructions (uv/pip/conda).
docs/HISTORY.rst Notes dropping Python 3.9 support in unreleased changelog.
docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md Adds an internal guide documenting the mypy → ty/pyrefly migration approach and pitfalls.
fastkml/config.py Uses TYPE_CHECKING to type against lxml stubs while keeping runtime fallback to stdlib etree.
fastkml/types.py Uses TYPE_CHECKING to alias Element to lxml.etree._Element for static analysis; keeps runtime Protocol fallback.
fastkml/base.py Updates signatures/return typing and reduces unnecessary casts for etree operations.
fastkml/kml.py Fixes validation typing (validate root element), improves tag typing via cast, and modernizes annotations.
fastkml/kml_base.py Tightens base object typing around id/target_id and modernizes init annotations.
fastkml/registry.py Modernizes Optional typing to PEP 604 unions and updates registry container typing.
fastkml/helpers.py Modernizes typing and adds checker-specific ignore comments where needed.
fastkml/utils.py Adds overloads to find/find_all for better type narrowing and modernizes typing.
fastkml/validator.py Adjusts typing around lxml error logs and ensures validate() works with root elements.
fastkml/times.py Modernizes typing and tightens KmlDateTime.parse return type.
fastkml/mixins.py Modernizes time-related union typing for mixins.
fastkml/views.py Modernizes view object annotations and constructor parameter types.
fastkml/styles.py Modernizes typing across styles and style selectors, including union aliases.
fastkml/links.py Modernizes link/icon typing to PEP 604 unions.
fastkml/containers.py Modernizes container typing and removes unnecessary type ignores.
fastkml/features.py Modernizes feature typing, including geometry unions and constructor annotations.
fastkml/data.py Modernizes schema/data typing and updates union usage.
fastkml/model.py Modernizes model-related typing and property return types.
fastkml/overlays.py Modernizes overlay typing and constructor signatures.
fastkml/network_link_control.py Modernizes typing for update/network link control structures.
fastkml/geometry.py Refactors coordinate formatting, modernizes geometry typing, and tightens multigeometry creation typing.
fastkml/gx/track.py Modernizes typing and refactors track-item construction logic.
fastkml/gx/data.py Modernizes typing for gx simple array field/data.
fastkml/atom.py Modernizes Atom element typing.
fastkml/abstract_geometry.py Modernizes base geometry typing (`AltitudeMode
examples/transform_cascading_style.py Modernizes typing and adds a runtime assertion for narrowing in the example.
examples/simple_example.py Adds type annotations to make the example typechecker-friendly.
examples/read_kml.py Fixes example to pass str (not bytes) to from_string.
tests/base.py Updates optional lxml import style for typing/pyrefly compatibility.
tests/config_test.py Updates optional lxml import style and avoids mutating a shared dict literal.
tests/registry_test.py Updates Optional→union typing and provides a concrete get_kwarg() body for typechecking.
tests/times_test.py Adds ty ignore to a deliberately-invalid construction.
tests/overlays_test.py Fixes a test to use AltitudeMode enum instead of a raw string.
tests/helper_test.py Switches Callable import to collections.abc.
tests/data_test.py Adds ty ignore to **dict splat cases that typecheckers can’t precisely validate.
tests/gx/data_test.py Adds ty ignore to **dict splat cases that typecheckers can’t precisely validate.
tests/geometries/point_test.py Adds ty ignore for deliberately-invalid geometry construction.
tests/geometries/geometry_test.py Replaces len(x) # type: ignore with explicit narrowing assertions; adds ty ignore in invalid-input test.
tests/geometries/functions_test.py Switches Callable import to collections.abc.
tests/geometries/boundaries_test.py Modernizes union typing in helper signature.
tests/hypothesis/atom_test.py Modernizes Optional→union typing for Hypothesis tests.
tests/hypothesis/data_test.py Modernizes Optional/Union typing for Hypothesis tests.
tests/hypothesis/feature_test.py Modernizes Optional/Union typing for Hypothesis tests.
tests/hypothesis/geometry_test.py Modernizes typing and union aliases for Hypothesis geometry tests.
tests/hypothesis/kml_test.py Adds ty ignore for a known list-item typing mismatch.
tests/hypothesis/links_test.py Modernizes typing for Hypothesis link/icon tests.
tests/hypothesis/model_test.py Modernizes typing for Hypothesis model tests.
tests/hypothesis/multi_geometry_test.py Uses zip(..., strict=False) for typechecker compatibility under 3.10+.
tests/hypothesis/network_link_control_test.py Modernizes typing for Hypothesis network link control tests.
tests/hypothesis/overlay_test.py Modernizes typing for Hypothesis overlay tests.
tests/hypothesis/style_test.py Modernizes typing and unions for Hypothesis style tests.
tests/hypothesis/times_test.py Modernizes typing for Hypothesis time tests.
tests/hypothesis/views_test.py Modernizes typing for Hypothesis view tests.
tests/hypothesis/gx/data_test.py Modernizes typing for Hypothesis gx data tests.
tests/hypothesis/gx/track_test.py Modernizes typing for Hypothesis gx track tests.

Comment thread fastkml/validator.py
Comment on lines +94 to +95
matches = cast("list[Element]", element.xpath(error_entry.path))
parent = matches[0].getparent()
Comment thread fastkml/gx/track.py Outdated
@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown

Greptile Summary

This PR drops Python 3.9 support, replaces mypy with ty and pyrefly for static analysis, and modernises all type annotations from Optional[X]/Union[X, Y] to X | None/X | Y syntax. Two latent bugs are also fixed along the way.

  • Type annotation modernisation (63 files): mechanical replacement of Optional/Union with PEP 604 union syntax, removal of # type: ignore[attr-defined] comments replaced by proper cast() calls, and @overload signatures added to find_all/find in utils.py for correct generic return types.
  • fastkml/validator.py correctness fixes: etree.parse(file).getroot() is now used so the root _Element (not the _ElementTree) is passed to XMLSchema.assert_(), and IndexError is added to the except clause in handle_validation_error to guard against empty XPath match lists.
  • fastkml/types.py / fastkml/config.py dual-mode imports: Element resolves to lxml.etree._Element under TYPE_CHECKING (giving accurate lxml-native attribute types) and to the custom Protocol at runtime; same pattern applied to the etree module in config.py.

Confidence Score: 5/5

Safe to merge — changes are primarily mechanical type-annotation modernisation with two small correctness fixes in validator.py that were pre-existing latent bugs.

The bulk of the diff is semantically equivalent annotation rewrites. The two functional changes in validator.py (.getroot() and adding IndexError) are genuine improvements with no regression risk. The dual TYPE_CHECKING import pattern for Element and etree is a well-understood idiom. No public API shapes change.

No files require special attention. fastkml/validator.py has the only non-trivial logic changes and they are straightforward improvements.

Important Files Changed

Filename Overview
fastkml/validator.py Two correctness fixes: .getroot() on the parsed tree before passing to assert_(), and IndexError added to the except clause in handle_validation_error; both were latent bugs exposed by tighter typing.
fastkml/types.py Switches Element from a runtime Protocol to lxml.etree._Element under TYPE_CHECKING; Protocol text field corrected to str
fastkml/config.py Moves the lxml import under TYPE_CHECKING so static analysis always resolves lxml stubs; runtime fallback logic is preserved unchanged.
fastkml/utils.py Adds @overload signatures to find_all and find so callers get typed Generator[_T] / _T
fastkml/geometry.py Extracts _create_homogeneous_multigeometry and _format_coordinates helpers; removes Self forward-reference in kml_geometries in favour of "KMLGeometryType" string annotation; all logic is semantically equivalent to before.
fastkml/helpers.py Replaces bare iteration over classes with cast("tuple[type[_XMLObject], ...]", classes) throughout; removes all # type: ignore[attr-defined] comments that were suppressing valid cast-related false positives.
pyproject.toml Replaces mypy config section with [tool.pyrefly] and [tool.ty] sections; pins hypothesis<6.156 pending a cp315 wheel; adds per-package ruff ignores; drops Python 3.9 classifier.
.github/workflows/run-all-tests.yml Drops Python 3.9/PyPy 3.9 from the test matrix and replaces mypy fastkml tests with ty check and pyrefly check in the typecheck step.
README.rst Updates Python version requirement text to 3.10+; restructures the Installation section to lead with uv add and adds a Using pip subsection. Inclusion marker is correctly positioned immediately above the Introduction heading.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[TYPE_CHECKING?] -->|Yes| B["Element = lxml.etree._Element\netree = lxml.etree"]
    A -->|No – runtime| C{lxml available?}
    C -->|Yes| D["etree = lxml.etree\nElement = Protocol"]
    C -->|No| E["etree = xml.etree.ElementTree\nElement = Protocol"]

    F["validate(file_to_validate=...)"] --> G["etree.parse(file).getroot()"]
    G --> H["XMLSchema.assert_(element: _Element)"]
    H -->|passes| I["return True"]
    H -->|fails| J["handle_validation_error()"]
    J --> K["cast Iterable[_LogEntry]"]
    K --> L["element.xpath(path)"]
    L -->|IndexError or XPathEvalError| M["parent = element"]
    L -->|ok| N["parent = matches[0].getparent()"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[TYPE_CHECKING?] -->|Yes| B["Element = lxml.etree._Element\netree = lxml.etree"]
    A -->|No – runtime| C{lxml available?}
    C -->|Yes| D["etree = lxml.etree\nElement = Protocol"]
    C -->|No| E["etree = xml.etree.ElementTree\nElement = Protocol"]

    F["validate(file_to_validate=...)"] --> G["etree.parse(file).getroot()"]
    G --> H["XMLSchema.assert_(element: _Element)"]
    H -->|passes| I["return True"]
    H -->|fails| J["handle_validation_error()"]
    J --> K["cast Iterable[_LogEntry]"]
    K --> L["element.xpath(path)"]
    L -->|IndexError or XPathEvalError| M["parent = element"]
    L -->|ok| N["parent = matches[0].getparent()"]
Loading

Reviews (3): Last reviewed commit: "Fix from_string crashing on XML strings ..." | Re-trigger Greptile

…om:cleder/fastkml into 503-typechecking-fails-with-ty-and-pyrefly
Comment thread fastkml/helpers.py Outdated

@llamapreview llamapreview Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Code Review by LlamaPReview

🎯 TL;DR & Recommendation

Recommendation: Request Changes

This PR modernizes type annotations by migrating from mypy to ty and pyrefly, but introduces a critical type inconsistency in _BaseObject that will cause false negatives in type checking and potential runtime AttributeErrors on attributes that can be None.

🌟 Strengths

  • Comprehensive type annotation modernization across the codebase (switching from Optional to X | None).
  • Added a thorough migration guide (docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md).

⚡ Key Risks & Improvements (P1)

  • fastkml/kml_base.py: Class-level id and target_id are declared as str (never None), contradicting the __init__ which allows None, leading to false negative type-checking results and unsafe access assuming non-None.

📈 Risk Diagram

This diagram illustrates the risk of a type annotation mismatch in _BaseObject leading to unsafe attribute access.

sequenceDiagram
    participant BO as _BaseObject
    participant TC as Type Checker
    participant DC as Downstream Code
    BO->>BO: Declares id: str (class-level)
    BO->>BO: __init__ accepts id: str|None=None
    Note over BO: R1(P1): Annotation mismatch<br/>id can be None at runtime
    TC->>BO: Reads annotation: id: str
    TC->>DC: Infers id is never None
    DC->>DC: Accesses obj.id.upper() without None check
    Note over DC: Potential AttributeError if id is None
Loading

💡 Have feedback? We'd love to hear it in our GitHub Discussions.
✨ This review was generated by LlamaPReview Advanced, which is free for all open-source projects. Learn more.

Comment thread fastkml/kml_base.py
Comment on lines 57 to +60
_default_nsid = config.KML

id = None
target_id = None
id: str
target_id: str

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 | Confidence: High

The class-level type annotation declares id and target_id as str (never None), but the __init__ method (unchanged in this PR, signature shown below) still accepts id: str | None = None and target_id: str | None = None.

This inconsistency means type checkers (both ty and pyrefly, which the PR is migrating to) will infer that _BaseObject.id is always a str, allowing unsafe access without None checks. At runtime, these attributes can still be None (when the corresponding constructor argument is omitted or explicitly set to None), leading to AttributeError on methods like getattr or direct attribute access that assume a non‑None string.

All classes inheriting from _BaseObject (e.g., _Geometry, Placemark, Document, Link) share this incorrect annotation, so the ripple effect is broad. The fix is straightforward: change class‑level annotations to str | None.

Suggested change
_default_nsid = config.KML
id = None
target_id = None
id: str
target_id: str
class _BaseObject(_XMLObject):
...
id: str | None = None
target_id: str | None = None

codescene-access[bot]

This comment was marked as outdated.

@codecov

codecov Bot commented Jul 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.64789% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 99.98%. Comparing base (cceda15) to head (48390cc).

Files with missing lines Patch % Lines
tests/registry_test.py 50.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #526      +/-   ##
===========================================
- Coverage   100.00%   99.98%   -0.02%     
===========================================
  Files           80       80              
  Lines         6579     6544      -35     
  Branches       164      166       +2     
===========================================
- Hits          6579     6543      -36     
- Misses           0        1       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
fastkml/gx/track.py (1)

188-203: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Enforce matching whens/coords lengths zip(..., strict=False) will silently drop any extra timestamps or coordinates here, so a caller can lose track points without an error; strict=True matches the stated pairing invariant.

🔧 Suggested fix
                 TrackItem(
                     when=when,
                     coord=geo.Point(*coord),
                     angle=Angle(*angles[i]) if i < len(angles) else Angle(),
                 )
-                for i, (when, coord) in enumerate(zip(whens, coords, strict=False))
+                for i, (when, coord) in enumerate(zip(whens, coords, strict=True))
             ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastkml/gx/track.py` around lines 188 - 203, The TrackItem construction in
track.py currently uses zip(..., strict=False), which can silently drop extra
values and violate the required one-to-one pairing between whens and coords.
Update the TrackItem creation logic in the track-building branch to enforce
matching lengths by using strict pairing behavior in that zip call, so any
mismatch raises instead of truncating data. Keep the existing invariant checks
around track_items, whens, and coords consistent with the TrackItem/Angle
construction path.
🧹 Nitpick comments (2)
.pre-commit-config.yaml (1)

65-78: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider pinning ty via its official pre-commit hook.

language: system hooks rely on ty/pyrefly already being installed in the developer's environment (via the typing extra), so the exact tool version isn't pinned by pre-commit itself — it can silently drift from what CI uses. Astral publishes an official astral-sh/ty-pre-commit repo hook with a pinned rev, which would give reproducible versioning across contributors' machines without requiring a system install.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.pre-commit-config.yaml around lines 65 - 78, The local pre-commit hook
setup for ty is using language: system, so its version depends on whatever is
installed on each developer machine. Update the hook definition to use ty’s
official pre-commit repository with a pinned rev instead of the local
system-based entry, and keep the existing pyrefly hook only if it still needs to
remain system-managed. Refer to the ty hook block in the pre-commit
configuration when making the change.
examples/simple_example.py (1)

7-7: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Give the helper a protocol for features and name. object still won’t type-check with the recursive element.features / feature.name access, and the getattr(..., "features", None) guard doesn’t narrow it for the type checker. A tiny local Protocol here would fit better than object or Any.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/simple_example.py` at line 7, The helper signature in
print_child_features still uses object, which doesn’t type-check for the
recursive element.features and feature.name access. Replace the broad object
annotation with a small local Protocol that declares features and name so the
type checker can understand the recursive traversal, and keep the guard logic
aligned with that protocol instead of relying on getattr narrowing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md`:
- Around line 110-124: The pyrefly casing guidance is too broad and should be
narrowed to the specific option naming patterns used in the docs. Update the
wording in the migration guide near the pyrefly TOML examples to explicitly
distinguish the hyphenated keys that remain hyphenated in `pyproject.toml` (for
example `ignore-missing-imports` and `sub-config`) from the underscore-based
keys like `replace_imports_with_any`. Also remove or revise the claim that the
later stub-package example is mismatched, since it already follows the same key
form as the project config.

In `@fastkml/types.py`:
- Around line 58-59: Update the Element protocol’s get method in types.py so it
matches the underlying XML APIs by returning str | None instead of only str.
Adjust the get signature on Element to allow a missing attribute result, and
keep the method name and protocol definition consistent with
lxml.etree._Element.get and xml.etree.ElementTree.Element.get.

In `@pyproject.toml`:
- Around line 139-153: The Pyrefly config is using an underscore key that won’t
be recognized, so the import-to-Any rule is not applied. Update the
[tool.pyrefly] section to use the hyphenated setting name for
replace-imports-with-any, keeping the existing lxml.* value, and leave the rest
of the pyrefly options unchanged.

In `@README.rst`:
- Around line 142-149: The pip install example omits the optional lxml
dependency and is inconsistent with the uv example. Update the installation
snippet in README.rst so the pip command matches the package extra used in the
uv example, and keep the reference consistent with the fastkml installation
guidance.

---

Outside diff comments:
In `@fastkml/gx/track.py`:
- Around line 188-203: The TrackItem construction in track.py currently uses
zip(..., strict=False), which can silently drop extra values and violate the
required one-to-one pairing between whens and coords. Update the TrackItem
creation logic in the track-building branch to enforce matching lengths by using
strict pairing behavior in that zip call, so any mismatch raises instead of
truncating data. Keep the existing invariant checks around track_items, whens,
and coords consistent with the TrackItem/Angle construction path.

---

Nitpick comments:
In @.pre-commit-config.yaml:
- Around line 65-78: The local pre-commit hook setup for ty is using language:
system, so its version depends on whatever is installed on each developer
machine. Update the hook definition to use ty’s official pre-commit repository
with a pinned rev instead of the local system-based entry, and keep the existing
pyrefly hook only if it still needs to remain system-managed. Refer to the ty
hook block in the pre-commit configuration when making the change.

In `@examples/simple_example.py`:
- Line 7: The helper signature in print_child_features still uses object, which
doesn’t type-check for the recursive element.features and feature.name access.
Replace the broad object annotation with a small local Protocol that declares
features and name so the type checker can understand the recursive traversal,
and keep the guard logic aligned with that protocol instead of relying on
getattr narrowing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: afe1f0ab-34ac-4c21-9969-c5c12b78369b

📥 Commits

Reviewing files that changed from the base of the PR and between cceda15 and 29bc15c.

📒 Files selected for processing (63)
  • .github/workflows/run-all-tests.yml
  • .pre-commit-config.yaml
  • .sourcery.yaml
  • README.rst
  • docs/HISTORY.rst
  • docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md
  • examples/read_kml.py
  • examples/simple_example.py
  • examples/transform_cascading_style.py
  • fastkml/abstract_geometry.py
  • fastkml/atom.py
  • fastkml/base.py
  • fastkml/config.py
  • fastkml/containers.py
  • fastkml/data.py
  • fastkml/features.py
  • fastkml/geometry.py
  • fastkml/gx/data.py
  • fastkml/gx/track.py
  • fastkml/helpers.py
  • fastkml/kml.py
  • fastkml/kml_base.py
  • fastkml/links.py
  • fastkml/mixins.py
  • fastkml/model.py
  • fastkml/network_link_control.py
  • fastkml/overlays.py
  • fastkml/registry.py
  • fastkml/styles.py
  • fastkml/times.py
  • fastkml/types.py
  • fastkml/utils.py
  • fastkml/validator.py
  • fastkml/views.py
  • pyproject.toml
  • tests/base.py
  • tests/config_test.py
  • tests/data_test.py
  • tests/geometries/boundaries_test.py
  • tests/geometries/functions_test.py
  • tests/geometries/geometry_test.py
  • tests/geometries/point_test.py
  • tests/gx/data_test.py
  • tests/helper_test.py
  • tests/hypothesis/atom_test.py
  • tests/hypothesis/data_test.py
  • tests/hypothesis/feature_test.py
  • tests/hypothesis/geometry_test.py
  • tests/hypothesis/gx/data_test.py
  • tests/hypothesis/gx/track_test.py
  • tests/hypothesis/kml_test.py
  • tests/hypothesis/links_test.py
  • tests/hypothesis/model_test.py
  • tests/hypothesis/multi_geometry_test.py
  • tests/hypothesis/network_link_control_test.py
  • tests/hypothesis/overlay_test.py
  • tests/hypothesis/style_test.py
  • tests/hypothesis/times_test.py
  • tests/hypothesis/views_test.py
  • tests/overlays_test.py
  • tests/registry_test.py
  • tests/times_test.py
  • tox.ini

Comment on lines +110 to +124
### 2. pyrefly's TOML keys are snake_case even though its CLI flags are kebab-case

This is the single most time-consuming mistake to make. `pyrefly check --replace-imports-with-any 'lxml.*'` works from the CLI. Writing the "obvious" TOML equivalent:

```toml
[tool.pyrefly]
replace-imports-with-any = ["lxml.*"] # WRONG — silently different key
```

...does not raise an error from `pyrefly check` in some code paths, but it **does** hard-fail with `pyrefly dump-config` (`unknown variant 'replace-imports-with-any'... Fatal configuration error`), and depending on invocation order this can also break `pyrefly check` itself later. The correct TOML key uses underscores:

```toml
[tool.pyrefly]
replace_imports_with_any = ["lxml.*"]
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Files of interest ==\n'
git ls-files 'docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md' 'pyproject.toml' '**/pyproject.toml' | sed 's#^`#-` #'

printf '\n== Guide excerpts ==\n'
nl -ba docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md | sed -n '100,190p'

printf '\n== pyproject.toml mentions of pyrefly ==\n'
rg -n --no-heading 'pyrefly|ignore-missing-imports|replace-imports-with-any|replace_imports_with_any|stub' pyproject.toml . -g 'pyproject.toml' -g '!**/node_modules/**' || true

Repository: cleder/fastkml

Length of output: 315


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Guide excerpts ==\n'
nl -ba docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md | sed -n '100,190p'

printf '\n== Root pyproject.toml ==\n'
if [ -f pyproject.toml ]; then
  nl -ba pyproject.toml | sed -n '1,260p'
fi

printf '\n== Repo-wide pyrefly key search ==\n'
rg -n --no-heading 'ignore-missing-imports|replace-imports-with-any|replace_imports_with_any|tool\.pyrefly|pyrefly' . -g 'pyproject.toml' -g '*.toml' -g '*.md' || true

Repository: cleder/fastkml

Length of output: 214


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Guide excerpt ==\n'
sed -n '100,190p' docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md

printf '\n== Root pyproject.toml excerpt ==\n'
sed -n '1,260p' pyproject.toml

printf '\n== Repo-wide pyrefly-related mentions ==\n'
rg -n --no-heading 'ignore-missing-imports|replace-imports-with-any|replace_imports_with_any|tool\.pyrefly|pyrefly' . -g 'pyproject.toml' -g '*.toml' -g '*.md' || true

Repository: cleder/fastkml

Length of output: 25002


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

for path in [Path('docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md'), Path('pyproject.toml')]:
    print(f"\n== {path} ==")
    text = path.read_text()
    if path.name == 'mypy-to-ty-and-pyrefly-migration.md':
        lines = text.splitlines()
        for i in range(100, min(190, len(lines))):
            print(f"{i+1:4d}: {lines[i]}")
    else:
        for i, line in enumerate(text.splitlines()[:260], 1):
            print(f"{i:4d}: {line}")
PY

printf '\n== pyrefly keys in TOML files ==\n'
python3 - <<'PY'
from pathlib import Path
import re

patterns = [
    re.compile(r'ignore-missing-imports'),
    re.compile(r'replace-imports-with-any'),
    re.compile(r'replace_imports_with_any'),
    re.compile(r'^\s*\[tool\.pyrefly\]'),
    re.compile(r'pyrefly'),
]
for path in Path('.').rglob('*.toml'):
    text = path.read_text(errors='ignore')
    if any(p.search(text) for p in patterns):
        print(f'-- {path}')
        for i, line in enumerate(text.splitlines(), 1):
            if any(p.search(line) for p in patterns):
                print(f'{i}: {line}')
PY

Repository: cleder/fastkml

Length of output: 15218


Clarify the pyrefly key-casing rule. pyproject.toml mixes hyphenated options like ignore-missing-imports and sub-config with underscored options like replace_imports_with_any, so “TOML keys are snake_case” is too broad. Narrow this wording to the specific option names, and drop the claim that the later stub-package example is mismatched; it already uses the same key form as the project config.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/codedocs/guides/mypy-to-ty-and-pyrefly-migration.md` around lines 110 -
124, The pyrefly casing guidance is too broad and should be narrowed to the
specific option naming patterns used in the docs. Update the wording in the
migration guide near the pyrefly TOML examples to explicitly distinguish the
hyphenated keys that remain hyphenated in `pyproject.toml` (for example
`ignore-missing-imports` and `sub-config`) from the underscore-based keys like
`replace_imports_with_any`. Also remove or revise the claim that the later
stub-package example is mismatched, since it already follows the same key form
as the project config.

Comment thread fastkml/types.py
Comment on lines +58 to +59
def get(self, tag: str) -> str:
"""Get the value of the tag."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

lxml etree _Element get method return type Optional str

💡 Result:

The lxml.etree._Element.get(key, default=None) method returns the value of an attribute if it exists, or the provided default value if it does not [1][2][3]. In practice, the return type is typically a string (str) if the attribute is found [4], or the value provided in the default argument if the attribute is absent [1][2]. Since the default parameter defaults to None, the method effectively returns an Optional[str] (a string or None) [1][2][3]. This behavior aligns with the standard Python ElementTree API, which lxml implements [5]. When an attribute does not exist and no explicit default is provided, the method returns None [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant file with line numbers
sed -n '1,140p' fastkml/types.py | cat -n

echo
echo "--- Usage search for Element.get in the codebase ---"
rg -n "\.get\(" fastkml | head -n 80

echo
echo "--- Search for Element protocol/type alias references ---"
rg -n "TYPE_CHECKING|Protocol|Element =" fastkml/types.py fastkml -g '*.py'

Repository: cleder/fastkml

Length of output: 6120


Change Element.get() to return str | None
lxml.etree._Element.get() and xml.etree.ElementTree.Element.get() both return None when the attribute is missing, so this protocol should match that behavior.

🔧 Suggested fix
-        def get(self, tag: str) -> str:
+        def get(self, tag: str) -> str | None:
             """Get the value of the tag."""
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get(self, tag: str) -> str:
"""Get the value of the tag."""
def get(self, tag: str) -> str | None:
"""Get the value of the tag."""
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastkml/types.py` around lines 58 - 59, Update the Element protocol’s get
method in types.py so it matches the underlying XML APIs by returning str | None
instead of only str. Adjust the get signature on Element to allow a missing
attribute result, and keep the method name and protocol definition consistent
with lxml.etree._Element.get and xml.etree.ElementTree.Element.get.

Comment thread pyproject.toml
Comment on lines +139 to +153
[tool.pyrefly]
ignore-missing-imports = [
"*",
]
preset = "strict"
replace_imports_with_any = [
"lxml.*",
]
sub-config = [
{ errors = { missing-attribute = "ignore" }, matches = "examples/**/*" },
{ errors = { missing-attribute = "ignore" }, matches = "tests/**/*" },
]

[[tool.mypy.overrides]]
disable_error_code = "union-attr"
module = "tests.*"
[tool.pyrefly.errors]
missing-override-decorator = "ignore"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify pyrefly recognizes the configured key and dumps expected settings
pip show pyrefly 2>/dev/null || pip install pyrefly
pyrefly dump-config 2>&1 | grep -i -A2 "replace"

Repository: cleder/fastkml

Length of output: 443


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== pyproject excerpt ==\n'
sed -n '130,160p' pyproject.toml

printf '\n== pyrefly install/version ==\n'
python3 - <<'PY'
import importlib.metadata as m
try:
    print(m.version("pyrefly"))
except Exception as e:
    print(f"pyrefly version unavailable: {e}")
PY

printf '\n== pyrefly help ==\n'
pyrefly --help 2>&1 | sed -n '1,120p'

printf '\n== pyrefly dump-config (repo root) ==\n'
pyrefly dump-config 2>&1 | sed -n '1,220p'

Repository: cleder/fastkml

Length of output: 835


🏁 Script executed:

python3 - <<'PY'
import importlib.util, importlib.metadata as m, sys, os, json
print("pyrefly version:", m.version("pyrefly"))
spec = importlib.util.find_spec("pyrefly")
print("pyrefly module spec:", spec)
if spec and spec.origin:
    print("pyrefly origin:", spec.origin)
    print("pyrefly package dir:", os.path.dirname(spec.origin))
PY

python3 -m pyrefly --help 2>&1 | sed -n '1,120p'

python3 -m pyrefly dump-config 2>&1 | sed -n '1,240p'

Repository: cleder/fastkml

Length of output: 3188


🏁 Script executed:

#!/bin/bash
set -euo pipefail

PYREFLY_DIR="$(python3 - <<'PY'
import importlib.util
spec = importlib.util.find_spec("pyrefly")
print(spec.submodule_search_locations[0] if spec and spec.submodule_search_locations else "")
PY
)"

printf 'pyrefly dir: %s\n' "$PYREFLY_DIR"

printf '\n== candidate key occurrences ==\n'
grep -RIn --include='*.py' --include='*.toml' --include='*.md' \
  -E 'replace[_-]imports[_-]with[_-]any|ignore[_-]missing[_-]imports|sub[_-]config|missing[_-]override[_-]decorator' \
  "$PYREFLY_DIR" | sed -n '1,240p'

printf '\n== config-related files ==\n'
find "$PYREFLY_DIR" -maxdepth 3 \( -iname '*config*' -o -iname '*schema*' -o -iname '*toml*' -o -iname '*md' \) | sed -n '1,240p'

Repository: cleder/fastkml

Length of output: 257


🏁 Script executed:

#!/bin/bash
set -euo pipefail

PYREFLY_DIR="$(python3 - <<'PY'
import importlib.util
spec = importlib.util.find_spec("pyrefly")
print(spec.submodule_search_locations[0] if spec and spec.submodule_search_locations else "")
PY
)"

find "$PYREFLY_DIR" -maxdepth 4 | sed -n '1,240p'

Repository: cleder/fastkml

Length of output: 684


🌐 Web query:

pyrefly 1.1.1 configuration replace imports with any key spelling hyphen underscore dump-config migration guide

💡 Result:

In Pyrefly 1.1.1, you can configure import handling and inspect your configuration using the following methods: Replace Imports with Any You can configure Pyrefly to replace specific imports with typing.Any, which is useful for improving performance or handling incomplete stubs [1]. This is done using the replace-imports-with-any configuration option, which accepts a list of module wildcard patterns [1]. In your pyrefly.toml or pyproject.toml ([tool.pyrefly] section), add it as follows: # pyrefly.toml replace-imports-with-any = [ "some_module.*", "another.module.name" ] Configuration Syntax Pyrefly configuration keys use hyphens (-) rather than underscores (_) [2][3]. Supported configuration files include pyrefly.toml (placed at the project root) or pyproject.toml (within the [tool.pyrefly] section) [2][3]. Debugging Configuration To inspect the resolved import configuration Pyrefly is applying to your files, you can use the dump-config command [4]. Replace check with dump-config in your standard Pyrefly command invocation to see the effective settings for the files being checked [4]: pyrefly dump-config Migration Guide If you are upgrading your Pyrefly version, the recommended approach to managing new type errors is to use error suppression rather than manual migration scripts [5]. After upgrading to 1.1.1, you can run the following sequence to safely silence errors while you work: 1. pyrefly check --suppress-errors (adds # pyrefly: ignore comments to your code) [5] 2. Run your code formatter [5] 3. pyrefly check --remove-unused-ignores [5] 4. Repeat as necessary until the codebase is clean [5]

Citations:


Use replace-imports-with-any here ``

Pyrefly 1.1.1 expects hyphenated config keys, so replace_imports_with_any won’t be picked up in this block. Use replace-imports-with-any to keep the import-to-Any rule applied.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyproject.toml` around lines 139 - 153, The Pyrefly config is using an
underscore key that won’t be recognized, so the import-to-Any rule is not
applied. Update the [tool.pyrefly] section to use the hyphenated setting name
for replace-imports-with-any, keeping the existing lxml.* value, and leave the
rest of the pyrefly options unchanged.

Comment thread README.rst
Comment on lines +142 to +149
uv add "fastkml[lxml]"

Using pip
---------

.. code-block:: bash

pip install fastkml

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant docs and package metadata.
git ls-files | rg '^(README\.rst|pyproject\.toml|setup\.cfg|setup\.py|requirements.*|.*\.toml)$'

# Show the target README section with line numbers.
sed -n '130,160p' README.rst | cat -n

# Inspect project metadata for extras / optional dependencies.
for f in pyproject.toml setup.cfg setup.py; do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    sed -n '1,260p' "$f" | cat -n
  fi
done

# Search for lxml / extras references across the repo.
rg -n --hidden --glob '!*.pyc' --glob '!*.png' --glob '!*.jpg' --glob '!*.gif' \
  'lxml|extra|optional-depend|fastkml\[lxml\]|pip install fastkml|uv add "fastkml\[lxml\]"' .

Repository: cleder/fastkml

Length of output: 21516


Keep the lxml extra in the pip example. pip install fastkml leaves out the optional lxml dependency, so this should match the uv example: pip install "fastkml[lxml]".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.rst` around lines 142 - 149, The pip install example omits the
optional lxml dependency and is inconsistent with the uv example. Update the
installation snippet in README.rst so the pip command matches the package extra
used in the uv example, and keep the reference consistent with the fastkml
installation guidance.

cleder and others added 2 commits July 5, 2026 19:02
Fix a real correctness bug flagged by three independent bots: Track's
whens/coords zip used strict=False, silently truncating data despite the
code's own comment stating they're mandatory pairs. Now strict=True.

Finish the Optional[X] -> X | None modernization for the handful of
TYPE_CHECKING-only forward references ruff safely declined to touch
(rewriting them unquoted would evaluate str.__or__ at def time and raise,
since these files don't use `from __future__ import annotations`); quote
the whole expression instead, matching the project's existing pattern for
forward refs.

Fix an uncaught IndexError in validator.handle_validation_error: an xpath
match list can legitimately come back empty without raising
XPathEvalError, so matches[0] could crash instead of falling back to the
element itself.

Restore the ~15 lines of [tool.pyrefly]/[tool.ty] config comments that
pre-commit.ci's pyprojectsort run silently stripped after the last push,
and add a `ci: skip: [pyprojectsort]` block so that keeps happening -
pyprojectsort has no option to preserve comments, so it stays useful
locally but is kept out of the bot's autofix path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
lxml's fromstring() rejects a Python str that contains an
<?xml ... encoding="..."?> declaration, raising ValueError. This
regressed in 2bb5aed, which dropped from_string's .encode("utf-8") call
as "unnecessary" - it was load-bearing. examples/read_kml.py (whose doc
string has an encoding declaration) surfaced this via CI's
doctest-lxml job. Encode to bytes before parsing so lxml can honor the
declared encoding itself; verified this also still works with the
stdlib xml.etree.ElementTree fallback backend.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@kody-ai

kody-ai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

Comment thread fastkml/gx/track.py
Comment on lines 398 to 410
def __init__(
self,
*,
ns: Optional[str] = None,
name_spaces: Optional[dict[str, str]] = None,
id: Optional[str] = None,
target_id: Optional[str] = None,
altitude_mode: Optional[AltitudeMode] = None,
tracks: Optional[Iterable[Track]] = None,
interpolate: Optional[bool] = None,
ns: str | None = None,
name_spaces: dict[str, str] | None = None,
id: str | None = None,
target_id: str | None = None,
altitude_mode: AltitudeMode | None = None,
tracks: Iterable[Track] | None = None,
interpolate: bool | None = None,
**kwargs: Any,
) -> None:
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential Type Safety Issue in MultiTrack.init

The constructor for MultiTrack does not validate that all elements in the tracks iterable are instances of the Track class. If a non-Track object is passed, it will be stored in self.tracks, potentially causing runtime errors in downstream methods (e.g., when calling track.geometry).

Recommendation:
Add explicit type validation for the tracks argument:

self.tracks = [t for t in tracks if isinstance(t, Track)] if tracks else []

This will ensure that only valid Track objects are included, improving robustness and preventing subtle bugs.

Comment thread fastkml/helpers.py
Comment on lines 144 to 150
def get_ns(obj: "_XMLObject", value: object) -> str:
"""Get the namespace of an attribute, fall back on the objects namespace."""
try:
return obj.name_spaces.get(value.get_ns_id(), "") # type: ignore[attr-defined]
return obj.name_spaces.get(value.get_ns_id(), "") # type: ignore[attr-defined] # pyrefly: ignore # ty: ignore[unresolved-attribute]
except AttributeError:
return obj.ns

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overly Broad Exception Handling in get_ns

The function catches all AttributeError exceptions, which could mask unrelated attribute errors on obj or value. This may make debugging more difficult if an unexpected attribute is missing. Consider narrowing the exception handling to only the expected case, or explicitly checking for the attribute before accessing it:

if hasattr(value, 'get_ns_id'):
    return obj.name_spaces.get(value.get_ns_id(), "")
return obj.ns

This approach improves maintainability and debuggability.

Comment thread fastkml/helpers.py
Comment on lines 154 to 161
*,
attr_name: str,
verbosity: Verbosity,
default: Optional[Any],
) -> Optional[Any]:
default: Any | None,
) -> Any | None:
"""
Get the value of an attribute from an object.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ambiguous Return Logic in get_value

The return statement combines two conditions in a single line, which may reduce readability and could lead to subtle bugs if the logic is changed in the future:

return None if value == default and verbosity == Verbosity.terse else value

Consider making the logic more explicit for maintainability:

if value == default and verbosity == Verbosity.terse:
    return None
return value

This makes the function's intent clearer and reduces the risk of future errors.

Comment thread fastkml/validator.py
Comment on lines 52 to 58

@lru_cache(maxsize=16)
def get_schema_parser(
schema: Optional[pathlib.Path] = None,
schema: pathlib.Path | None = None,
) -> "etree.XMLSchema":
"""
Parse the XML schema.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential unhandled exception in schema parsing

If the schema file is missing or unreadable, config.etree.parse(schema) will raise an exception that is not caught. This could cause the application to fail unexpectedly. Consider wrapping the parsing logic in a try-except block and logging or handling the error gracefully:

try:
    return config.etree.XMLSchema(config.etree.parse(schema))
except (OSError, config.etree.XMLSchemaParseError) as e:
    logger.error("Failed to parse schema: %s", e)
    raise

Comment thread fastkml/validator.py
Comment on lines 146 to +147
if file_to_validate is not None:
element = config.etree.parse(file_to_validate)
element = config.etree.parse(file_to_validate).getroot()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No error handling for file parsing

If config.etree.parse(file_to_validate) fails due to a malformed or missing file, an exception will be raised and not caught, resulting in abrupt failure. To improve robustness, wrap the parsing in a try-except block and log the error:

try:
    element = config.etree.parse(file_to_validate).getroot()
except (OSError, config.etree.XMLSyntaxError) as e:
    logger.error("Failed to parse file: %s", e)
    raise

@codescene-access codescene-access Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Health Improved (2 files improve in Code Health)

Our agent can fix these. Install it.

Gates Passed
6 Quality Gates Passed

View Improvements
File Code Health Impact Categories Improved
utils.py 9.69 → 10.00 Complex Conditional
overlay_test.py 8.82 → 9.39 Code Duplication

Quality Gate Profile: Customizable Safeguards
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

Comment thread fastkml/base.py
# lxml rejects a `str` with an XML encoding declaration
# (`<?xml ... encoding="..."?>`); bytes let it honor the
# declared encoding itself.
element=config.etree.fromstring(string.encode("utf-8")),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

Encoding mismatch in fastkml/base.py causes config.etree.fromstring() to misdecode XML documents that declare a non-UTF-8 encoding, such as <?xml version="1.0" encoding="ISO-8859-1"?>, and silently corrupt non-ASCII characters into mojibake. Strip or rewrite the XML encoding declaration to UTF-8 before encoding, or pass the string directly when no encoding declaration is present and only encode when necessary.

Prompt for LLM

File fastkml/base.py:

Line 482:

Encoding mismatch in fastkml/base.py causes `config.etree.fromstring()` to misdecode XML documents that declare a non-UTF-8 encoding, such as `<?xml version="1.0" encoding="ISO-8859-1"?>`, and silently corrupt non-ASCII characters into mojibake. Strip or rewrite the XML encoding declaration to `UTF-8` before encoding, or pass the string directly when no encoding declaration is present and only encode when necessary.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@cleder
cleder merged commit 196a12f into develop Jul 5, 2026
57 of 63 checks passed
@cleder
cleder deleted the 503-typechecking-fails-with-ty-and-pyrefly branch July 5, 2026 18:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Typechecking fails with ty and pyrefly

3 participants