Skip to content

[codex] fix Python 3.8/3.9 import compatibility#98

Merged
Jiu-xiao merged 2 commits into
XRobot2.0from
codex/python38-import-compat
Apr 27, 2026
Merged

[codex] fix Python 3.8/3.9 import compatibility#98
Jiu-xiao merged 2 commits into
XRobot2.0from
codex/python38-import-compat

Conversation

@Jiu-xiao

@Jiu-xiao Jiu-xiao commented Apr 27, 2026

Copy link
Copy Markdown
Member

Fixes #97.

This change fixes the Python-version compatibility regression reported in issue #97.

The immediate user-facing failure was that xrobot_setup crashed during import on Python 3.9 because xrobot.SourceManager used PEP 604 union type syntax such as str | Path, which is only valid at runtime on Python 3.10 and newer. The package metadata, however, still declares Requires-Python >=3.8, so the installed package could be pulled into Python 3.8 and 3.9 environments even though some modules were no longer importable there.

The fix keeps the declared Python support floor and makes the affected modules compatible with that floor again. In SourceManager.py, the PEP 604 annotations are replaced with typing.Union and typing.Optional. In XRobotSetup.py, the list[str] annotation is replaced with typing.List[str], which is also required for real Python 3.8 compatibility. Together these changes remove the import-time incompatibilities while preserving behavior.

This PR also adds a small CI guard so the same class of regression is caught automatically next time. After the wheel or sdist is built in the existing Python version matrix job, the workflow now installs the built package and runs scripts/ci_import_smoke.py, which imports every top-level xrobot module from the installed package. That turns this issue from a user-reported runtime surprise into a direct CI failure on unsupported syntax regressions.

Validation for this change was done locally in two ways. First, the repository was rescanned to make sure no remaining top-level type annotations relied on PEP 604 or PEP 585 generics in the affected modules. Second, the package was built and installed into a clean virtual environment and python scripts/ci_import_smoke.py completed successfully, importing all top-level modules, including xrobot.SourceManager and xrobot.XRobotSetup.

Summary by Sourcery

Restore Python 3.8–3.9 compatibility and add a CI smoke test to catch import-time regressions for xrobot modules.

Bug Fixes:

  • Replace Python 3.10-only PEP 604 and PEP 585-style type annotations with typing.Union, typing.Optional, and typing.List equivalents to make xrobot modules importable on Python 3.8 and 3.9.

Enhancements:

  • Add an import smoke test script that imports all top-level xrobot modules from the installed package.
  • Bump the project version to 0.3.0 to reflect the compatibility and CI changes.

CI:

  • Extend the Python publish workflow to install the built distribution on all platforms and run the import smoke test as part of the matrix build.

@sourcery-ai

sourcery-ai Bot commented Apr 27, 2026

Copy link
Copy Markdown

Reviewer's Guide

Restores Python 3.8/3.9 import compatibility by replacing runtime PEP 604/585 type syntax with typing-based annotations and adds a CI smoke test that imports all top-level xrobot modules from the built package across the Python matrix.

Sequence diagram for CI import smoke test execution

