Skip to content

Commit

Permalink
test: add typing tests (#178)
Browse files Browse the repository at this point in the history
* test: adding typesafety

* fix manifest

* add test dep
  • Loading branch information
tlambert03 authored Jul 4, 2023
1 parent a5430f1 commit 2eb5f42
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 11 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,23 @@ jobs:
- uses: codecov/codecov-action@v2

test-types:
name: Typesafety
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install dependencies
run: |
python -m pip install -U pip
python -m pip install pytest pytest-mypy-plugins
python -m pip install .
- name: Test
run: pytest typesafety -v

deploy:
name: Deploy
runs-on: ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ repos:
hooks:
- id: mypy
exclude: ^tests|^docs|_napari_plugin|widgets
additional_dependencies:
- pydantic<2
- Pint
- types-lxml
18 changes: 16 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,21 @@ ome-types = "ome_types._napari_plugin"
# extras
# https://peps.python.org/pep-0621/#dependencies-optional-dependencies
[project.optional-dependencies]
dev = ["black", "ruff", "xsdata[cli]>=23.6", "mypy"]
dev = ["black", "ruff", "xsdata[cli]>=23.6", "mypy", "pre-commit", "types-lxml"]
docs = [
"numpydoc",
"pygments",
"sphinx==5.3.0",
"sphinx-rtd-theme==1.1.1",
"ipython",
]
test = ["pytest", "pytest-cov", "xmlschema"]
test = [
"pytest",
"pytest-cov",
"xmlschema",
"pytest-mypy-plugins",
"types-lxml",
]

# https://hatch.pypa.io/latest/plugins/build-hook/custom/
[tool.hatch.build.targets.wheel.hooks.custom]
Expand Down Expand Up @@ -128,6 +134,7 @@ ignore = [
".readthedocs.yml",
"docs/**/*",
"tests/**/*",
"typesafety/**/*",
]


Expand All @@ -144,6 +151,7 @@ target-version = ['py38']
[tool.pytest.ini_options]
minversion = "6.0"
testpaths = ["tests"]
addopts = '--mypy-only-local-stub'
filterwarnings = [
"error",
"ignore:Casting invalid AnnotationID:UserWarning",
Expand All @@ -159,10 +167,16 @@ files = "src/**/*/*.py"
follow_imports = 'silent'
strict_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
disallow_any_generics = false
no_implicit_reexport = true
ignore_missing_imports = true
disallow_untyped_defs = true
plugins = "pydantic.mypy"

[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = false # allow parsing Any

[[tool.mypy.overrides]]
module = ['ome_types._autogenerated.ome_2016_06.structured_annotations']
Expand Down
6 changes: 3 additions & 3 deletions src/ome_types/_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
try:
from lxml import etree as ET
except ImportError: # pragma: no cover
from xml.etree import ElementTree as ET
from xml.etree import ElementTree as ET # type: ignore[no-redef]

from xsdata.formats.dataclass.parsers.config import ParserConfig

Expand Down Expand Up @@ -57,8 +57,8 @@ def _get_ome_type(xml: str | bytes) -> type[OMEType]:
xml = xml.encode("utf-8")
root = ET.fromstring(xml) # noqa: S314

*ns, localname = root.tag[1:].split("}", 1)
ns = next(iter(ns), None)
*_ns, localname = root.tag[1:].split("}", 1)
ns = next(iter(_ns), None)

if not ns or ns not in MODULES:
raise ValueError(f"Unsupported OME schema tag {root.tag!r} in namespace {ns!r}")
Expand Down
4 changes: 1 addition & 3 deletions src/ome_types/_mixins/_base_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from textwrap import indent
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Sequence, Set, cast

from pydantic import BaseModel, PrivateAttr, validator
from pydantic import BaseModel, validator

from ome_types._mixins._ids import validate_id
from ome_types.units import ureg
Expand Down Expand Up @@ -57,11 +57,9 @@ class Config:
underscore_attrs_are_private = True
use_enum_values = False
validate_all = True
validation_mode: str = "strict"

# allow use with weakref
__slots__: ClassVar[Set[str]] = {"__weakref__"} # type: ignore
_validation_mode: str = PrivateAttr("strict")

def __init__(__pydantic_self__, **data: Any) -> None:
if "id" in __pydantic_self__.__fields__:
Expand Down
4 changes: 2 additions & 2 deletions src/ome_types/_mixins/_ome.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def _link_refs(self) -> None:
for ref in collect_references(self):
# all reference subclasses do actually have an 'id' field
# but it's not declared in the base class
ref._ref = weakref.ref(ids[ref.id]) # type: ignore [attr-defined]
ref._ref = weakref.ref(ids[ref.id])

def __setstate__(self, state: dict[str, Any]) -> None:
"""Support unpickle of our weakref references."""
Expand Down Expand Up @@ -66,7 +66,7 @@ def collect_ids(value: Any) -> dict[str, OMEType]:
if f == "id" and not isinstance(value, Reference):
# We don't need to recurse on the id string, so just record it
# and move on.
ids[value.id] = value # type: ignore
ids[value.id] = value
else:
ids.update(collect_ids(getattr(value, f)))
# Do nothing for uninteresting types.
Expand Down
2 changes: 1 addition & 1 deletion src/xsdata_pydantic_basemodel/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def derived_element(self) -> Type:
def is_model(self, obj: Any) -> bool:
clazz = obj if isinstance(obj, type) else type(obj)
if issubclass(clazz, BaseModel):
clazz.update_forward_refs() # type: ignore
clazz.update_forward_refs()
return True

return False
Expand Down
25 changes: 25 additions & 0 deletions typesafety/test_type_inits.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
- case: types_requiring_no_arguments
main: |
import ome_types.model as m
m.OME()
m.Annotation()
m.BasicAnnotation()
m.Dataset()
m.Arc()
m.Microscope()
ch = m.Channel()
reveal_type(ch.id) # N: Revealed type is "builtins.str"
- case: types_requiring_arguments
main: |
import ome_types.model as m
m.BinData(value=b'213', length=1) # ER: Missing named argument "big_endian" .*
m.Image() # ER: Missing named argument "pixels" .*
- case: extra_arguments
main: |
import ome_types.model as m
m.Channel(idd='123') # ER: Unexpected keyword argument "idd" .*

0 comments on commit 2eb5f42

Please sign in to comment.