From a5fa35e7858139010fe0b9930ad709c46e0fdfd1 Mon Sep 17 00:00:00 2001 From: Unrud Date: Thu, 16 Aug 2018 08:00:02 +0200 Subject: [PATCH] require python>=3.5.2 --- .travis.yml | 4 ---- radicale/server.py | 22 ++++++------------- radicale/storage.py | 51 +++++++++++++-------------------------------- setup.py | 5 ++--- 4 files changed, 23 insertions(+), 59 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1b5e520..6ad034e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,10 +3,6 @@ sudo: false matrix: include: - - os: linux - python: 3.3 - - os: linux - python: 3.4 - os: linux python: 3.5 - os: linux diff --git a/radicale/server.py b/radicale/server.py index 15b1703..694d838 100644 --- a/radicale/server.py +++ b/radicale/server.py @@ -73,10 +73,6 @@ class HTTPServer(wsgiref.simple_server.WSGIServer): self.server_close() raise - if self.client_timeout and sys.version_info < (3, 5, 2): - logger.warning("Using server.timeout with Python < 3.5.2 " - "can cause network connection failures") - def get_request(self): # Set timeout for client _socket, address = super().get_request() @@ -249,12 +245,8 @@ def serve(configuration): if configuration.getboolean("server", "ssl") else "") # Create a socket pair to notify the select syscall of program shutdown - # This is not available in python < 3.5 on Windows - if hasattr(socket, "socketpair"): - shutdown_program_socket_in, shutdown_program_socket_out = ( - socket.socketpair()) - else: - shutdown_program_socket_in, shutdown_program_socket_out = None, None + shutdown_program_socket_in, shutdown_program_socket_out = ( + socket.socketpair()) # SIGTERM and SIGINT (aka KeyboardInterrupt) should just mark this for # shutdown @@ -265,18 +257,16 @@ def serve(configuration): return logger.info("Stopping Radicale") shutdown_program = True - if shutdown_program_socket_in: - shutdown_program_socket_in.sendall(b"goodbye") + shutdown_program_socket_in.sendall(b" ") signal.signal(signal.SIGTERM, shutdown) signal.signal(signal.SIGINT, shutdown) # Main loop: wait for requests on any of the servers or program shutdown sockets = list(servers.keys()) - if shutdown_program_socket_out: - # Use socket pair to get notified of program shutdown - sockets.append(shutdown_program_socket_out) + # Use socket pair to get notified of program shutdown + sockets.append(shutdown_program_socket_out) select_timeout = None - if not shutdown_program_socket_out or os.name == "nt": + if os.name == "nt": # Fallback to busy waiting. (select.select blocks SIGINT on Windows.) select_timeout = 1.0 logger.info("Radicale server ready") diff --git a/radicale/storage.py b/radicale/storage.py index 9701b38..ffd89b8 100644 --- a/radicale/storage.py +++ b/radicale/storage.py @@ -34,7 +34,6 @@ import pickle import posixpath import shlex import subprocess -import sys import threading import time from contextlib import contextmanager @@ -47,12 +46,9 @@ from tempfile import NamedTemporaryFile, TemporaryDirectory import vobject +from radicale import xmlutils from radicale.log import logger -if sys.version_info >= (3, 5): - # HACK: Avoid import cycle for Python < 3.5 - from radicale import xmlutils - if os.name == "nt": import ctypes import ctypes.wintypes @@ -97,10 +93,6 @@ INTERNAL_TYPES = ("multifilesystem",) def load(configuration): """Load the storage manager chosen in configuration.""" - if sys.version_info < (3, 5): - # HACK: Avoid import cycle for Python < 3.5 - global xmlutils - from radicale import xmlutils storage_type = configuration.get("storage", "type") if storage_type == "multifilesystem": collection_class = Collection @@ -241,27 +233,6 @@ def find_available_name(exists_fn, suffix=""): raise RuntimeError("No unique random sequence found") -def scandir(path, only_dirs=False, only_files=False): - """Iterator for directory elements. (For compatibility with Python < 3.5) - - ``only_dirs`` only return directories - - ``only_files`` only return files - - """ - if sys.version_info >= (3, 5): - for entry in os.scandir(path): - if ((not only_files or entry.is_file()) and - (not only_dirs or entry.is_dir())): - yield entry.name - else: - for name in os.listdir(path): - p = os.path.join(path, name) - if ((not only_files or os.path.isfile(p)) and - (not only_dirs or os.path.isdir(p))): - yield name - - def get_etag(text): """Etag from collection or item. @@ -354,7 +325,8 @@ def path_to_filesystem(root, *paths): # Check for conflicting files (e.g. case-insensitive file systems # or short names on Windows file systems) if (os.path.lexists(safe_path) and - part not in scandir(safe_path_parent)): + part not in (e.name for e in + os.scandir(safe_path_parent))): raise CollidingPathError(part) return safe_path @@ -899,7 +871,10 @@ class Collection(BaseCollection): with child_context_manager(sane_path, href): yield collection.get(href) - for href in scandir(filesystem_path, only_dirs=True): + for entry in os.scandir(filesystem_path): + if not entry.is_dir(): + continue + href = entry.name if not is_safe_filesystem_path_component(href): if not href.startswith(".Radicale"): logger.debug("Skipping collection %r in %r", @@ -1174,7 +1149,8 @@ class Collection(BaseCollection): history_folder = os.path.join(self._filesystem_path, ".Radicale.cache", "history") try: - for href in scandir(history_folder): + for entry in os.scandir(history_folder): + href = entry.name if not is_safe_filesystem_path_component(href): continue if os.path.isfile(os.path.join(self._filesystem_path, href)): @@ -1285,7 +1261,10 @@ class Collection(BaseCollection): return token, changes def list(self): - for href in scandir(self._filesystem_path, only_files=True): + for entry in os.scandir(self._filesystem_path): + if not entry.is_file(): + continue + href = entry.name if not is_safe_filesystem_path_component(href): if not href.startswith(".Radicale"): logger.debug("Skipping item %r in %r", href, self.path) @@ -1357,8 +1336,8 @@ class Collection(BaseCollection): cache_folder = os.path.join(self._filesystem_path, ".Radicale.cache", "item") self._clean_cache(cache_folder, ( - href for href in scandir(cache_folder) if not - os.path.isfile(os.path.join(self._filesystem_path, href)))) + e.name for e in os.scandir(cache_folder) if not + os.path.isfile(os.path.join(self._filesystem_path, e.name)))) def _get_with_metadata(self, href, verify_href=True): """Like ``get`` but additonally returns the following metadata: diff --git a/setup.py b/setup.py index 323bac4..3a25b0b 100644 --- a/setup.py +++ b/setup.py @@ -74,7 +74,7 @@ setup( "md5": "passlib", "bcrypt": "passlib[bcrypt]"}, keywords=["calendar", "addressbook", "CalDAV", "CardDAV"], - python_requires=">=3.3", + python_requires=">=3.5.2", classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", @@ -84,8 +84,7 @@ setup( "License :: OSI Approved :: GNU General Public License (GPL)", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", "Topic :: Office/Business :: Groupware"])