sequenceDiagram
    participant GithubActionsRunner
    participant PythonProcess
    participant ci_import_smoke
    participant importlib

    GithubActionsRunner->>PythonProcess: python scripts/ci_import_smoke.py
    activate PythonProcess
    PythonProcess->>ci_import_smoke: main()
    activate ci_import_smoke
    ci_import_smoke->>ci_import_smoke: discover src/xrobot/*.py
    loop each module file
        ci_import_smoke->>importlib: import_module(xrobot.module)
        alt import succeeds
            importlib-->>ci_import_smoke: module object
            ci_import_smoke->>ci_import_smoke: record imported module
        else import fails
            importlib-->>ci_import_smoke: raises Exception
            ci_import_smoke->>ci_import_smoke: record failure(module, error)
        end
    end
    ci_import_smoke->>PythonProcess: print results
    alt failures exist
        ci_import_smoke-->>PythonProcess: return code 1
    else no failures
        ci_import_smoke-->>PythonProcess: return code 0
    end
    deactivate ci_import_smoke
    PythonProcess-->>GithubActionsRunner: exit with status
    deactivate PythonProcess
Loading

Class diagram for updated xrobot type annotations

classDiagram
    class SourceManagerModule {
        +dict load_yaml(source: Union_str_Path)
        +None save_yaml(path: Union_str_Path, data: dict)
        +None __init__(sources_yaml)
        +None load_sources(yaml_path: Union_Path_str)
        +list list_modules()
        +Optional_str get_repo_url(modid: str)
    }

    class XRobotSetupModule {
        +None ensure_modules_and_sources()
        +None run_subprocess(cmd: List_str)
    }

    class typing {
        <<module>>
        +Union
        +Optional
        +List
    }

    class pathlib {
        <<module>>
        +Path
    }

    SourceManagerModule ..> typing : uses Union, Optional
    SourceManagerModule ..> pathlib : uses Path
    XRobotSetupModule ..> typing : uses List
Loading

File-Level Changes

Change Details Files
Replace Python 3.10+ union and generic syntax with typing-based annotations to match the declared >=3.8 support floor.
  • Updated SourceManager helper functions and methods to use Union[...] and Optional[...] instead of PEP 604 union syntax for parameters and return types.
  • Updated XRobotSetup.run_subprocess signature to use typing.List[str] instead of list[str] for Python 3.8 compatibility.
  • Ensured necessary typing imports (Union, Optional, List) are present where new annotations are used.
src/xrobot/SourceManager.py
src/xrobot/XRobotSetup.py
Add CI import-smoke coverage on built artifacts to catch future import-time incompatibilities on all supported Python versions and platforms.
  • Extended the python-publish GitHub Actions workflow to install the built wheel or sdist on each matrix job and run a new smoke script.
  • Added a cross-platform install step, including a PowerShell path selection on Windows, to install whichever dist artifact was built.
  • Introduced scripts/ci_import_smoke.py, which discovers all top-level xrobot/*.py modules and attempts to import them, failing the job if any import raises.
.github/workflows/python-publish.yml
scripts/ci_import_smoke.py
Bump project version to reflect the compatibility fix release.
  • Incremented the xrobot package version from 0.2.10 to 0.3.0 in pyproject.toml.
pyproject.toml

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@Jiu-xiao Jiu-xiao marked this pull request as ready for review April 27, 2026 11:59
@Jiu-xiao Jiu-xiao merged commit a0ab142 into XRobot2.0 Apr 27, 2026
44 checks passed
@Jiu-xiao Jiu-xiao deleted the codex/python38-import-compat branch April 27, 2026 12:02

@sourcery-ai sourcery-ai 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.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path=".github/workflows/python-publish.yml" line_range="128-131" />
<code_context>
           name: python-package-${{ matrix.os }}-${{ matrix.arch }}-py${{ matrix.python-version }}
           path: dist/*

+      - name: Install built package (Linux/macOS)
+        if: runner.os != 'Windows'
+        run: |
+          pip install dist/*.whl || pip install dist/*.tar.gz
+
+      - name: Install built package (Windows)
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Use `python -m pip` to ensure the same interpreter is used for install and smoke tests

This step invokes `pip` directly, while the smoke tests use `python`. On some runners `pip` can point to a different interpreter than the job’s `python`, so you may end up installing into one environment and importing from another. Using `python -m pip install ...` (and likewise in the Windows step) ensures `pip` matches the matrix Python interpreter.

Suggested implementation:

```
      - name: Install built package (Linux/macOS)
        if: runner.os != 'Windows'
        run: |
          python -m pip install dist/*.whl || python -m pip install dist/*.tar.gz

```

```
      - name: Install built package (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          $pkg = Get-ChildItem dist\*.whl,dist\*.tar.gz | Select-Object -First 1
          if ($pkg) {
            python -m pip install $pkg.FullName
          }

```
</issue_to_address>

### Comment 2
<location path="scripts/ci_import_smoke.py" line_range="11-16" />
<code_context>
+
+def main() -> int:
+    repo_root = Path(__file__).resolve().parents[1]
+    src_dir = repo_root / "src" / "xrobot"
+
+    failures = []
+    imported = []
+
+    for path in sorted(src_dir.glob("*.py")):
+        if path.name == "__init__.py":
+            continue
</code_context>
<issue_to_address>
**issue (bug_risk):** Fail fast if the expected `src/xrobot` directory does not exist

If `src_dir` is missing (e.g., due to layout changes or different working dirs), this will import 0 modules and still return success. Please add an explicit `if not src_dir.is_dir():` that exits with a non-zero status (or logs a clear error) to avoid silent false positives.
</issue_to_address>

### Comment 3
<location path="scripts/ci_import_smoke.py" line_range="24-25" />
<code_context>
+        try:
+            importlib.import_module(module_name)
+            imported.append(module_name)
+        except Exception as error:
+            failures.append((module_name, error))
+
+    print(f"Imported {len(imported)} modules.")
</code_context>
<issue_to_address>
**suggestion:** Include traceback details to make debugging import failures easier

Currently only `str(error)` is available for failed imports, which limits debuggability. Please also capture and log the full traceback (e.g., via `traceback.format_exc()` or `traceback.print_exc()` along with the module name) so CI logs contain enough context to diagnose import failures.

Suggested implementation:

```python
        try:
            importlib.import_module(module_name)
            imported.append(module_name)
        except Exception as error:
            tb = traceback.format_exc()
            failures.append((module_name, error, tb))

```

```python
    if failures:
        print(f"Failed to import {len(failures)} modules.", file=sys.stderr)
        for module_name, error, tb in failures:
            print(f"ERR {module_name}: {error}", file=sys.stderr)
            print(tb, file=sys.stderr)
        return 1

```

1. Add `import traceback` near the top of `scripts/ci_import_smoke.py` alongside the other imports.
2. If `failures` is typed or destructured elsewhere (e.g., via type hints or unpacking), update that code to expect a 3-tuple `(module_name, error, traceback_str)` instead of a 2-tuple.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +128 to +131
- name: Install built package (Linux/macOS)
if: runner.os != 'Windows'
run: |
pip install dist/*.whl || pip install dist/*.tar.gz

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (bug_risk): Use python -m pip to ensure the same interpreter is used for install and smoke tests

This step invokes pip directly, while the smoke tests use python. On some runners pip can point to a different interpreter than the job’s python, so you may end up installing into one environment and importing from another. Using python -m pip install ... (and likewise in the Windows step) ensures pip matches the matrix Python interpreter.

Suggested implementation:

      - name: Install built package (Linux/macOS)
        if: runner.os != 'Windows'
        run: |
          python -m pip install dist/*.whl || python -m pip install dist/*.tar.gz

      - name: Install built package (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          $pkg = Get-ChildItem dist\*.whl,dist\*.tar.gz | Select-Object -First 1
          if ($pkg) {
            python -m pip install $pkg.FullName
          }

Comment on lines +11 to +16
src_dir = repo_root / "src" / "xrobot"

failures = []
imported = []

for path in sorted(src_dir.glob("*.py")):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Fail fast if the expected src/xrobot directory does not exist

If src_dir is missing (e.g., due to layout changes or different working dirs), this will import 0 modules and still return success. Please add an explicit if not src_dir.is_dir(): that exits with a non-zero status (or logs a clear error) to avoid silent false positives.

Comment on lines +24 to +25
except Exception as error:
failures.append((module_name, error))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Include traceback details to make debugging import failures easier

Currently only str(error) is available for failed imports, which limits debuggability. Please also capture and log the full traceback (e.g., via traceback.format_exc() or traceback.print_exc() along with the module name) so CI logs contain enough context to diagnose import failures.

Suggested implementation:

        try:
            importlib.import_module(module_name)
            imported.append(module_name)
        except Exception as error:
            tb = traceback.format_exc()
            failures.append((module_name, error, tb))
    if failures:
        print(f"Failed to import {len(failures)} modules.", file=sys.stderr)
        for module_name, error, tb in failures:
            print(f"ERR {module_name}: {error}", file=sys.stderr)
            print(tb, file=sys.stderr)
        return 1
  1. Add import traceback near the top of scripts/ci_import_smoke.py alongside the other imports.
  2. If failures is typed or destructured elsewhere (e.g., via type hints or unpacking), update that code to expect a 3-tuple (module_name, error, traceback_str) instead of a 2-tuple.

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.

xrobot_setup 在 Python 3.9 下启动崩溃,但包声明 Requires-Python >=3.8

1 participant