8000 Merge pull request #54 from scijava/to-xarray · scijava/scyjava@aeb5c3d · GitHub
[go: up one dir, main page]

Skip to content 8000

Commit aeb5c3d

Browse files
authored
Merge pull request #54 from scijava/to-xarray
Add hints mechanism for scyjava converters
2 parents 33258b1 + 058bb5c commit aeb5c3d

File tree

6 files changed

+204
-96
lines changed

6 files changed

+204
-96
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "scyjava"
7-
version = "1.7.1.dev0"
7+
version = "1.8.0.dev0"
88
description = "Supercharged Java access from Python"
99
license = {text = "The Unlicense"}
1010
authors = [{name = "SciJava developers", email = "ctrueden@wisc.edu"}]

src/scyjava/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
jarray,
103103
jclass,
104104
jimport,
105+
jinstance,
105106
jstacktrace,
106107
jvm_started,
107108
jvm_version,

src/scyjava/_convert.py

Lines changed: 75 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
"""
44

55
import collections
6-
import typing
6+
import inspect
7+
import math
78
from pathlib import Path
8-
from typing import Any, Callable, NamedTuple
9+
from typing import Any, Callable, List, NamedTuple
910

1011
from jpype import JArray, JBoolean, JByte, JChar, JDouble, JFloat, JInt, JLong, JShort
1112

12-
from ._java import JavaClasses, isjava, jclass, jimport, start_jvm
13+
from ._java import JavaClasses, isjava, jclass, jimport, jinstance, start_jvm
1314

1415

1516
# NB: We cannot use org.scijava.priority.Priority or other Java-side class
@@ -27,16 +28,37 @@ class Priority:
2728
LAST = -1e300
2829

2930

31+
def _has_kwargs(f):
32+
return not isjava(f) and any(
33+
p.kind == inspect.Parameter.VAR_KEYWORD
34+
for p in inspect.signature(f).parameters.values()
35+
)
36+
37+
3038
class Converter(NamedTuple):
3139
predicate: Callable[[Any], bool]
3240
converter: Callable[[Any], Any]
3341
priority: float = Priority.NORMAL
3442

43+
def supports(self, obj: Any, **hints: dict) -> bool:
44+
return (
45+
self.predicate(obj, **hints)
46+
if _has_kwargs(self.predicate)
47+
else self.predicate(obj)
48+
)
49+
50+
def convert(self, obj: Any, **hints: dict) -> Any:
51+
return (
52+
self.converter(obj, **hints)
53+
if _has_kwargs(self.converter)
54+
else self.converter(obj)
55+
)
56+
3557

36-
def _convert(obj: Any, converters: typing.List[Converter]) -> Any:
37-
suitable_converters = filter(lambda c: c.predicate(obj), converters)
58+
def _convert(obj: Any, converters: List[Converter], **hints: dict) -> Any:
59+
suitable_converters = [c for c in converters if c.supports(obj, **hints)]
3860
prioritized = max(suitable_converters, key=lambda c: c.priority)
39-
return prioritized.converter(obj)
61+
return prioritized.convert(obj, **hints)
4062

4163

4264
# -- Python to Java --
@@ -74,18 +96,18 @@ def _convertIterable(obj: collections.abc.Iterable):
7496
return jlist
7597

7698

77-
java_converters: typing.List[Converter] = []
99+
java_converters: List[Converter] = []
78100

79101

80102
def add_java_converter(converter: Converter):
81103
"""
82-
Adds a converter to the list used by to_java
104+
Add a converter to the list used by to_java.
83105
:param converter: A Converter going from python to java
84106
"""
85107
java_converters.append(converter)
86108

87109

88-
def to_java(obj: Any) -> Any:
110+
def to_java(obj: Any, **hints: dict) -> Any:
89111
"""
90112
Recursively convert a Python object to a Java object.
91113
@@ -103,15 +125,15 @@ def to_java(obj: Any) -> Any:
103125
:raises TypeError: if the argument is not one of the aforementioned types.
104126
"""
105127
start_jvm()
106-
return _convert(obj, java_converters)
128+
return _convert(obj, java_converters, **hints)
107129

108130

109-
def _stock_java_converters() -> typing.List[Converter]:
131+
def _stock_java_converters() -> List[Converter]:
110132
"""
111-
Returns all python-to-java converters supported out of the box!
112-
This should only be called after the JVM has been started!
133+
Construct the Python-to-Java converters supported out of the box.
113134
:returns: A list of Converters
114135
"""
136+
start_jvm()
115137
return [
116138
# Other (Exceptional) converter
117139
Converter(
@@ -141,15 +163,33 @@ def _stock_java_converters() -> typing.List[Converter]:
141163
predicate=lambda obj: isinstance(obj, bool),
142164
converter=_jc.Boolean,
143165
),
166+
# int -> java.lang.Byte
167+
Converter(
168+
predicate=lambda obj, **hints: isinstance(obj, int)
169+
and ("type" in hints and hints["type"] in ("b", "byte", "Byte"))
170+
and _jc.Byte.MIN_VALUE <= obj <= _jc.Byte.MAX_VALUE,
171+
converter=_jc.Byte,
172+
priority=Priority.HIGH,
173+
),
174+
# int -> java.lang.Short
175+
Converter(
176+
predicate=lambda obj, **hints: isinstance(obj, int)
177+
and ("type" in hints and hints["type"] in ("s", "short", "Short"))
178+
and _jc.Short.MIN_VALUE <= obj <= _jc.Short.MAX_VALUE,
179+
converter=_jc.Short,
180+
priority=Priority.HIGH,
181+
),
144182
# int -> java.lang.Integer
145183
Converter(
146-
predicate=lambda obj: isinstance(obj, int)
184+
predicate=lambda obj, **hints: isinstance(obj, int)
185+
and ("type" not in hints or hints["type"] in ("i", "int", "Integer"))
147186
and _jc.Integer.MIN_VALUE <= obj <= _jc.Integer.MAX_VALUE,
148187
converter=_jc.Integer,
149188
),
150189
# int -> java.lang.Long
151190
Converter(
152-
predicate=lambda obj: isinstance(obj, int)
191+
predicate=lambda obj, **hints: isinstance(obj, int)
192+
and ("type" not in hints or hints["type"] in ("j", "l", "long", "Long"))
153193
and _jc.Long.MIN_VALUE <= obj <= _jc.Long.MAX_VALUE,
154194
converter=_jc.Long,
155195
priority=Priority.NORMAL - 1,
@@ -162,14 +202,24 @@ def _stock_java_converters() -> typing.List[Converter]:
162202
),
163203
# float -> java.lang.Float
164204
Converter(
165-
predicate=lambda obj: isinstance(obj, float)
166-
and _jc.Float.MIN_VALUE <= obj <= _jc.Float.MAX_VALUE,
205+
predicate=lambda obj, **hints: isinstance(obj, float)
206+
and ("type" not in hints or hints["type"] in ("f", "float", "Float"))
207+
and (
208+
math.isinf(obj)
209+
or math.isnan(obj)
210+
or -_jc.Float.MAX_VALUE <= obj <= _jc.Float.MAX_VALUE
211+
),
167212
converter=_jc.Float,
168213
),
169214
# float -> java.lang.Double
170215
Converter(
171-
predicate=lambda obj: isinstance(obj, float)
172-
and _jc.Double.MAX_VALUE <= obj <= _jc.Double.MAX_VALUE,
216+
predicate=lambda obj, **hints: isinstance(obj, float)
217+
and ("type" not in hints or hints["type"] in ("d", "double", "Double"))
218+
and (
219+
math.isinf(obj)
220+
or math.isnan(obj)
221+
or -_jc.Double.MAX_VALUE <= obj <= _jc.Double.MAX_VALUE
222+
),
173223
converter=_jc.Double,
174224
priority=Priority.NORMAL - 1,
175225
),
@@ -377,12 +427,12 @@ def __str__(self):
377427
return "{" + ", ".join(_jstr(v) for v in self) + "}"
378428

379429

380-
py_converters: typing.List[Converter] = []
430+
py_converters: List[Converter] = []
381431

382432

383433
def add_py_converter(converter: Converter):
384434
"""
385-
Adds a converter to the list used by to_python
435+
Add a converter to the list used by to_python.
386436
:param converter: A Converter from java to python
387437
"""
388438
py_converters.append(converter)
@@ -420,12 +470,12 @@ def to_python(data: Any, gentle: bool = False) -> Any:
420470
raise exc
421471

422472

423-
def _stock_py_converters() -> typing.List:
473+
def _stock_py_converters() -> List:
424474
"""
425-
Returns all java-to-python converters supported out of the box!
426-
This should only be called after the JVM has been started!
475+
Construct the Java-to-Python converters supported out of the box.
427476
:returns: A list of Converters
428477
"""
478+
start_jvm()
429479

430480
converters = [
431481
# Other (Exceptional) converter
@@ -682,7 +732,7 @@ def _import_numpy(required=True):
682732
def _is_table(obj: Any) -> bool:
683733
"""Check if obj is a table."""
684734
try:
685-
return isinstance(obj, jimport("org.scijava.table.Table"))
735+
return jinstance(obj, "org.scijava.table.Table")
686736
except BaseException:
687737
# No worries if scijava-table is not available.
688738
pass

src/scyjava/_java.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,20 @@ def jclass(data):
384384
raise TypeError("Cannot glean class from data of type: " + str(type(data)))
385385

386386

387+
def jinstance(obj, jtype):
388+
"""
389+
Test if the given object is an instance of a particular Java type.
390+
391+
:param obj: The object to check.
392+
:param jtype: The Java type, as either a jimported class or as a string.
393+
:returns: True iff the object is an instance of that Java type.
394+
"""
395+
if isinstance(jtype, str):
396+
jtype = jimport(jtype)
397+
398+
return isinstance(obj, jtype)
399+
400+
387401
def jstacktrace(exc):
388402
"""
389403
Extract the Java-side stack trace from a Java exception.

0 commit comments

Comments
 (0)
0