Skip to content

PY: generated modules import unused names and redefine Extension (ruff F401/F811) #258

Description

@Palid

Describe the bug

The Python generator emits modules that import names they never use, and every extension profile module that has sub-extensions imports Extension twice. mypy has no unused-import check, so the example passes CI. A pyflakes-based linter reports both: on the committed examples/python-r4-us-core/fhir_types (36 .py files), ruff reports 40 F401 and 4 F811.

Present in 0.0.21, which is also current main (5de74e1).

To Reproduce

  1. Versions: @atomic-ehr/codegen 0.0.21, ruff 0.16.9, macOS. The repository has no ruff configuration.
  2. No generation needed. On a checkout of 5de74e1:
ruff check --isolated --select F401,F811,F841,I examples/python-r4-us-core/fhir_types

Result: Found 78 errors.

Rule Count Files
F401 imported but unused 40 19
F811 redefinition of unused name 4 4
F841 local assigned but never used 0 0
I001 unsorted import block 34 34

F401 by name:

Name Count Where
fhir_types.profile_helpers.matches_value 9 profile modules
fhir_types.profile_helpers.get_extension_value 3 us-core race, ethnicity and tribal-affiliation extension profiles
typing.Any 2 profiles/extension_nationality.py, base.py
pydantic.BaseModel 8 type modules
pydantic.PositiveInt 8 type modules
fhir_types.fhirpy_base_model.FhirpyBaseModel 7 type modules
typing.Literal 2 domain_resource.py, resource.py
typing.List 1 resource.py

13 are in 10 profile modules. 27 are in 9 type modules under hl7_fhir_r4_core/: base.py, bundle.py, domain_resource.py, observation.py, operation_outcome.py, organization.py, patient.py, provenance.py, resource.py.

The four F811 are extension_nationality.py, extension_uscore_ethnicity_extension.py, extension_uscore_race_extension.py and extension_uscore_tribal_affiliation_extension.py. hl7_fhir_r4_core/profiles/extension_nationality.py:

from typing import Any, Literal, cast, overload

from fhir_types.hl7_fhir_r4_core.base import Extension
from fhir_types.hl7_fhir_r4_core.base import Extension
from fhir_types.hl7_fhir_r4_core.base import CodeableConcept, Period

hl7_fhir_r4_core/domain_resource.py uses none of BaseModel, PositiveInt, Literal or FhirpyBaseModel:

from __future__ import annotations
from pydantic import BaseModel, ConfigDict, Field, PositiveInt, model_validator
from typing import Any, Generic, List as PyList, Literal
from typing_extensions import Self, TypeVar
from fhir_types.fhirpy_base_model import FhirpyBaseModel

Cause, at 5de74e1:

  • collectHelperImports adds helpers in groups, from structural predicates:
    • matches_value is imported for any non-type-discriminated slice (L130-L138). It is called only by the getter of a non-array slice (profile-slices.ts L216-L222).
    • The extension group, including get_extension_value, is imported for any extension (L139-L149). get_extension_value is called only by the single-value extension getter (profile-extensions.ts L349-L361).
    • Only the validate_* names come from the emitted body: generateProfileModule builds the validate() lines with collectValidateBody before it collects imports (L410-L414).
  • emitModuleImports imports Any for any extension or slice (L228-L230). It imports the base type from base, then imports Extension from base again whenever the profile has extensions (L249-L254). For an extension profile the base type is Extension, which is the F811.
  • generateValidateMethod writes profile_name = "..." unconditionally (L333). Every committed profile module has at least one validation call, so this is not reported today; a profile with nothing to validate gets an F841.
  • Type modules: generateDefaultImports writes a fixed pydantic list (BaseModel, ConfigDict, Field, PositiveInt) and a fixed typing list (Any, List as PyList, Literal) into every base.py and resource module. generateResourceModule also calls generateFhirBaseModelImport, which imports FhirpyBaseModel into every resource module. Only a root class uses it as a base (getSuperClasses, injectSuperClasses), which in this output is resource.py.

This is not caught in CI because the Python job (sdk-tests.yml L94-L111) runs test-python-r4-us-core-example, which runs only mypy . and pytest. Nothing in the repository runs ruff, flake8 or pyflakes.

Expected result

A generated module imports each name once and imports only the names it uses, and validate() does not bind a local it does not read. ruff check --select F401,F811,F841 passes on examples/python-r4-us-core/fhir_types, and test-python-r4-us-core-example runs that check next to mypy ., the way #183 turned on strict mode for the mypy . run in the same target.

Additional context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions