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
- Versions:
@atomic-ehr/codegen 0.0.21, ruff 0.16.9, macOS. The repository has no ruff configuration.
- 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
Describe the bug
The Python generator emits modules that import names they never use, and every extension profile module that has sub-extensions imports
Extensiontwice. mypy has no unused-import check, so the example passes CI. A pyflakes-based linter reports both: on the committedexamples/python-r4-us-core/fhir_types(36.pyfiles), ruff reports 40F401and 4F811.Present in 0.0.21, which is also current
main(5de74e1).To Reproduce
@atomic-ehr/codegen0.0.21, ruff 0.16.9, macOS. The repository has no ruff configuration.Result:
Found 78 errors.F401imported but unusedF811redefinition of unused nameF841local assigned but never usedI001unsorted import blockF401by name:fhir_types.profile_helpers.matches_valuefhir_types.profile_helpers.get_extension_valuetyping.Anyprofiles/extension_nationality.py,base.pypydantic.BaseModelpydantic.PositiveIntfhir_types.fhirpy_base_model.FhirpyBaseModeltyping.Literaldomain_resource.py,resource.pytyping.Listresource.py13 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
F811areextension_nationality.py,extension_uscore_ethnicity_extension.py,extension_uscore_race_extension.pyandextension_uscore_tribal_affiliation_extension.py.hl7_fhir_r4_core/profiles/extension_nationality.py:hl7_fhir_r4_core/domain_resource.pyuses none ofBaseModel,PositiveInt,LiteralorFhirpyBaseModel:Cause, at 5de74e1:
collectHelperImportsadds helpers in groups, from structural predicates:matches_valueis imported for any non-type-discriminated slice (L130-L138). It is called only by the getter of a non-array slice (profile-slices.tsL216-L222).get_extension_value, is imported for any extension (L139-L149).get_extension_valueis called only by the single-value extension getter (profile-extensions.tsL349-L361).validate_*names come from the emitted body:generateProfileModulebuilds thevalidate()lines withcollectValidateBodybefore it collects imports (L410-L414).emitModuleImportsimportsAnyfor any extension or slice (L228-L230). It imports the base type frombase, then importsExtensionfrombaseagain whenever the profile has extensions (L249-L254). For an extension profile the base type isExtension, which is theF811.generateValidateMethodwritesprofile_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 anF841.generateDefaultImportswrites a fixedpydanticlist (BaseModel, ConfigDict, Field, PositiveInt) and a fixedtypinglist (Any, List as PyList, Literal) into everybase.pyand resource module.generateResourceModulealso callsgenerateFhirBaseModelImport, which importsFhirpyBaseModelinto every resource module. Only a root class uses it as a base (getSuperClasses,injectSuperClasses), which in this output isresource.py.This is not caught in CI because the Python job (
sdk-tests.ymlL94-L111) runstest-python-r4-us-core-example, which runs onlymypy .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,F841passes onexamples/python-r4-us-core/fhir_types, andtest-python-r4-us-core-exampleruns that check next tomypy ., the way #183 turned on strict mode for themypy .run in the same target.Additional context
validate_*helpers; the slice and extension helpers, thetypingnames and the type-module imports need the same.I001findings are import order only. They are left out of the proposed guard.python-r4-us-core-test-setup(Makefile L124-L130) installsfhir_types/requirements.txt, which is copied fromassets/api/writer-generator/python/requirements.txtand lists mypy and pytest.