diff --git a/flexx/__init__.py b/flexx/__init__.py index e944d3fc..eb38afd5 100644 --- a/flexx/__init__.py +++ b/flexx/__init__.py @@ -31,8 +31,8 @@ # Assert compatibility import sys -if sys.version_info < (3, 5): # pragma: no cover - raise RuntimeError('Flexx needs at least Python 3.5') +if sys.version_info < (3, 8): # pragma: no cover + raise RuntimeError('Flexx needs at least Python 3.8') # Import config object from ._config import config # noqa diff --git a/flexx/app/_server.py b/flexx/app/_server.py index 2cf9a88f..89cd5853 100644 --- a/flexx/app/_server.py +++ b/flexx/app/_server.py @@ -118,7 +118,7 @@ def __init__(self, host, port, loop=None, **kwargs): # Please add comment: What is this used for?? asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) if loop is None: - self._loop = asyncio.get_event_loop() + self._loop = _loop.get_or_create_event_loop() else: assert isinstance(loop, asyncio.AbstractEventLoop) self._loop = loop @@ -140,7 +140,7 @@ def start(self): raise RuntimeError('Cannot start a closed or non-serving server!') if self._running: raise RuntimeError('Cannot start a running server.') - if asyncio.get_event_loop() is not self._loop: + if _loop.get_or_create_event_loop() is not self._loop: raise RuntimeError('Can only start server in same thread that created it.') logger.info('Starting Flexx event loop.') # Make use of the semi-standard defined by IPython to determine diff --git a/flexx/app/_session.py b/flexx/app/_session.py index 8bd56b3f..bc789e5a 100644 --- a/flexx/app/_session.py +++ b/flexx/app/_session.py @@ -277,7 +277,8 @@ def set_cookie(self, name, value, expires_days=30, version=None, # Clear cookie? if value is None: value = "" - expires = datetime.datetime.utcnow() - datetime.timedelta(days=365) + expires = (datetime.datetime.now(datetime.timezone.utc) - + datetime.timedelta(days=365)) else: secret = config.cookie_secret value = create_signed_value(secret, name, value, version=version, @@ -296,8 +297,8 @@ def set_cookie(self, name, value, expires_days=30, version=None, if domain: morsel["domain"] = domain if expires_days is not None and not expires: - expires = datetime.datetime.utcnow() + datetime.timedelta( - days=expires_days) + expires = (datetime.datetime.now(datetime.timezone.utc) + + datetime.timedelta(days=expires_days)) if expires: morsel["expires"] = format_timestamp(expires) if path: diff --git a/flexx/event/_loop.py b/flexx/event/_loop.py index 543c0451..e2b38c55 100644 --- a/flexx/event/_loop.py +++ b/flexx/event/_loop.py @@ -15,6 +15,24 @@ def this_is_js(): return False +def get_or_create_event_loop(): + """ Get the current asyncio event loop, creating a new one if needed. + + In Python 3.10+ ``asyncio.get_event_loop()`` no longer creates a loop + when none exists, and from Python 3.12 it raises an error in that case. + This helper preserves the historical behavior. + """ + try: + return asyncio.get_running_loop() + except RuntimeError: + try: + return asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + return loop + + class Loop: """ The singleton Flexx event loop at ``flexx.event.loop``. This holds the queue of pending calls, actions, and reactions. These are queued @@ -386,7 +404,7 @@ def integrate(self, loop=None, reset=True): (though this is currently not tested). """ if loop is None: - loop = asyncio.get_event_loop() + loop = get_or_create_event_loop() with self._lock: self._thread_id = threading.get_ident() self._local._active_components = [] diff --git a/flexx/event/both_tester.py b/flexx/event/both_tester.py index d94b71b7..5fed0922 100644 --- a/flexx/event/both_tester.py +++ b/flexx/event/both_tester.py @@ -199,7 +199,8 @@ def run_in_both(*classes, js=True, py=True, extra_nodejs_args=None): """ def wrapper(func): - reference = '\n'.join(line[4:] for line in func.__doc__.splitlines()) + reference = '\n'.join(line[4:] if line.startswith(' ') else line + for line in func.__doc__.splitlines()) parts = reference.split('-'*10) pyref = parts[0].strip(' \n') jsref = parts[-1].strip(' \n-') diff --git a/setup.py b/setup.py index 3df2213d..7445142f 100644 --- a/setup.py +++ b/setup.py @@ -8,12 +8,7 @@ import sys import shutil -try: - import setuptools # noqa, analysis:ignore -except ImportError: - pass # setuptools allows for "develop", but it's not essential - -from distutils.core import setup +from setuptools import setup ## Function we need @@ -115,7 +110,7 @@ def get_all_resources(): long_description=doc, platforms='any', provides=[name], - python_requires='>=3.5', + python_requires='>=3.8', install_requires=['tornado', 'pscript>=0.7.7', 'webruntime>=0.5.6', 'dialite>=0.5.2'], packages=package_tree('flexx') + package_tree('flexxamples'), package_dir={'flexx': 'flexx', 'flexxamples': 'flexxamples'}, @@ -134,10 +129,12 @@ def get_all_resources(): 'Operating System :: POSIX', 'Programming Language :: JavaScript', 'Programming Language :: Python', - '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 :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', ], )