Skip to content

Commit 40a564c

Browse files
authored
Merge pull request #126 from ArcanaFramework/tuple-of
adds '+tuple-of' support
2 parents eb4aff1 + 7993b9d commit 40a564c

File tree

11 files changed

+92
-74
lines changed

11 files changed

+92
-74
lines changed

.pre-commit-config.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,32 @@
22
# See https://pre-commit.com/hooks.html for more hooks
33
repos:
44
- repo: https://github.com/pre-commit/pre-commit-hooks
5-
rev: v4.3.0
5+
rev: v6.0.0
66
hooks:
77
- id: trailing-whitespace
88
- id: end-of-file-fixer
99
- id: check-yaml
1010
- id: check-added-large-files
1111
- repo: https://github.com/psf/black
12-
rev: 22.3.0
12+
rev: 25.12.0
1313
hooks:
1414
- id: black
1515
exclude: ^(fileformats/core/_version\.py)$
1616
args:
1717
- -l 88
1818
- repo: https://github.com/codespell-project/codespell
19-
rev: v2.1.0
19+
rev: v2.4.1
2020
hooks:
2121
- id: codespell
2222
exclude: ^(fileformats/core/_version\.py)$
2323
args:
2424
- --ignore-words=.codespell-ignorewords
2525
- repo: https://github.com/PyCQA/flake8
26-
rev: 7.0.0
26+
rev: 7.3.0
2727
hooks:
2828
- id: flake8
2929
- repo: https://github.com/pre-commit/mirrors-mypy
30-
rev: v1.11.2
30+
rev: v1.19.1
3131
hooks:
3232
- id: mypy
3333
args:

extras/fileformats/extras/application/archive.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@
5252
# ]
5353

5454

