Skip to content

Commit 2887b45

Browse files
committed
added tests for optional dependency checking
1 parent 10b2c11 commit 2887b45

File tree

7 files changed

+46
-20
lines changed

7 files changed

+46
-20
lines changed

extras/fileformats/extras/core/__init__.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
"""Only required to hold the automatically generated version for the "core" extras"""
22

3+
import typing as ty
34
import types
45
import inspect
56
from ._version import __version__
67

78

8-
def check_optional_dependency(module: types.ModuleType | None) -> None:
9+
def check_optional_dependency(module: ty.Optional[types.ModuleType]) -> None:
910
if module is None:
1011
frame = inspect.currentframe()
11-
prev_frame = None
1212
while frame:
13-
# Find the frame where the extra_implementation method was called
14-
if frame.f_code.co_name == "extra_implementation":
15-
return frame.f_locals["workflow"] # local var "workflow" in construct
16-
prev_frame = frame
13+
# Find the frame where the decorated_extra method was called
14+
if frame.f_code.co_name == "decorated_extra":
15+
extras_module = frame.f_locals["extras"][0].pkg.split(".")[-1]
16+
break
1717
frame = frame.f_back
1818
raise ImportError(
19-
f"The optional dependency '{module.__name__}' is not installed required to use the "
20-
"{} method. It is "
19+
f"The optional dependencies are not installed for '{extras_module}', please include it in "
20+
f"when installing fileformats-extras, e.g. `pip install 'fileformats-extras[{extras_module}]'`"
2121
)
2222

2323

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from fileformats.testing import MyFormat
2+
import pytest
3+
4+
5+
def test_check_optional_dependency_fail():
6+
7+
with pytest.raises(
8+
ImportError, match="The optional dependencies are not installed for 'testing'"
9+
):
10+
MyFormat.sample().dummy_extra()

extras/fileformats/extras/testing/__init__.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
from pydra.compose import python
22

3-
from fileformats.core import converter
4-
from fileformats.testing import AbstractFile, ConvertibleToFile, EncodedText
3+
from fileformats.core import converter, extra_implementation
4+
from fileformats.extras.core import check_optional_dependency
5+
from fileformats.testing import AbstractFile, ConvertibleToFile, EncodedText, MyFormat
56
from fileformats.text import TextFile
67

8+
dummy_import = None
9+
710

811
@converter # pyright: ignore[reportArgumentType]
912
@python.define(outputs=["out_file"]) # type: ignore[misc]
@@ -31,3 +34,9 @@ def EncodedToTextConverter(in_file: EncodedText) -> TextFile:
3134
out_file = TextFile.sample()
3235
out_file.write_text(decoded_contents)
3336
return out_file
37+
38+
39+
@extra_implementation(MyFormat.dummy_extra)
40+
def my_format_dummy_extra(my_format: MyFormat) -> int:
41+
check_optional_dependency(dummy_import)
42+
return 42

fileformats/core/extras.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def extra(method: ExtraMethod) -> "ExtraMethod":
3434
dispatch_method: ty.Callable[..., ty.Any] = functools.singledispatch(method)
3535

3636
@functools.wraps(method)
37-
def decorated(obj: DataType, *args: ty.Any, **kwargs: ty.Any) -> ty.Any:
37+
def decorated_extra(obj: DataType, *args: ty.Any, **kwargs: ty.Any) -> ty.Any:
3838
cls = type(obj)
3939
extras = []
4040
for tp in cls.referenced_types(): # type: ignore[attr-defined]
@@ -61,8 +61,8 @@ def decorated(obj: DataType, *args: ty.Any, **kwargs: ty.Any) -> ty.Any:
6161

6262
# Store single dispatch method on the decorated function so we can register
6363
# implementations to it later
64-
decorated._dispatch = dispatch_method # type: ignore[attr-defined]
65-
return decorated # type: ignore[return-value]
64+
decorated_extra._dispatch = dispatch_method # type: ignore[attr-defined]
65+
return decorated_extra # type: ignore[return-value]
6666

6767

6868
def extra_implementation(
@@ -78,7 +78,9 @@ def extra_implementation(
7878
"an implementation"
7979
)
8080

81-
def decorator(implementation: ExtraImplementation) -> ExtraImplementation:
81+
def extra_implementation_decorator(
82+
implementation: ExtraImplementation,
83+
) -> ExtraImplementation:
8284
msig = inspect.signature(method)
8385
fsig = inspect.signature(implementation)
8486
msig_args = list(msig.parameters.values())[1:]
@@ -188,7 +190,7 @@ def type_match(mtype: ty.Union[str, type], ftype: ty.Union[str, type]) -> bool:
188190
dispatch_method.register(implementation)
189191
return implementation
190192

191-
return decorator
193+
return extra_implementation_decorator
192194

193195

194196
WrappedTask = ty.TypeVar("WrappedTask", bound=ty.Callable[..., ty.Any])

fileformats/core/mixin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -773,9 +773,9 @@ class WithOrderedClassifiers(WithClassifiers):
773773
ordered_classifiers = True
774774

775775

776-
def origin_type(tp: ty.Type) -> ty.Type:
776+
def origin_type(tp: type) -> type:
777777
"""Get the origin type of a possibly generic type"""
778-
origin = ty.get_origin(tp)
778+
origin: ty.Optional[type] = ty.get_origin(tp)
779779
if origin is None:
780780
return tp
781-
return origin
781+
return origin # type: ignore[no-any-return]

fileformats/core/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ def get_optional_type(
280280
return args[0] if args[0] is not None else args[1] # type: ignore[no-any-return]
281281

282282

283-
def is_union(type_: type, args: list[type] = None) -> bool:
283+
def is_union(type_: type, args: ty.Optional[ty.List[type]] = None) -> bool:
284284
"""Checks whether a type is a Union, in either ty.Union[T, U] or T | U form
285285
286286
Parameters
@@ -297,6 +297,6 @@ def is_union(type_: type, args: list[type] = None) -> bool:
297297
"""
298298
if ty.get_origin(type_) in UNION_TYPES:
299299
if args is not None:
300-
return ty.get_args(type_) == args
300+
return list(ty.get_args(type_)) == args
301301
return True
302302
return False

fileformats/testing/headers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from fileformats.core import extra
12
from fileformats.application import Json
23
from fileformats.core.mixin import WithMagicNumber, WithSeparateHeader, WithSideCars
34
from fileformats.generic import BinaryFile, UnicodeFile
@@ -21,6 +22,10 @@ class MyFormat(UnicodeFile):
2122

2223
ext = ".my"
2324

25+
@extra
26+
def dummy_extra(self) -> int: # type: ignore[empty-body]
27+
... # type: ignore[empty-body]
28+
2429

2530
class MyFormatGz(MyFormat):
2631

0 commit comments

Comments
 (0)