diff --git a/py4j-python/.pydevproject b/py4j-python/.pydevproject
index 0ca60ff5..f64f44ed 100644
--- a/py4j-python/.pydevproject
+++ b/py4j-python/.pydevproject
@@ -2,6 +2,6 @@
/py4j-python/src
-python 2.6
+python 3.9
Default
\ No newline at end of file
diff --git a/py4j-python/nose.cfg b/py4j-python/nose.cfg
deleted file mode 100644
index f8747ca0..00000000
--- a/py4j-python/nose.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-[nosetests]
-verbosity=3
diff --git a/py4j-python/setup.cfg b/py4j-python/setup.cfg
deleted file mode 100644
index 79bc6784..00000000
--- a/py4j-python/setup.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-[bdist_wheel]
-# This flag says that the code is written to work on both Python 2 and Python
-# 3. If at all possible, it is good practice to do this. If you cannot, you
-# will need to generate wheels for each Python version that you support.
-universal=1
diff --git a/py4j-python/setup.py b/py4j-python/setup.py
index 221b575f..c61ad460 100644
--- a/py4j-python/setup.py
+++ b/py4j-python/setup.py
@@ -1,13 +1,9 @@
-try:
- from setuptools import setup
-except ImportError:
- from distutils.core import setup
+from setuptools import setup
import os
DOC_DIR = "doc"
DIST_DIR = "dist"
VERSION_PATH = os.path.join("src", "py4j", "version.py")
-# For Python 3 compatibility, we can't use execfile; this is 2to3's conversion:
exec(compile(open(VERSION_PATH).read(),
VERSION_PATH, "exec"))
VERSION = __version__ # noqa
@@ -37,18 +33,20 @@
author="Barthelemy Dagenais",
author_email="barthelemy@infobart.com",
license="BSD License",
+ python_requires=">=3.9",
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
- "Programming Language :: Python :: 2",
- "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
"Programming Language :: Java",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Object Brokering",
diff --git a/py4j-python/src/py4j/__init__.py b/py4j-python/src/py4j/__init__.py
index e6c041ac..be09cd99 100644
--- a/py4j-python/src/py4j/__init__.py
+++ b/py4j-python/src/py4j/__init__.py
@@ -1,5 +1,4 @@
# Py4J Package
-from __future__ import absolute_import
from . import version
__version__ = version.__version__
diff --git a/py4j-python/src/py4j/clientserver.py b/py4j-python/src/py4j/clientserver.py
index 7301a9ed..d0d7e247 100644
--- a/py4j-python/src/py4j/clientserver.py
+++ b/py4j-python/src/py4j/clientserver.py
@@ -1,4 +1,3 @@
-# -*- coding: UTF-8 -*-
"""Module that implements a different threading model between
a Java Virtual Machine a Python interpreter.
@@ -6,9 +5,6 @@
thread. For example, if a request is started in a Java UI thread and the Python
code calls some Java code, the Java code will be executed in the UI thread.
"""
-
-from __future__ import unicode_literals, absolute_import
-
from collections import deque
import logging
import socket
diff --git a/py4j-python/src/py4j/compat.py b/py4j-python/src/py4j/compat.py
index 6e9b2ce9..8958080b 100644
--- a/py4j-python/src/py4j/compat.py
+++ b/py4j-python/src/py4j/compat.py
@@ -1,112 +1,95 @@
-# coding: utf-8
"""
-Compatibility functions for unified behavior between Python 2.x and 3.x.
+Compatibility shims left over from py4j's Python 2 / 3 dual-support era.
+
+Python 2 support has been removed (py4j requires Python 3.9+). The
+names below are kept solely so that downstream code that still imports
+from this module (``from py4j.compat import unicode``, etc.) continues
+to work.
+
+New code inside py4j should use the Python 3 built-ins directly:
+
+ - ``unicode`` -> ``str``
+ - ``basestring`` -> ``str``
+ - ``long`` -> ``int``
+ - ``range`` -> built-in ``range``
+ - ``unichr`` -> ``chr``
+ - ``bytestr`` -> ``bytes``
+ - ``bytearray2`` -> ``bytes`` (or ``bytearray``; alias kept as ``bytes``)
+ - ``iteritems(d)`` -> ``d.items()``
+ - ``items(d)`` -> ``list(d.items())``
+ - ``next(x)`` -> built-in ``next(x)``
+ - ``Queue`` -> ``from queue import Queue``
+ - ``Empty`` -> ``from queue import Empty``
+ - ``CompatThread`` -> ``threading.Thread`` (already supports ``daemon``)
+
+Only ``hasattr2`` has no Python-3-builtin replacement: it uses
+``inspect.getattr_static`` to test for an attribute without triggering
+descriptor / ``__getattr__`` side effects, which is genuinely
+different behavior from the built-in ``hasattr``.
+
+This module may be removed in a future major release; downstream code
+should migrate off the imports above.
:author: Alex Grönholm
"""
-from __future__ import unicode_literals, absolute_import
-
import inspect
import sys
from threading import Thread
+from queue import Queue as _StdQueue, Empty as _StdEmpty
-version_info = sys.version_info
-
-if version_info.major < 3:
- def items(d):
- return d.items()
-
- def iteritems(d):
- return d.iteritems()
-
- def next(x):
- return x.next()
-
- range = xrange # noqa
-
- long = long # noqa
-
- basestring = basestring # noqa
-
- unicode = unicode # noqa
-
- bytearray2 = bytearray
- unichr = unichr # noqa
-
- bytestr = str
-
- tobytestr = str
-
- def isbytestr(s):
- return isinstance(s, str)
-
- def ispython3bytestr(s):
- return False
-
- def isbytearray(s):
- return isinstance(s, bytearray)
+version_info = sys.version_info
- def bytetoint(b):
- return ord(b)
- def bytetostr(b):
- return b
+def items(d):
+ return list(d.items())
- def strtobyte(b):
- return b
- import Queue
- Empty = Queue.Empty
- Queue = Queue.Queue
+def iteritems(d):
+ return d.items()
-else:
- def items(d):
- return list(d.items())
- def iteritems(d):
- return d.items()
+# Built-in passthroughs — kept as module attributes so existing
+# ``from py4j.compat import range, unicode, ...`` imports still work.
+range = range
+long = int
+basestring = str
+unicode = str
+bytearray2 = bytes
+unichr = chr
+bytestr = bytes
- next = next
- range = range
+def tobytestr(s):
+ return bytes(s, "ascii")
- long = int
- basestring = str
+def isbytestr(s):
+ return isinstance(s, bytes)
- unicode = str
- bytearray2 = bytes
+def ispython3bytestr(s):
+ return isinstance(s, bytes)
- unichr = chr
- bytestr = bytes
+def isbytearray(s):
+ return isinstance(s, bytearray)
- def tobytestr(s):
- return bytes(s, "ascii")
- def isbytestr(s):
- return isinstance(s, bytes)
+def bytetoint(b):
+ return b
- def ispython3bytestr(s):
- return isinstance(s, bytes)
- def isbytearray(s):
- return isinstance(s, bytearray)
+def bytetostr(b):
+ return str(b, encoding="ascii")
- def bytetoint(b):
- return b
- def bytetostr(b):
- return str(b, encoding="ascii")
+def strtobyte(s):
+ return bytes(s, encoding="ascii")
- def strtobyte(s):
- return bytes(s, encoding="ascii")
- import queue
- Queue = queue.Queue
- Empty = queue.Empty
+Queue = _StdQueue
+Empty = _StdEmpty
if hasattr(inspect, "getattr_static"):
@@ -116,19 +99,7 @@ def hasattr2(obj, attr):
hasattr2 = hasattr
-class CompatThread(Thread):
- """Compatibility Thread class.
-
- Allows Python 2 Thread class to accept daemon kwarg in init.
- """
-
- def __init__(self, *args, **kwargs):
- daemon = None
- try:
- daemon = kwargs.pop("daemon")
- except KeyError:
- pass
- super(CompatThread, self).__init__(*args, **kwargs)
-
- if daemon:
- self.daemon = daemon
+# Python 3's ``threading.Thread`` accepts the ``daemon`` kwarg natively,
+# so this is just a passthrough. Kept as a name for external callers
+# that imported ``CompatThread`` from this module.
+CompatThread = Thread
diff --git a/py4j-python/src/py4j/finalizer.py b/py4j-python/src/py4j/finalizer.py
index 381ba5e5..c131b44b 100644
--- a/py4j-python/src/py4j/finalizer.py
+++ b/py4j-python/src/py4j/finalizer.py
@@ -1,4 +1,3 @@
-# -*- coding: UTF-8 -*-
"""
Module that defines a Finalizer class responsible for registering and cleaning
finalizer
@@ -7,12 +6,8 @@
:author: Barthelemy Dagenais
"""
-from __future__ import unicode_literals, absolute_import
-
from threading import RLock
-from py4j.compat import items
-
class ThreadSafeFinalizer(object):
"""A `ThreadSafeFinalizer` is a global class used to register weak
@@ -64,7 +59,7 @@ def clear_finalizers(cls, clear_all=False):
if clear_all:
cls.finalizers.clear()
else:
- for id, ref in items(cls.finalizers):
+ for id, ref in list(cls.finalizers.items()):
if ref() is None:
cls.finalizers.pop(id, None)
@@ -116,7 +111,7 @@ def clear_finalizers(cls, clear_all=False):
if clear_all:
cls.finalizers.clear()
else:
- for id, ref in items(cls.finalizers):
+ for id, ref in list(cls.finalizers.items()):
if ref() is None:
cls.finalizers.pop(id, None)
diff --git a/py4j-python/src/py4j/java_collections.py b/py4j-python/src/py4j/java_collections.py
index 3ae3636e..b478309d 100644
--- a/py4j-python/src/py4j/java_collections.py
+++ b/py4j-python/src/py4j/java_collections.py
@@ -1,4 +1,3 @@
-# -*- coding: UTF-8 -*-
"""
Module responsible for converting Java collection classes to Python collection
classes. This module is optional but loaded by default.
@@ -8,26 +7,11 @@
:author: Barthelemy Dagenais
"""
-from __future__ import unicode_literals, absolute_import
-
-# As of Python 3.3, the abstract base classes in the collections module have
-# been moved to collections.abc.
-# (see https://docs.python.org/3.3/library/collections.abc.html)
-try:
- # Python >=3.3
- from collections.abc import (
- MutableMapping, Sequence, MutableSequence,
- MutableSet, Set)
-except ImportError:
- # Python <=3.2
- from collections import (
- MutableMapping, Sequence, MutableSequence,
- MutableSet, Set)
-import sys
-
-from py4j.compat import (
- iteritems, next, hasattr2, isbytearray,
- ispython3bytestr, basestring)
+from collections.abc import (
+ MutableMapping, Sequence, MutableSequence,
+ MutableSet, Set)
+
+from py4j.compat import hasattr2
from py4j.java_gateway import JavaObject, JavaMember, get_method, JavaClass
from py4j import protocol as proto
from py4j.protocol import (
@@ -105,7 +89,7 @@ def __str__(self):
def __repr__(self):
items = (
"{0}: {1}".format(repr(k), repr(v))
- for k, v in iteritems(self))
+ for k, v in self.items())
return "{{{0}}}".format(", ".join(items))
@@ -114,8 +98,8 @@ class JavaSet(JavaObject, MutableSet):
All operations possible on a Python set are implemented."""
- __EMPTY_SET = "set([])" if sys.version_info.major < 3 else "set()"
- __SET_TEMPLATE = "set([{0}])" if sys.version_info.major < 3 else "{{{0}}}"
+ __EMPTY_SET = "set()"
+ __SET_TEMPLATE = "{{{0}}}"
def __init__(self, target_id, gateway_client):
JavaObject.__init__(self, target_id, gateway_client)
@@ -507,8 +491,9 @@ class ListConverter(object):
def can_convert(self, object):
# Check for iterator protocol and should not be an instance of byte
# array (taken care of by protocol)
- return hasattr2(object, "__iter__") and not isbytearray(object) and\
- not ispython3bytestr(object) and not isinstance(object, basestring)
+ return hasattr2(object, "__iter__") and \
+ not isinstance(object, bytearray) and \
+ not isinstance(object, bytes) and not isinstance(object, str)
def convert(self, object, gateway_client):
ArrayList = JavaClass("java.util.ArrayList", gateway_client)
diff --git a/py4j-python/src/py4j/java_gateway.py b/py4j-python/src/py4j/java_gateway.py
index 1607c00f..f65e8369 100644
--- a/py4j-python/src/py4j/java_gateway.py
+++ b/py4j-python/src/py4j/java_gateway.py
@@ -1,4 +1,3 @@
-# -*- coding: UTF-8 -*-
"""Module to interact with objects in a Java Virtual Machine from a
Python Virtual Machine.
@@ -10,12 +9,11 @@
:author: Barthelemy Dagenais
"""
-from __future__ import unicode_literals, absolute_import
-
from collections import deque
import logging
import os
from pydoc import pager
+from queue import Queue
import select
import socket
import struct
@@ -26,8 +24,7 @@
from threading import Thread, RLock
import weakref
-from py4j.compat import (
- range, hasattr2, basestring, CompatThread, Queue)
+from py4j.compat import hasattr2
from py4j.finalizer import ThreadSafeFinalizer
from py4j import protocol as proto
from py4j.protocol import (
@@ -451,7 +448,7 @@ def is_instance_of(gateway, java_object, java_class):
:param java_class: can be a string (fully qualified name), a JavaClass
instance, or a JavaObject instance)
"""
- if isinstance(java_class, basestring):
+ if isinstance(java_class, str):
param = java_class
elif isinstance(java_class, JavaClass):
param = java_class._fqn
@@ -694,7 +691,7 @@ def _garbage_collect_proxy(pool, proxy_id):
return success
-class OutputConsumer(CompatThread):
+class OutputConsumer(Thread):
"""Thread that consumes output
"""
@@ -725,7 +722,7 @@ def run(self):
self.redirect_func(smart_decode(line))
-class ProcessConsumer(CompatThread):
+class ProcessConsumer(Thread):
"""Thread that ensures process stdout and stderr are properly closed.
"""
diff --git a/py4j-python/src/py4j/protocol.py b/py4j-python/src/py4j/protocol.py
index da13fdd2..2f01514e 100644
--- a/py4j-python/src/py4j/protocol.py
+++ b/py4j-python/src/py4j/protocol.py
@@ -16,17 +16,10 @@
:author: Barthelemy Dagenais
"""
-from __future__ import unicode_literals, absolute_import
-
from base64 import standard_b64encode, standard_b64decode
from decimal import Decimal
-from py4j.compat import (
- long, basestring, unicode, bytearray2,
- bytestr, isbytestr, isbytearray, ispython3bytestr,
- bytetoint, bytetostr, strtobyte)
-
JAVA_MAX_INT = 2147483647
JAVA_MIN_INT = -2147483648
@@ -159,7 +152,7 @@
OUTPUT_CONVERTER = {
NULL_TYPE: (lambda x, y: None),
BOOLEAN_TYPE: (lambda value, y: value.lower() == "true"),
- LONG_TYPE: (lambda value, y: long(value)),
+ LONG_TYPE: (lambda value, y: int(value)),
DECIMAL_TYPE: (lambda value, y: Decimal(value)),
INTEGER_TYPE: (lambda value, y: int(value)),
BYTES_TYPE: (lambda value, y: decode_bytearray(value)),
@@ -213,13 +206,12 @@ def unescape_new_line(escaped):
def smart_decode(s):
- if isinstance(s, unicode):
+ if isinstance(s, str):
return s
- elif isinstance(s, bytestr):
- # Should never reach this case in Python 3
- return unicode(s, "utf-8")
+ elif isinstance(s, bytes):
+ return str(s, "utf-8")
else:
- return unicode(s)
+ return str(s)
def encode_float(float_value):
@@ -234,16 +226,16 @@ def encode_float(float_value):
def encode_bytearray(barray):
- if isbytestr(barray):
- return bytetostr(standard_b64encode(barray))
+ if isinstance(barray, bytes):
+ return str(standard_b64encode(barray), encoding="ascii")
else:
- newbytestr = bytestr(barray)
- return bytetostr(standard_b64encode(newbytestr))
+ newbytestr = bytes(barray)
+ return str(standard_b64encode(newbytestr), encoding="ascii")
def decode_bytearray(encoded):
- new_bytes = strtobyte(encoded)
- return bytearray2([bytetoint(b) for b in standard_b64decode(new_bytes)])
+ new_bytes = bytes(encoded, encoding="ascii")
+ return bytes([b for b in standard_b64decode(new_bytes)])
def is_python_proxy(parameter):
@@ -265,7 +257,7 @@ def get_command_part(parameter, python_proxy_pool=None):
"""Converts a Python object into a string representation respecting the
Py4J protocol.
- For example, the integer `1` is converted to `u"i1"`
+ For example, the integer `1` is converted to `"i1"`
:param parameter: the object to convert
:rtype: the string representing the command part
@@ -281,15 +273,15 @@ def get_command_part(parameter, python_proxy_pool=None):
elif isinstance(parameter, int) and parameter <= JAVA_MAX_INT\
and parameter >= JAVA_MIN_INT:
command_part = INTEGER_TYPE + smart_decode(parameter)
- elif isinstance(parameter, long) or isinstance(parameter, int):
+ elif isinstance(parameter, int):
command_part = LONG_TYPE + smart_decode(parameter)
elif isinstance(parameter, float):
command_part = DOUBLE_TYPE + encode_float(parameter)
- elif isbytearray(parameter):
+ elif isinstance(parameter, bytearray):
command_part = BYTES_TYPE + encode_bytearray(parameter)
- elif ispython3bytestr(parameter):
+ elif isinstance(parameter, bytes):
command_part = BYTES_TYPE + encode_bytearray(parameter)
- elif isinstance(parameter, basestring):
+ elif isinstance(parameter, str):
command_part = STRING_TYPE + escape_new_line(parameter)
elif is_python_proxy(parameter):
command_part = PYTHON_PROXY_TYPE + python_proxy_pool.put(parameter)
@@ -410,7 +402,7 @@ def register_input_converter(converter, prepend=False):
When initialized with `auto_convert=True`, a :class:`JavaGateway
` will use the input converters on any
parameter that is not a :class:`JavaObject `
- or `basestring` instance.
+ or `str` instance.
:param converter: A converter that declares the methods
`can_convert(object)` and `convert(object,gateway_client)`.
@@ -456,8 +448,6 @@ class Py4JJavaError(Py4JError):
`str(py4j_java_error)` returns the error message and the stack trace
available on the Java side (similar to printStackTrace()).
- Note that `str(py4j_java_error)` in Python 2 might not automatically handle
- a non-ascii unicode string but throw an error if the exception contains it.
"""
def __init__(self, msg, java_exception):
@@ -471,7 +461,4 @@ def __str__(self):
gateway_client = self.java_exception._gateway_client
answer = gateway_client.send_command(self.exception_cmd)
return_value = get_return_value(answer, gateway_client, None, None)
- # Note: technically this should return a bytestring 'str' rather than
- # unicodes in Python 2; however, it can return unicodes for now.
- # See https://github.com/bartdag/py4j/issues/306 for more details.
return "{0}: {1}".format(self.errmsg, return_value)
diff --git a/py4j-python/src/py4j/signals.py b/py4j-python/src/py4j/signals.py
index cc3d37fc..1627a8d1 100644
--- a/py4j-python/src/py4j/signals.py
+++ b/py4j-python/src/py4j/signals.py
@@ -1,4 +1,3 @@
-# -*- coding: UTF-8 -*-
"""Module that provides a simple signals library.
The signals pattern is very similar to the listener/observer pattern.
@@ -7,8 +6,6 @@
from inspect import ismethod
from threading import Lock
-from py4j.compat import range
-
def make_id(func):
if ismethod(func):
diff --git a/py4j-python/src/py4j/tests/byte_string_test.py b/py4j-python/src/py4j/tests/byte_string_test.py
index 2f9ea060..9f7a3919 100644
--- a/py4j-python/src/py4j/tests/byte_string_test.py
+++ b/py4j-python/src/py4j/tests/byte_string_test.py
@@ -1,6 +1,3 @@
-# -*- coding: UTF-8 -*-
-from __future__ import absolute_import
-
import unittest
import time
diff --git a/py4j-python/src/py4j/tests/client_server_test.py b/py4j-python/src/py4j/tests/client_server_test.py
index 3e83e226..ef3fd04a 100644
--- a/py4j-python/src/py4j/tests/client_server_test.py
+++ b/py4j-python/src/py4j/tests/client_server_test.py
@@ -1,4 +1,3 @@
-# -*- coding: UTF-8 -*-
from contextlib import contextmanager
import gc
from multiprocessing import Process
diff --git a/py4j-python/src/py4j/tests/finalizer_test.py b/py4j-python/src/py4j/tests/finalizer_test.py
index edebf741..0f2baed3 100644
--- a/py4j-python/src/py4j/tests/finalizer_test.py
+++ b/py4j-python/src/py4j/tests/finalizer_test.py
@@ -3,8 +3,6 @@
@author: barthelemy
"""
-from __future__ import unicode_literals, absolute_import
-
import gc
import unittest
from weakref import ref
diff --git a/py4j-python/src/py4j/tests/java_array_test.py b/py4j-python/src/py4j/tests/java_array_test.py
index 694f0b92..0d06964f 100644
--- a/py4j-python/src/py4j/tests/java_array_test.py
+++ b/py4j-python/src/py4j/tests/java_array_test.py
@@ -3,8 +3,6 @@
@author: Barthelemy Dagenais
"""
-from __future__ import unicode_literals, absolute_import
-
import time
import unittest
diff --git a/py4j-python/src/py4j/tests/java_callback_test.py b/py4j-python/src/py4j/tests/java_callback_test.py
index 8e1d634f..a2f7b179 100644
--- a/py4j-python/src/py4j/tests/java_callback_test.py
+++ b/py4j-python/src/py4j/tests/java_callback_test.py
@@ -3,8 +3,6 @@
@author: Barthelemy Dagenais
"""
-from __future__ import unicode_literals, absolute_import
-
from contextlib import contextmanager
from multiprocessing import Process
import subprocess
@@ -12,7 +10,6 @@
from traceback import print_exc
import unittest
-from py4j.compat import range
from py4j.java_gateway import (
JavaGateway, PythonProxyPool, CallbackServerParameters,
set_default_callback_accept_timeout, is_instance_of)
diff --git a/py4j-python/src/py4j/tests/java_dir_test.py b/py4j-python/src/py4j/tests/java_dir_test.py
index f8a1e0bd..7cc2de47 100644
--- a/py4j-python/src/py4j/tests/java_dir_test.py
+++ b/py4j-python/src/py4j/tests/java_dir_test.py
@@ -1,5 +1,3 @@
-from __future__ import unicode_literals, absolute_import
-
from py4j.java_gateway import (
java_import, UserHelpAutoCompletion)
from py4j.protocol import Py4JError
diff --git a/py4j-python/src/py4j/tests/java_gateway_test.py b/py4j-python/src/py4j/tests/java_gateway_test.py
index 7392179e..66e81679 100644
--- a/py4j-python/src/py4j/tests/java_gateway_test.py
+++ b/py4j-python/src/py4j/tests/java_gateway_test.py
@@ -1,11 +1,8 @@
-# -*- coding: UTF-8 -*-
"""
Created on Dec 10, 2009
@author: barthelemy
"""
-from __future__ import unicode_literals, absolute_import, print_function
-
from collections import deque
from contextlib import contextmanager
from decimal import Decimal
@@ -14,6 +11,7 @@
from multiprocessing import Process
import os
import sys
+from queue import Queue
from socket import AF_INET, SOCK_STREAM, socket
import subprocess
import tempfile
@@ -22,9 +20,6 @@
from traceback import print_exc
import unittest
-from py4j.compat import (
- range, isbytearray, ispython3bytestr, bytearray2, long,
- Queue)
from py4j.finalizer import ThreadSafeFinalizer
from py4j.java_gateway import (
JavaGateway, JavaMember, get_field, get_method,
@@ -366,7 +361,7 @@ def testProtocolReceive(self):
self.assertAlmostEqual(1.25, ex.method3())
self.assertTrue(ex.method2() is None)
self.assertTrue(ex.method4())
- self.assertEqual(long(123), ex.method8())
+ self.assertEqual(123, ex.method8())
self.assertEqual(float("inf"), ex.method8())
self.gateway.shutdown()
@@ -496,7 +491,7 @@ def testAutoField(self):
gateway_parameters=GatewayParameters(auto_field=True))
ex = self.gateway.getNewExample()
self.assertEqual(ex.field10, 10)
- self.assertEqual(ex.field11, long(11))
+ self.assertEqual(ex.field11, 11)
sb = ex.field20
sb.append("Hello")
self.assertEqual("Hello", sb.toString())
@@ -733,11 +728,11 @@ def testLongInt(self):
self.assertEqual(1, ex.method7(1234))
self.assertEqual(4, ex.method7(2147483648))
self.assertEqual(4, ex.method7(-2147483649))
- self.assertEqual(4, ex.method7(long(2147483648)))
- self.assertEqual(long(4), ex.method8(3))
+ self.assertEqual(4, ex.method7(2147483648))
+ self.assertEqual(4, ex.method8(3))
self.assertEqual(4, ex.method8(3))
- self.assertEqual(long(4), ex.method8(long(3)))
- self.assertEqual(long(4), ex.method9(long(3)))
+ self.assertEqual(4, ex.method8(3))
+ self.assertEqual(4, ex.method9(3))
try:
ex.method8(3000000000000000000000000000000000000)
self.fail("Should not be able to convert overflowing long")
@@ -874,7 +869,7 @@ def testBytesType(self):
int_list = [0, 1, 10, 127, 128, 255]
ba1 = bytearray(int_list)
# Same for Python2, bytes for Python 3
- ba2 = bytearray2(int_list)
+ ba2 = bytes(int_list)
a1 = ex.getBytesValue(ba1)
a2 = ex.getBytesValue(ba2)
for i1, i2 in zip(a1, int_list):
@@ -891,7 +886,7 @@ def testBytesType2(self):
# strings)
# Python 3: bytes (because bytes is closer to the byte[] representation
# in Java)
- self.assertTrue(isbytearray(a1) or ispython3bytestr(a1))
+ self.assertTrue(isinstance(a1, (bytearray, bytes)))
for i1, i2 in zip(a1, int_list):
self.assertEqual(i1, i2)
@@ -1164,11 +1159,7 @@ def testShutdownSubprocess(self):
# giving two orders of magnitude of headroom over a healthy
# JVM's tens-of-ms shutdown - reaching 5 s indicates a real
# hang, not scheduler jitter.
- if sys.version_info < (3,):
- sleep()
- self.assertFalse(self.gateway.java_process.poll() is None)
- else:
- self.gateway.java_process.wait(5)
+ self.gateway.java_process.wait(5)
# Popen.wait() will raise a TimeoutExpired exception if the subprocess
# has not yet terminated.
@@ -1183,13 +1174,9 @@ def testShutdownSubprocessThatDiesOnExit(self):
self.assertTrue(self.gateway.java_process.poll() is None)
self.gateway.java_process.stdin.write("\n".encode("utf-8"))
self.gateway.java_process.stdin.flush()
- if sys.version_info < (3,):
- sleep()
- self.assertFalse(self.gateway.java_process.poll() is None)
- else:
- # Same rationale as testShutdownSubprocess: 1 s timed out on
- # Py3.11/Java 21/macOS. 5 s gives headroom for noisy CI.
- self.gateway.java_process.wait(5)
+ # Same rationale as testShutdownSubprocess: 1 s timed out on
+ # Py3.11/Java 21/macOS. 5 s gives headroom for noisy CI.
+ self.gateway.java_process.wait(5)
def testJavaopts(self):
self.gateway = JavaGateway.launch_gateway(javaopts=["-Xmx64m"])
diff --git a/py4j-python/src/py4j/tests/java_help_test.py b/py4j-python/src/py4j/tests/java_help_test.py
index 3a6cf26d..3f2f1abb 100644
--- a/py4j-python/src/py4j/tests/java_help_test.py
+++ b/py4j-python/src/py4j/tests/java_help_test.py
@@ -1,6 +1,3 @@
-from __future__ import unicode_literals, absolute_import
-
-
from py4j.tests.java_gateway_test import gateway, example_app_process
diff --git a/py4j-python/src/py4j/tests/java_iter_test.py b/py4j-python/src/py4j/tests/java_iter_test.py
index 9873c633..317462b5 100644
--- a/py4j-python/src/py4j/tests/java_iter_test.py
+++ b/py4j-python/src/py4j/tests/java_iter_test.py
@@ -1,5 +1,3 @@
-from __future__ import unicode_literals, absolute_import
-
import unittest
from py4j.java_gateway import JavaGateway, is_instance_of
diff --git a/py4j-python/src/py4j/tests/java_list_test.py b/py4j-python/src/py4j/tests/java_list_test.py
index bccef6e0..b6f6c0d6 100644
--- a/py4j-python/src/py4j/tests/java_list_test.py
+++ b/py4j-python/src/py4j/tests/java_list_test.py
@@ -3,11 +3,8 @@
@author: barthelemy
"""
-from __future__ import unicode_literals, absolute_import
-
import unittest
-from py4j.compat import unicode
from py4j.java_gateway import JavaGateway, GatewayParameters
from py4j.protocol import Py4JJavaError, Py4JError
from py4j.tests.java_gateway_test import (
@@ -15,7 +12,7 @@
def get_list(count):
- return [unicode(i) for i in range(count)]
+ return [str(i) for i in range(count)]
class AutoConvertTest(unittest.TestCase):
diff --git a/py4j-python/src/py4j/tests/java_map_test.py b/py4j-python/src/py4j/tests/java_map_test.py
index b8abf22f..f3d00783 100644
--- a/py4j-python/src/py4j/tests/java_map_test.py
+++ b/py4j-python/src/py4j/tests/java_map_test.py
@@ -3,8 +3,6 @@
@author: barthelemy
"""
-from __future__ import unicode_literals, absolute_import
-
import unittest
from py4j.java_gateway import JavaGateway, GatewayParameters
diff --git a/py4j-python/src/py4j/tests/java_set_test.py b/py4j-python/src/py4j/tests/java_set_test.py
index 8afdf45d..d5901701 100644
--- a/py4j-python/src/py4j/tests/java_set_test.py
+++ b/py4j-python/src/py4j/tests/java_set_test.py
@@ -3,8 +3,6 @@
@author: Barthelemy Dagenais
"""
-from __future__ import unicode_literals, absolute_import
-
import unittest
from py4j.java_gateway import JavaGateway, GatewayParameters
diff --git a/py4j-python/src/py4j/tests/java_tls_test.py b/py4j-python/src/py4j/tests/java_tls_test.py
index fefcd6c3..7a2947d9 100644
--- a/py4j-python/src/py4j/tests/java_tls_test.py
+++ b/py4j-python/src/py4j/tests/java_tls_test.py
@@ -3,8 +3,6 @@
@author: Nick White
"""
-from __future__ import unicode_literals, absolute_import
-
from multiprocessing import Process
import subprocess
import unittest
diff --git a/py4j-python/src/py4j/tests/memory_leak_test.py b/py4j-python/src/py4j/tests/memory_leak_test.py
index 950d3b66..e3ba9c88 100644
--- a/py4j-python/src/py4j/tests/memory_leak_test.py
+++ b/py4j-python/src/py4j/tests/memory_leak_test.py
@@ -1,4 +1,3 @@
-# -*- coding: UTF-8 -*-
import platform
from contextlib import contextmanager
import gc
diff --git a/py4j-python/src/py4j/tests/multithreadtest.py b/py4j-python/src/py4j/tests/multithreadtest.py
index eeb53f8d..f0492574 100644
--- a/py4j-python/src/py4j/tests/multithreadtest.py
+++ b/py4j-python/src/py4j/tests/multithreadtest.py
@@ -3,12 +3,9 @@
@author: barthelemy
"""
-from __future__ import unicode_literals, absolute_import
-
from threading import Thread
import unittest
-from py4j.compat import range
from py4j.java_gateway import JavaGateway
from py4j.tests.java_gateway_test import (
start_example_app_process, sleep)
diff --git a/py4j-python/src/py4j/tests/protocol_test.py b/py4j-python/src/py4j/tests/protocol_test.py
new file mode 100644
index 00000000..c24e548b
--- /dev/null
+++ b/py4j-python/src/py4j/tests/protocol_test.py
@@ -0,0 +1,229 @@
+"""
+Pure-protocol unit tests covering Python 2 / 3 boundary semantics.
+
+Pinned here so the Python 2 removal can't silently regress the
+externally-visible behaviour of ``py4j.compat`` (which downstream
+projects still import from) or the protocol-encoding paths that
+deal with ``bytes`` vs ``str`` distinctions.
+
+No JVM dependency — fast pure-Python tests.
+"""
+import unittest
+
+from py4j.protocol import (
+ decode_bytearray, encode_bytearray, encode_float,
+ escape_new_line, unescape_new_line, get_command_part,
+ smart_decode,
+ NULL_TYPE, BOOLEAN_TYPE, INTEGER_TYPE, LONG_TYPE, DOUBLE_TYPE,
+ BYTES_TYPE, STRING_TYPE, JAVA_INFINITY, JAVA_NEGATIVE_INFINITY,
+ JAVA_NAN, JAVA_MAX_INT, JAVA_MIN_INT,
+)
+
+
+class EncodeBytearrayRoundTripTest(unittest.TestCase):
+ """``encode_bytearray`` / ``decode_bytearray`` round-trip protected
+ after the Python 2 removal collapsed the ``bytearray2`` /
+ ``bytestr`` aliases into ``bytes``.
+ """
+
+ def test_round_trip_bytes(self):
+ original = b"\x00\x01\x02\xff\xfe\xfd"
+ encoded = encode_bytearray(original)
+ self.assertIsInstance(encoded, str)
+ self.assertEqual(decode_bytearray(encoded), original)
+
+ def test_round_trip_bytearray(self):
+ original = bytearray(b"abc\x00xyz")
+ encoded = encode_bytearray(original)
+ self.assertIsInstance(encoded, str)
+ # decode returns bytes, but content must compare equal.
+ self.assertEqual(decode_bytearray(encoded), bytes(original))
+
+ def test_round_trip_empty(self):
+ self.assertEqual(decode_bytearray(encode_bytearray(b"")), b"")
+
+ def test_round_trip_high_bytes(self):
+ # All single-byte values, exercises the full 0-255 range.
+ original = bytes(range(256))
+ self.assertEqual(decode_bytearray(encode_bytearray(original)), original)
+
+
+class EscapeNewLineTest(unittest.TestCase):
+ """``escape_new_line`` / ``unescape_new_line`` round-trip protected.
+ The function preserves a long-standing wrapping behaviour for
+ ``bytes`` inputs (via ``smart_decode``) so removing ``smart_decode``
+ here would be a behaviour change, not a Python 2 removal.
+ """
+
+ def test_str_passthrough_simple(self):
+ self.assertEqual(escape_new_line("hello"), "hello")
+
+ def test_escapes_backslash_then_newlines(self):
+ # Order matters: backslash must be doubled first so that the
+ # ``\n`` / ``\r`` insertions don't get re-escaped.
+ self.assertEqual(escape_new_line("a\\b\rc\nd"), "a\\\\b\\rc\\nd")
+
+ def test_round_trip_with_unescape(self):
+ original = "first line\nsecond\tline\rthird\\fourth"
+ self.assertEqual(unescape_new_line(escape_new_line(original)), original)
+
+ def test_falsy_passthrough(self):
+ # Documented behaviour: empty / None returns input unchanged.
+ self.assertEqual(escape_new_line(""), "")
+ self.assertIsNone(escape_new_line(None))
+
+
+class GetCommandPartTest(unittest.TestCase):
+ """Spot-check ``get_command_part`` after the type-check aliases
+ (``isinstance(x, long)`` etc.) were collapsed into native py3
+ types.
+ """
+
+ def _strip(self, s):
+ self.assertTrue(s.endswith("\n"))
+ return s[:-1]
+
+ def test_none_uses_null_prefix(self):
+ self.assertEqual(self._strip(get_command_part(None)), NULL_TYPE.strip())
+
+ def test_bool_dispatched_before_int(self):
+ # ``isinstance(True, int)`` is True in Python — the bool branch
+ # must run first so booleans don't get INTEGER-encoded.
+ self.assertTrue(
+ self._strip(get_command_part(True)).startswith(BOOLEAN_TYPE))
+ self.assertTrue(
+ self._strip(get_command_part(False)).startswith(BOOLEAN_TYPE))
+
+ def test_small_int_uses_integer_prefix(self):
+ self.assertEqual(
+ self._strip(get_command_part(42)),
+ INTEGER_TYPE + "42")
+
+ def test_int_at_java_max_uses_integer_prefix(self):
+ # Inclusive boundary.
+ self.assertEqual(
+ self._strip(get_command_part(JAVA_MAX_INT)),
+ INTEGER_TYPE + str(JAVA_MAX_INT))
+
+ def test_int_above_java_max_uses_long_prefix(self):
+ big = JAVA_MAX_INT + 1
+ self.assertEqual(
+ self._strip(get_command_part(big)),
+ LONG_TYPE + str(big))
+
+ def test_int_below_java_min_uses_long_prefix(self):
+ small = JAVA_MIN_INT - 1
+ self.assertEqual(
+ self._strip(get_command_part(small)),
+ LONG_TYPE + str(small))
+
+ def test_float_uses_double_prefix(self):
+ self.assertEqual(
+ self._strip(get_command_part(0.5)),
+ DOUBLE_TYPE + "0.5")
+
+ def test_str_uses_string_prefix_with_escaping(self):
+ out = self._strip(get_command_part("a\nb"))
+ self.assertEqual(out, STRING_TYPE + "a\\nb")
+
+ def test_bytes_uses_bytes_prefix(self):
+ out = self._strip(get_command_part(b"abc"))
+ self.assertTrue(out.startswith(BYTES_TYPE))
+
+ def test_bytearray_uses_bytes_prefix(self):
+ out = self._strip(get_command_part(bytearray(b"abc")))
+ self.assertTrue(out.startswith(BYTES_TYPE))
+
+
+class EncodeFloatTest(unittest.TestCase):
+ """``encode_float`` after the ``smart_decode(repr(...))`` /
+ ``unicode(...)`` aliases were inlined. Java-side spellings for
+ inf / -inf / NaN preserved.
+ """
+
+ def test_positive_infinity(self):
+ self.assertEqual(encode_float(float("inf")), JAVA_INFINITY)
+
+ def test_negative_infinity(self):
+ self.assertEqual(encode_float(float("-inf")), JAVA_NEGATIVE_INFINITY)
+
+ def test_nan(self):
+ self.assertEqual(encode_float(float("nan")), JAVA_NAN)
+
+ def test_finite_float(self):
+ self.assertEqual(encode_float(1.0), "1.0")
+
+
+class SmartDecodeTest(unittest.TestCase):
+ """``smart_decode`` body was rewritten with native py3 types
+ (``str`` instead of ``unicode``; ``bytes`` instead of ``bytestr``).
+ Function-level behaviour must be unchanged.
+ """
+
+ def test_str_input_returns_input_unchanged(self):
+ self.assertEqual(smart_decode("hello"), "hello")
+
+ def test_bytes_input_decodes_as_utf8(self):
+ self.assertEqual(smart_decode(b"hello"), "hello")
+ self.assertEqual(smart_decode("café".encode("utf-8")), "café")
+
+ def test_non_string_falls_back_to_str(self):
+ self.assertEqual(smart_decode(123), "123")
+ self.assertEqual(smart_decode(0.5), "0.5")
+ self.assertEqual(smart_decode(None), "None")
+
+
+class CompatModuleBackcompatTest(unittest.TestCase):
+ """``py4j.compat`` is kept around as a soft-deprecated shim for any
+ external callers that imported its py2/3 names
+ (``from py4j.compat import unicode``, etc.). This pins every
+ exported name to its py3 equivalent so a future cleanup of the
+ module can't silently break downstream imports.
+ """
+
+ def test_compat_exports_resolve(self):
+ from py4j.compat import ( # noqa: F401
+ items, iteritems, range, long, basestring, unicode,
+ bytearray2, unichr, bytestr, tobytestr, isbytestr,
+ ispython3bytestr, isbytearray, bytetoint, bytetostr,
+ strtobyte, Queue, Empty, hasattr2, CompatThread,
+ version_info,
+ )
+
+ def test_compat_aliases_resolve_to_py3_builtins(self):
+ from py4j import compat
+ self.assertIs(compat.long, int)
+ self.assertIs(compat.basestring, str)
+ self.assertIs(compat.unicode, str)
+ self.assertIs(compat.bytestr, bytes)
+ self.assertIs(compat.range, range)
+ self.assertIs(compat.unichr, chr)
+
+ def test_compat_helpers_use_py3_semantics(self):
+ from py4j import compat
+ self.assertTrue(compat.isbytestr(b""))
+ self.assertFalse(compat.isbytestr(""))
+ self.assertTrue(compat.ispython3bytestr(b""))
+ self.assertFalse(compat.ispython3bytestr(""))
+ self.assertTrue(compat.isbytearray(bytearray()))
+ self.assertFalse(compat.isbytearray(b""))
+ self.assertEqual(compat.bytetoint(b"a"[0]), b"a"[0])
+ self.assertEqual(compat.bytetostr(b"abc"), "abc")
+ self.assertEqual(compat.strtobyte("abc"), b"abc")
+ self.assertEqual(compat.items({"a": 1}), [("a", 1)])
+ self.assertEqual(list(compat.iteritems({"a": 1})), [("a", 1)])
+
+ def test_compat_thread_is_threading_thread(self):
+ from threading import Thread
+ from py4j.compat import CompatThread
+ self.assertIs(CompatThread, Thread)
+
+ def test_compat_queue_is_stdlib_queue(self):
+ from queue import Queue as StdQueue, Empty as StdEmpty
+ from py4j.compat import Queue, Empty
+ self.assertIs(Queue, StdQueue)
+ self.assertIs(Empty, StdEmpty)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/py4j-python/src/py4j/tests/py4j_signals_test.py b/py4j-python/src/py4j/tests/py4j_signals_test.py
index 05ffab9e..118fade9 100644
--- a/py4j-python/src/py4j/tests/py4j_signals_test.py
+++ b/py4j-python/src/py4j/tests/py4j_signals_test.py
@@ -1,6 +1,3 @@
-# -*- coding: UTF-8 -*-
-from __future__ import unicode_literals, absolute_import
-
from collections import defaultdict
import unittest
diff --git a/py4j-python/src/py4j/tests/signals_test.py b/py4j-python/src/py4j/tests/signals_test.py
index 6406418d..85f2d102 100644
--- a/py4j-python/src/py4j/tests/signals_test.py
+++ b/py4j-python/src/py4j/tests/signals_test.py
@@ -1,6 +1,3 @@
-# -*- coding: UTF-8 -*-
-from __future__ import unicode_literals, absolute_import
-
from unittest import TestCase
from py4j.signals import Signal
diff --git a/py4j-python/tox.ini b/py4j-python/tox.ini
index a28ffe50..00793a7f 100644
--- a/py4j-python/tox.ini
+++ b/py4j-python/tox.ini
@@ -1,6 +1,6 @@
[tox]
-envlist=py27,py34,py35
+envlist = py{39,310,311,312,313}
[testenv]
-deps=nose
-commands=nosetests -w src -c nose.cfg
+deps = -r requirements-test.txt
+commands = pytest -k "not java_tls_test." src/py4j/tests {posargs}
diff --git a/py4j-web/.pydevproject b/py4j-web/.pydevproject
index 266ba195..257f7e4c 100644
--- a/py4j-web/.pydevproject
+++ b/py4j-web/.pydevproject
@@ -1,4 +1,4 @@
-python 2.6
+python 3.9
Default
\ No newline at end of file
diff --git a/py4j-web/advanced_topics.rst b/py4j-web/advanced_topics.rst
index e7f9f0fa..5a50b479 100644
--- a/py4j-web/advanced_topics.rst
+++ b/py4j-web/advanced_topics.rst
@@ -146,7 +146,7 @@ access to a byte required a call between the Python and the Java interpreter
In summary:
* If from Java, you return a byte[], Py4J will convert the byte[] to a
- bytearray (Python 2.x) or bytes (Python 3.x) variable in Python.
+ bytes variable in Python.
* If from Python, you pass a bytearray or bytes variable to the Java side,
Py4J will convert it to a byte[].