55-
@converter(source_format=FsObject, target_format=Tar) # type: ignore[misc]
56-
@converter(source_format=FsObject, target_format=TarGzip, compression="gz") # type: ignore[misc]
57-
@converter(source_format=Compressed, target_format=Tar[Compressed]) # type: ignore[misc]
55+
@converter(source_format=FsObject, target_format=Tar) # type: ignore[untyped-decorator]
56+
@converter(source_format=FsObject, target_format=TarGzip, compression="gz") # type: ignore[untyped-decorator]
57+
@converter(source_format=Compressed, target_format=Tar[Compressed]) # type: ignore[untyped-decorator,misc]
5858
@converter(
59-
source_format=Compressed, target_format=TarGzip[Compressed], compression="gz" # type: ignore[misc]
59+
source_format=Compressed, target_format=TarGzip[Compressed], compression="gz" # type: ignore[untyped-decorator,misc]
6060
)
61-
@python.define(outputs={"out_file": Path}) # type: ignore[misc]
61+
@python.define(outputs={"out_file": Path}) # type: ignore[untyped-decorator]
6262
def create_tar(
6363
in_file: FsObject,
6464
out_file: ty.Optional[Path] = None,
@@ -103,11 +103,11 @@ def create_tar(
103103
return Path(out_file)
104104

105105

106-
@converter(source_format=Tar, target_format=FsObject) # type: ignore[misc]
107-
@converter(source_format=TarGzip, target_format=FsObject) # type: ignore[misc]
108-
@converter(source_format=Tar[Compressed], target_format=Compressed) # type: ignore[misc]
109-
@converter(source_format=TarGzip[Compressed], target_format=Compressed) # type: ignore[misc]
110-
@python.define(outputs={"out_file": Path}) # type: ignore[misc]
106+
@converter(source_format=Tar, target_format=FsObject) # type: ignore[untyped-decorator]
107+
@converter(source_format=TarGzip, target_format=FsObject) # type: ignore[untyped-decorator]
108+
@converter(source_format=Tar[Compressed], target_format=Compressed) # type: ignore[untyped-decorator,misc]
109+
@converter(source_format=TarGzip[Compressed], target_format=Compressed) # type: ignore[untyped-decorator,misc]
110+
@python.define(outputs={"out_file": Path}) # type: ignore[untyped-decorator]
111111
def extract_tar(
112112
in_file: FsObject,
113113
extract_dir: ty.Optional[Path] = None,
@@ -136,9 +136,9 @@ def extract_tar(
136136
return extracted[0]
137137

138138

139-
@converter(source_format=FsObject, target_format=Zip) # type: ignore[misc]
140-
@converter(source_format=Compressed, target_format=Zip[Compressed]) # type: ignore[misc]
141-
@python.define(outputs={"out_file": Zip}) # type: ignore[misc]
139+
@converter(source_format=FsObject, target_format=Zip) # type: ignore[untyped-decorator]
140+
@converter(source_format=Compressed, target_format=Zip[Compressed]) # type: ignore[untyped-decorator,misc]
141+
@python.define(outputs={"out_file": Zip}) # type: ignore[untyped-decorator]
142142
def create_zip(
143143
in_file: FsObject,
144144
out_file: ty.Optional[Path] = None,
@@ -183,9 +183,9 @@ def create_zip(
183183
return Zip(out_file)
184184

185185

186-
@converter(source_format=Zip, target_format=FsObject) # type: ignore[misc]
187-
@converter(source_format=Zip[Compressed], target_format=Compressed) # type: ignore[misc]
188-
@python.define(outputs={"out_file": Path}) # type: ignore[misc]
186+
@converter(source_format=Zip, target_format=FsObject) # type: ignore[untyped-decorator]
187+
@converter(source_format=Zip[Compressed], target_format=Compressed) # type: ignore[untyped-decorator,misc]
188+
@python.define(outputs={"out_file": Path}) # type: ignore[untyped-decorator]
189189
def extract_zip(in_file: Zip, extract_dir: ty.Optional[Path] = None) -> Path:
190190

191191
if extract_dir is None:

extras/fileformats/extras/application/serialization.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
from pathlib import Path
2-
import typing as ty
31
import tempfile
2+
import typing as ty
3+
from pathlib import Path
4+
45
import yaml
56
from pydra.compose import python
6-
from fileformats.core import FileSet, converter, extra_implementation
7-
from fileformats.application import TextSerialization, Json, Yaml
7+
8+
from fileformats.application import Json, TextSerialization, Yaml
89
from fileformats.application.serialization import SerializationType
10+
from fileformats.core import FileSet, converter, extra_implementation
911

1012

11-
@converter(target_format=Json, output_format=Json) # type: ignore[misc]
12-
@converter(target_format=Yaml, output_format=Yaml) # type: ignore[misc]
13-
@python.define(outputs={"out_file": TextSerialization}) # type: ignore[misc]
13+
@converter(target_format=Json, output_format=Json) # type: ignore[untyped-decorator]
14+
@converter(target_format=Yaml, output_format=Yaml) # type: ignore[untyped-decorator]
15+
@python.define(outputs={"out_file": TextSerialization}) # type: ignore[untyped-decorator]
1416
def convert_data_serialization(
1517
in_file: TextSerialization,
1618
output_format: ty.Type[TextSerialization],

extras/fileformats/extras/core/tests/test_converter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020

2121

2222
@converter
23-
@python.define(outputs={"out_file": Bar}) # type: ignore[misc]
23+
@python.define(outputs={"out_file": Bar}) # type: ignore[untyped-decorator]
2424
def FooBarConverter(in_file: Foo):
2525
return Bar(write_test_file(Path.cwd() / "bar.bar", in_file.raw_contents))
2626

2727

2828
@converter(out_file="out")
29-
@python.define(outputs={"out": Bar}) # type: ignore[misc]
29+
@python.define(outputs={"out": Bar}) # type: ignore[untyped-decorator]
3030
def BazBarConverter(in_file: Baz):
3131
assert in_file
3232
return Bar(write_test_file(Path.cwd() / "bar.bar", in_file.raw_contents))

extras/fileformats/extras/generic/converters.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
import tempfile
2-
from pathlib import Path
32
import typing as ty
3+
from pathlib import Path
4+
45
from pydra.compose import python
5-
from fileformats.core import converter, FileSet
6+
7+
from fileformats.core import FileSet, converter
68
from fileformats.generic import DirectoryOf, SetOf, TypedDirectory, TypedSet
79

810
T = FileSet.type_var("T")
911

1012

11-
@converter(target_format=SetOf[T], source_format=DirectoryOf[T]) # type: ignore[misc]
12-
@python.define(outputs={"out_file": TypedSet}) # type: ignore[misc]
13+
@converter(target_format=SetOf[T], source_format=DirectoryOf[T]) # type: ignore[untyped-decorator,misc]
14+
@python.define(outputs={"out_file": TypedSet}) # type: ignore[untyped-decorator]
1315
def list_dir_contents(in_file: TypedDirectory) -> TypedSet:
1416
classified_set: ty.Type[TypedSet] = SetOf.__class_getitem__(*in_file.content_types) # type: ignore[assignment, arg-type]
1517
return classified_set(in_file.contents)
1618

1719

18-
@converter(target_format=DirectoryOf[T], source_format=SetOf[T]) # type: ignore[misc]
19-
@python.define(outputs={"out_file": TypedDirectory}) # type: ignore[misc]
20+
@converter(target_format=DirectoryOf[T], source_format=SetOf[T]) # type: ignore[untyped-decorator,misc]
21+
@python.define(outputs={"out_file": TypedDirectory}) # type: ignore[untyped-decorator]
2022
def put_contents_in_dir(
2123
in_file: TypedSet,
2224
out_dir: ty.Optional[Path] = None,

extras/fileformats/extras/image/converters.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
from pathlib import Path
2-
import typing as ty
31
import tempfile
2+
import typing as ty
3+
from pathlib import Path
4+
45
from pydra.compose import python
6+
57
from fileformats.core import converter
6-
from fileformats.image.raster import RasterImage, Bitmap, Gif, Jpeg, Png, Tiff
8+
from fileformats.image.raster import Bitmap, Gif, Jpeg, Png, RasterImage, Tiff
79

810

9-
@converter(target_format=Bitmap, output_format=Bitmap) # type: ignore[misc]
10-
@converter(target_format=Gif, output_format=Gif) # type: ignore[misc]
11-
@converter(target_format=Jpeg, output_format=Jpeg) # type: ignore[misc]
12-
@converter(target_format=Png, output_format=Png) # type: ignore[misc]
13-
@converter(target_format=Tiff, output_format=Tiff) # type: ignore[misc]
14-
@python.define(outputs={"out_file": RasterImage}) # type: ignore[misc]
11+
@converter(target_format=Bitmap, output_format=Bitmap) # type: ignore[untyped-decorator]
12+
@converter(target_format=Gif, output_format=Gif) # type: ignore[untyped-decorator]
13+
@converter(target_format=Jpeg, output_format=Jpeg) # type: ignore[untyped-decorator]
14+
@converter(target_format=Png, output_format=Png) # type: ignore[untyped-decorator]
15+
@converter(target_format=Tiff, output_format=Tiff) # type: ignore[untyped-decorator]
16+
@python.define(outputs={"out_file": RasterImage}) # type: ignore[untyped-decorator]
1517
def convert_image(
1618
in_file: RasterImage,
1719
output_format: ty.Type[RasterImage],

extras/fileformats/extras/testing/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1+
from fileformats.extras.core import check_optional_dependency
12
from pydra.compose import python
23

34
from fileformats.core import converter, extra_implementation
4-
from fileformats.extras.core import check_optional_dependency
55
from fileformats.testing import AbstractFile, ConvertibleToFile, EncodedText, MyFormat
66
from fileformats.text import TextFile
77

88
dummy_import = None
99

1010

1111
@converter # pyright: ignore[reportArgumentType]
12-
@python.define(outputs=["out_file"]) # type: ignore[misc]
12+
@python.define(outputs=["out_file"]) # type: ignore[untyped-decorator]
1313
def ConvertibleToConverter(in_file: AbstractFile) -> ConvertibleToFile:
1414
return ConvertibleToFile.sample()
1515

1616

1717
@converter # pyright: ignore[reportArgumentType]
18-
@python.define(outputs=["out_file"]) # type: ignore[misc]
18+
@python.define(outputs=["out_file"]) # type: ignore[untyped-decorator]
1919
def EncodedFromTextConverter(in_file: TextFile, shift: int = 1) -> EncodedText:
2020
contents = in_file.read_text()
2121
# Encode by shifting ASCII codes forward by 1
@@ -26,7 +26,7 @@ def EncodedFromTextConverter(in_file: TextFile, shift: int = 1) -> EncodedText:
2626

2727

2828
@converter # pyright: ignore[reportArgumentType]
29-
@python.define(outputs=["out_file"]) # type: ignore[misc]
29+
@python.define(outputs=["out_file"]) # type: ignore[untyped-decorator]
3030
def EncodedToTextConverter(in_file: EncodedText, shift: int = 1) -> TextFile:
3131
contents = in_file.read_text()
3232
# Decode by shifting ASCII codes back by 1

fileformats/core/converter_helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class SubtypeVar:
2626
AnyFileFormat = FileSet.type_var("AnyFileFormat")
2727
2828
@converter
29-
@python.define # type: ignore[misc]
29+
@python.define # type: ignore[untyped-decorator]
3030
def unzip(in_file: Zip[AnyFileFormat], out_file: AnyFileFormat):
3131
...
3232
"""

fileformats/core/identification.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .utils import add_exc_note, fspaths_converter, is_union
1212

1313
LIST_MIME = "+list-of"
14+
TUPLE_MIME = "+tuple-of"
1415
IANA_MIME_TYPE_REGISTRIES = [
1516
"application",
1617
"audio",
@@ -101,13 +102,18 @@ def from_mime(
101102
FormatRecognitionError
102103
if the MIME string does not correspond to a valid file format class
103104
"""
104-
if mime_str.endswith(LIST_MIME):
105-
item_mime = mime_str[: -len(LIST_MIME)]
105+
if match := re.match(
106+
r".*(" + re.escape(LIST_MIME) + "|" + re.escape(TUPLE_MIME) + r")$", mime_str
107+
):
108+
item_mime = mime_str[: -len(match.group(1))]
106109
if item_mime.startswith("[") and item_mime.endswith("]"):
107110
item_mime = item_mime[1:-1]
108-
return ty.List[from_mime(item_mime)] # type: ignore
109-
if "," in mime_str:
110-
return functools.reduce(operator.or_, (from_mime(t) for t in mime_str.split(","))) # type: ignore
111+
if match.group(1) == LIST_MIME:
112+
return list[from_mime(item_mime)] # type: ignore
113+
else:
114+
return tuple[from_mime(item_mime)] # type: ignore
115+
if "|" in mime_str:
116+
return functools.reduce(operator.or_, (from_mime(t) for t in mime_str.split("|"))) # type: ignore
111117
return fileformats.core.DataType.from_mime(mime_str)
112118

113119

@@ -149,14 +155,14 @@ def to_mime(
149155
f"Cannot convert {datatype} to official mime-type as it is not a proper "
150156
'file-type, please use official=False to convert to "mime-like" string instead'
151157
)
152-
if origin is list:
158+
if origin in (list, tuple):
153159
item_mime = to_mime(ty.get_args(datatype)[0], official=official)
154160
if "," in item_mime:
155161
item_mime = "[" + item_mime + "]"
156-
item_mime += LIST_MIME
162+
item_mime += LIST_MIME if origin is list else TUPLE_MIME
157163
return item_mime
158164
if is_union(datatype):
159-
return ",".join(to_mime(t, official=official) for t in ty.get_args(datatype))
165+
return "|".join(to_mime(t, official=official) for t in ty.get_args(datatype))
160166
if (
161167
isinstance(datatype, str)
162168
and datatype.startswith("fileformats.")

fileformats/core/tests/test_classifiers.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ def test_file_classifiers7():
187187

188188
@converter(source_format=F[A], target_format=H[A]) # Additional converter
189189
@converter
190-
@python.define(outputs=["out_file"]) # type: ignore[misc]
190+
@python.define(outputs=["out_file"]) # type: ignore[untyped-decorator]
191191
def f2h(in_file: F) -> H:
192192
return in_file
193193

@@ -212,7 +212,7 @@ def test_qualifier_converters():
212212
@converter(source_format=K[C, D], target_format=L[D, C])
213213
@converter(source_format=K[A, B, C], target_format=L[A, B])
214214
@converter(source_format=K[A, C, B], target_format=L[C, A])
215-
@python.define(outputs=["out_file"]) # type: ignore[misc]
215+
@python.define(outputs=["out_file"]) # type: ignore[untyped-decorator]
216216
def k2l(in_file: K) -> L:
217217
return in_file
218218

@@ -313,19 +313,19 @@ def test_arrays():
313313

314314

315315
@converter
316-
@python.define(outputs=["out_file"]) # type: ignore[misc]
316+
@python.define(outputs=["out_file"]) # type: ignore[untyped-decorator]
317317
def f2n_template(in_file: F[SpecificDataType]) -> N[SpecificDataType]:
318318
return in_file
319319

320320

321321
@converter
322-
@python.define(outputs=["out_file"]) # type: ignore[misc]
322+
@python.define(outputs=["out_file"]) # type: ignore[untyped-decorator]
323323
def f2p_template(in_file: F[SpecificDataType]) -> P[SpecificDataType]:
324324
return in_file
325325

326326

327327
@converter
328-
@python.define(outputs=["out_file"]) # type: ignore[misc]
328+
@python.define(outputs=["out_file"]) # type: ignore[untyped-decorator]
329329
def p2n_template(in_file: P[SpecificDataType]) -> N[SpecificDataType]:
330330
return in_file
331331

@@ -340,13 +340,13 @@ def test_wildcard_template_from_template_conversion():
340340

341341

342342
@converter
343-
@python.define(outputs=["out_file"]) # type: ignore[misc]
343+
@python.define(outputs=["out_file"]) # type: ignore[untyped-decorator]
344344
def generic2f(in_file: SpecificDataType) -> F[SpecificDataType]:
345345
return in_file
346346

347347

348348
@converter
349-
@python.define(outputs=["out_file"]) # type: ignore[misc]
349+
@python.define(outputs=["out_file"]) # type: ignore[untyped-decorator]
350350
def generic2n(in_file: SpecificDataType) -> N[SpecificDataType, H]:
351351
return in_file
352352

@@ -382,13 +382,13 @@ def test_wildcard_template_from_generic_conversion6():
382382

383383

384384
@converter
385-
@python.define(outputs=["out_file"]) # type: ignore[misc]
385+
@python.define(outputs=["out_file"]) # type: ignore[untyped-decorator]
386386
def f2generic(in_file: F[SpecificFileSet]) -> SpecificFileSet:
387387
return in_file
388388

389389

390390
@converter
391-
@python.define(outputs=["out_file"]) # type: ignore[misc]
391+
@python.define(outputs=["out_file"]) # type: ignore[untyped-decorator]
392392
def n2generic(in_file: N[SpecificFileSet, G]) -> SpecificFileSet:
393393
return in_file
394394

@@ -406,7 +406,7 @@ def test_wildcard_generic_from_multi_template_conversion():
406406

407407

408408
@converter
409-
@python.define(outputs=["out_file"]) # type: ignore[misc]
409+
@python.define(outputs=["out_file"]) # type: ignore[untyped-decorator]
410410
def l2r(in_file: L[A, SpecificFileSet, C]) -> R[A, SpecificFileSet, C, D]:
411411
return in_file
412412

0 commit comments

Comments
 (0)