diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1163673d..8ddf0856 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,22 +11,27 @@ on: jobs: test: runs-on: '${{ matrix.os }}' + timeout-minutes: 20 strategy: + fail-fast: false matrix: - os: [ ubuntu-18.04 ] + os: [ ubuntu-22.04 ] java-version: [ 8 ] - python-version: [ '2.7', '3.5', '3.6', '3.7', '3.8', '3.9', '3.10' ] + python-version: [ '3.8' ] include: - - os: windows-2019 - java-version: 17 - python-version: '3.10' - - os: ubuntu-18.04 - java-version: 11 - python-version: '2.7' - - os: ubuntu-18.04 - java-version: 17 - python-version: '3.8' - name: Py ${{ matrix.python-version }}, Java ${{ matrix.java-version }}, ${{ matrix.os }} + - os: macos-14 + java-version: 8 + python-version: '3.12' + # - os: windows-2019 + # java-version: 11 + # python-version: '3.10' + # - os: ubuntu-22.04 + # java-version: 17 + # python-version: '3.11' + # - os: ubuntu-22.04 + # java-version: 21 + # python-version: '3.9' + name: Python ${{ matrix.python-version }}, Java ${{ matrix.java-version }}, ${{ matrix.os }} steps: - uses: actions/checkout@1e204e9a9253d643386038d443f96446fa156a97 # pin@v2.3.5 @@ -57,12 +62,7 @@ jobs: ./gradlew clean shell: bash - - name: Enable IPV6 - if: ${{ runner.os != 'Windows' }} - run: | - echo 0 | sudo tee /proc/sys/net/ipv6/conf/all/disable_ipv6 - - - name: Run gradle tests + - name: Run Gradle tests run: | cd py4j-java ./gradlew check @@ -82,7 +82,7 @@ jobs: echo `java -version` echo $JAVA_HOME # Java TLS tests are disabled until they can be fixed (refs #441) - pytest -k "not java_tls_test." + pytest -k "not java_tls_test." -vvv -s test-doc: name: Documentation build diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index ca56fef5..aa3c4711 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -61,7 +61,7 @@ We follow pep8 rather stricly: 3. Line length is 80 4. Code must pass the default flake8 (version 2.5) tests (pep8 + pyflakes) -Code must be compatible with Python 2.7 and from 3.4 to the newest released +Code must be compatible with Python 3.8 to the newest released version of Python. If external libraries must be used, they should be wrapped in a mechanism that diff --git a/README.rst b/README.rst index 087261db..fe8f159a 100644 --- a/README.rst +++ b/README.rst @@ -30,8 +30,8 @@ documentation fixes. Please visit the `contributing guide `_ for more information. -.. image:: https://img.shields.io/github/workflow/status/bartdag/py4j/test.svg - :target: https://github.com/bartdag/py4j/actions/workflows/test.yml +.. image:: https://img.shields.io/github/actions/workflow/status/py4j/py4j/test.yml.svg + :target: https://github.com/py4j/py4j/actions/workflows/test.yml .. image:: https://img.shields.io/pypi/l/py4j.svg diff --git a/py4j-java/src/test/java/py4j/commands/DirCommandTest.java b/py4j-java/src/test/java/py4j/commands/DirCommandTest.java index c177b292..fa0d1310 100644 --- a/py4j-java/src/test/java/py4j/commands/DirCommandTest.java +++ b/py4j-java/src/test/java/py4j/commands/DirCommandTest.java @@ -69,8 +69,8 @@ public class DirCommandTest { { // Defined in ExampleClass ExampleClassMethods.addAll(Arrays.asList(new String[] { "method1", "method2", "method3", "method4", "method5", - "method6", "method7", "method8", "method9", "method10", "method11", "getList", "getField1", "setField1", - "getStringArray", "getIntArray", "callHello", "callHello2", "static_method", "getInteger", + "method6", "method7", "method8", "method9", "method10", "method11", "method12", "getList", "getField1", + "setField1", "getStringArray", "getIntArray", "callHello", "callHello2", "static_method", "getInteger", "getBrokenStream", "getStream", "sleepFirstTimeOnly" })); // Defined in Object ExampleClassMethods.addAll(Arrays diff --git a/py4j-java/src/test/java/py4j/examples/ExampleClass.java b/py4j-java/src/test/java/py4j/examples/ExampleClass.java index 933daf82..75df0ef8 100644 --- a/py4j-java/src/test/java/py4j/examples/ExampleClass.java +++ b/py4j-java/src/test/java/py4j/examples/ExampleClass.java @@ -37,6 +37,7 @@ import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; public class ExampleClass { @@ -174,6 +175,20 @@ public BigInteger method11(BigInteger bi) { return bi.add(new BigInteger("1")); } + public int method12(HashSet set) { + Object element = set.stream().findAny().get(); + if (element instanceof Long) { + return 4; + } + if (element instanceof Integer) { + return 1; + } + if (element instanceof String) { + return 2; + } + return 3; + } + @SuppressWarnings("unused") private int private_method() { return 0; diff --git a/py4j-python/setup.py b/py4j-python/setup.py index 68c05303..221b575f 100644 --- a/py4j-python/setup.py +++ b/py4j-python/setup.py @@ -44,15 +44,11 @@ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Java", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Object Brokering", diff --git a/py4j-python/src/py4j/protocol.py b/py4j-python/src/py4j/protocol.py index 49d7e7ec..1f983d77 100644 --- a/py4j-python/src/py4j/protocol.py +++ b/py4j-python/src/py4j/protocol.py @@ -70,6 +70,13 @@ ITERATOR_TYPE = "g" PYTHON_PROXY_TYPE = "f" +class TypeHint: + """Enables users to provide a hint to the Python to Java converter specifying the accurate data type for a given value. + Essential to enforce i.e. correct number type, like Long.""" + def __init__(self, value, java_type): + self.value = value + self.java_type = java_type + # Protocol END = "e" ERROR = "x" @@ -273,6 +280,8 @@ def get_command_part(parameter, python_proxy_pool=None): if parameter is None: command_part = NULL_TYPE + elif isinstance(parameter, TypeHint): + command_part = parameter.java_type + smart_decode(parameter.value) elif isinstance(parameter, bool): command_part = BOOLEAN_TYPE + smart_decode(parameter) elif isinstance(parameter, Decimal): diff --git a/py4j-python/src/py4j/tests/client_server_test.py b/py4j-python/src/py4j/tests/client_server_test.py index e05e675b..68959373 100644 --- a/py4j-python/src/py4j/tests/client_server_test.py +++ b/py4j-python/src/py4j/tests/client_server_test.py @@ -146,7 +146,7 @@ def testSendObjects(self): start_gc_test=True, join=False) as p: p.join() client_server.shutdown() - self.assertEquals(1000, hello.calls) + self.assertEqual(1000, hello.calls) def testCleanConnections(self): """This test intentionally create multiple connections in multiple @@ -605,9 +605,9 @@ def testMultiClientServerWithSharedJavaThread(self): self.assertNotEqual(sharedPythonThreadId0, sharedPythonThreadId1) # Check that the Python thread id does not change between # invocations - self.assertEquals(sharedPythonThreadId0, + self.assertEqual(sharedPythonThreadId0, entry0.getSharedPythonThreadId()) - self.assertEquals(sharedPythonThreadId1, + self.assertEqual(sharedPythonThreadId1, entry1.getSharedPythonThreadId()) # ## 3 Hops to Shared Java Thread @@ -648,9 +648,9 @@ def testMultiClientServer(self): # ## 0 Hops to Thread ID # Check that the two thread getters get the same thread - self.assertEquals(thisThreadId, + self.assertEqual(thisThreadId, int(threadIdGetter0.getThreadId())) - self.assertEquals(thisThreadId, + self.assertEqual(thisThreadId, int(threadIdGetter1.getThreadId())) # ## 1 Hop to Thread ID diff --git a/py4j-python/src/py4j/tests/java_dir_test.py b/py4j-python/src/py4j/tests/java_dir_test.py index f8a1e0bd..77057c78 100644 --- a/py4j-python/src/py4j/tests/java_dir_test.py +++ b/py4j-python/src/py4j/tests/java_dir_test.py @@ -30,6 +30,7 @@ # overloaded "method10", "method11", + "method12", "getList", "getField1", "setField1", diff --git a/py4j-python/src/py4j/tests/java_gateway_test.py b/py4j-python/src/py4j/tests/java_gateway_test.py index fb357f6f..90800c7c 100644 --- a/py4j-python/src/py4j/tests/java_gateway_test.py +++ b/py4j-python/src/py4j/tests/java_gateway_test.py @@ -33,8 +33,8 @@ set_default_callback_accept_timeout, GatewayConnectionGuard, get_java_class) from py4j.protocol import ( - Py4JError, Py4JJavaError, Py4JNetworkError, decode_bytearray, - encode_bytearray, escape_new_line, unescape_new_line, smart_decode) + Py4JError, Py4JJavaError, Py4JNetworkError, TypeHint, LONG_TYPE, + decode_bytearray, encode_bytearray, escape_new_line, unescape_new_line, smart_decode) SERVER_PORT = 25333 @@ -364,8 +364,8 @@ def testNoneArg(self): try: ex.method2(None) ex2 = ex.method4(None) - self.assertEquals(ex2.getField1(), 3) - self.assertEquals(2, ex.method7(None)) + self.assertEqual(ex2.getField1(), 3) + self.assertEqual(2, ex.method7(None)) except Exception: print_exc() self.fail() @@ -439,11 +439,11 @@ def testSetField(self): ex = self.gateway.getNewExample() set_field(ex, "field10", 2334) - self.assertEquals(get_field(ex, "field10"), 2334) + self.assertEqual(get_field(ex, "field10"), 2334) sb = self.gateway.jvm.java.lang.StringBuffer("Hello World!") set_field(ex, "field21", sb) - self.assertEquals(get_field(ex, "field21").toString(), "Hello World!") + self.assertEqual(get_field(ex, "field21").toString(), "Hello World!") self.assertRaises(Exception, set_field, ex, "field1", 123) @@ -582,6 +582,7 @@ def testGCCollectNoMemoryManagement(self): enable_memory_management=False)) gc.collect() # Should have nothing in the finalizers + print(ThreadSafeFinalizer.finalizers) self.assertEqual(len(ThreadSafeFinalizer.finalizers), 0) def internal(): @@ -607,7 +608,7 @@ def internal(): class TypeConversionTest(unittest.TestCase): def setUp(self): self.p = start_example_app_process() - self.gateway = JavaGateway() + self.gateway = JavaGateway(auto_convert=True) def tearDown(self): safe_shutdown(self) @@ -619,6 +620,8 @@ def testLongInt(self): self.assertEqual(4, ex.method7(2147483648)) self.assertEqual(4, ex.method7(-2147483649)) self.assertEqual(4, ex.method7(long(2147483648))) + self.assertEqual(4, ex.method7(TypeHint(1, LONG_TYPE))) + self.assertEqual(4, ex.method12({TypeHint(1, LONG_TYPE)})) self.assertEqual(long(4), ex.method8(3)) self.assertEqual(4, ex.method8(3)) self.assertEqual(long(4), ex.method8(long(3))) @@ -1032,6 +1035,7 @@ def testAccessSubprocess(self): self.gateway = JavaGateway.launch_gateway() self.assertTrue(self.gateway.java_process) + @unittest.skipIf(sys.platform.startswith("win"), "Flaky on Windows") def testShutdownSubprocess(self): self.gateway = JavaGateway.launch_gateway() self.assertTrue(self.gateway.java_process) diff --git a/py4j-web/advanced_topics.rst b/py4j-web/advanced_topics.rst index 18504bbc..41ed9b4b 100644 --- a/py4j-web/advanced_topics.rst +++ b/py4j-web/advanced_topics.rst @@ -726,6 +726,27 @@ Java methods slightly less efficient because in the worst case, Py4J needs to go through all registered converters for all parameters. This is why automatic conversion is disabled by default. +.. _explicit_conversion: + +Explicit converting Python objects to Java primitives +----------------------------------------------------- + +Sometimes, especially when ``auto_convert=True`` it is difficult to enforce correct type +passed from Python to Java. Then, ``TypeHint`` from ``py4j.protocol`` may be used. +``java_type`` argument of constructor should be one of Java types defined in ``py4j.protocol``. + +So if you have method in Java like: + +.. code-block:: java + + void method(HashSet longs) {} + +Then you can pass arguments with correct type to this method with ``TypeHint`` + +:: + + >>> set_with_longs = { TypeHint(1, LONG_TYPE), TypeHint(2, LONG_TYPE) } + >>> gateway.jvm.my.Class().method(set_with_longs) .. _py4j_exceptions: diff --git a/py4j-web/contributing.rst b/py4j-web/contributing.rst index 4e1a55f1..709e4900 100644 --- a/py4j-web/contributing.rst +++ b/py4j-web/contributing.rst @@ -43,7 +43,7 @@ We follow pep8 rather strictly: 3. Line length is 80. 4. Code must pass the default flake8 tests (pep8 + pyflakes). -Code must be compatible with Python 2.7 and from 3.4 to the newest released +Code must be compatible with Python 3.8 to the newest released version of Python. If external libraries must be used, they should be wrapped in a mechanism that diff --git a/py4j-web/download.rst b/py4j-web/download.rst index cf98f5c9..03136090 100644 --- a/py4j-web/download.rst +++ b/py4j-web/download.rst @@ -32,8 +32,7 @@ Requirements Py4J requires: -* A Python interpreter. Py4J has been tested with CPython 2.7, - 3.4, 3.5, 3.6, 3.7, 3.8, 3.9 and 3.10. +* A Python interpreter. Py4J has been tested with 3.8+. * Java 7.0+. Py4J for Eclipse requires: diff --git a/py4j-web/install.rst b/py4j-web/install.rst index b11e82b6..e82f7dce 100644 --- a/py4j-web/install.rst +++ b/py4j-web/install.rst @@ -3,11 +3,11 @@ Installing Py4J =============== -Installing Python 2.7 or 3.4-3.10 +Installing Python 3.8-3.12 --------------------------------- Py4J is a library written in Python and Java. Currently, Py4J has been tested -with Python 2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9 and 3.10. You can install Python by going to the +with Python 3.8, 3.9, 3.10, 3.11 and 3.12. You can install Python by going to the `official Python download page `_. diff --git a/py4j-web/requirements-doc.txt b/py4j-web/requirements-doc.txt index f15077eb..3e8069e4 100644 --- a/py4j-web/requirements-doc.txt +++ b/py4j-web/requirements-doc.txt @@ -1 +1 @@ -Sphinx >=4.4,<5.0 +Sphinx >5.0,<6.0 diff --git a/setup.py b/setup.py index 965b821e..555ea398 100644 --- a/setup.py +++ b/setup.py @@ -54,13 +54,11 @@ "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Java", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Object Brokering",