require python>=3.5.2

This commit is contained in:
Unrud 2018-08-16 08:00:02 +02:00
parent 59d10ef9f7
commit a5fa35e785
4 changed files with 23 additions and 59 deletions

View File

@ -3,10 +3,6 @@ sudo: false
matrix: matrix:
include: include:
- os: linux
python: 3.3
- os: linux
python: 3.4
- os: linux - os: linux
python: 3.5 python: 3.5
- os: linux - os: linux

View File

@ -73,10 +73,6 @@ class HTTPServer(wsgiref.simple_server.WSGIServer):
self.server_close() self.server_close()
raise 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): def get_request(self):
# Set timeout for client # Set timeout for client
_socket, address = super().get_request() _socket, address = super().get_request()
@ -249,12 +245,8 @@ def serve(configuration):
if configuration.getboolean("server", "ssl") else "") if configuration.getboolean("server", "ssl") else "")
# Create a socket pair to notify the select syscall of program shutdown # Create a socket pair to notify the select syscall of program shutdown
# This is not available in python < 3.5 on Windows shutdown_program_socket_in, shutdown_program_socket_out = (
if hasattr(socket, "socketpair"): socket.socketpair())
shutdown_program_socket_in, shutdown_program_socket_out = (
socket.socketpair())
else:
shutdown_program_socket_in, shutdown_program_socket_out = None, None
# SIGTERM and SIGINT (aka KeyboardInterrupt) should just mark this for # SIGTERM and SIGINT (aka KeyboardInterrupt) should just mark this for
# shutdown # shutdown
@ -265,18 +257,16 @@ def serve(configuration):
return return
logger.info("Stopping Radicale") logger.info("Stopping Radicale")
shutdown_program = True shutdown_program = True
if shutdown_program_socket_in: shutdown_program_socket_in.sendall(b" ")
shutdown_program_socket_in.sendall(b"goodbye")
signal.signal(signal.SIGTERM, shutdown) signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown) signal.signal(signal.SIGINT, shutdown)
# Main loop: wait for requests on any of the servers or program shutdown # Main loop: wait for requests on any of the servers or program shutdown
sockets = list(servers.keys()) sockets = list(servers.keys())
if shutdown_program_socket_out: # Use socket pair to get notified of program shutdown
# Use socket pair to get notified of program shutdown sockets.append(shutdown_program_socket_out)
sockets.append(shutdown_program_socket_out)
select_timeout = None 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.) # Fallback to busy waiting. (select.select blocks SIGINT on Windows.)
select_timeout = 1.0 select_timeout = 1.0
logger.info("Radicale server ready") logger.info("Radicale server ready")

View File

@ -34,7 +34,6 @@ import pickle
import posixpath import posixpath
import shlex import shlex
import subprocess import subprocess
import sys
import threading import threading
import time import time
from contextlib import contextmanager from contextlib import contextmanager
@ -47,12 +46,9 @@ from tempfile import NamedTemporaryFile, TemporaryDirectory
import vobject import vobject
from radicale import xmlutils
from radicale.log import logger 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": if os.name == "nt":
import ctypes import ctypes
import ctypes.wintypes import ctypes.wintypes
@ -97,10 +93,6 @@ INTERNAL_TYPES = ("multifilesystem",)
def load(configuration): def load(configuration):
"""Load the storage manager chosen in 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") storage_type = configuration.get("storage", "type")
if storage_type == "multifilesystem": if storage_type == "multifilesystem":
collection_class = Collection collection_class = Collection
@ -241,27 +233,6 @@ def find_available_name(exists_fn, suffix=""):
raise RuntimeError("No unique random sequence found") 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): def get_etag(text):
"""Etag from collection or item. """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 # Check for conflicting files (e.g. case-insensitive file systems
# or short names on Windows file systems) # or short names on Windows file systems)
if (os.path.lexists(safe_path) and 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) raise CollidingPathError(part)
return safe_path return safe_path
@ -899,7 +871,10 @@ class Collection(BaseCollection):
with child_context_manager(sane_path, href): with child_context_manager(sane_path, href):
yield collection.get(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 is_safe_filesystem_path_component(href):
if not href.startswith(".Radicale"): if not href.startswith(".Radicale"):
logger.debug("Skipping collection %r in %r", logger.debug("Skipping collection %r in %r",
@ -1174,7 +1149,8 @@ class Collection(BaseCollection):
history_folder = os.path.join(self._filesystem_path, history_folder = os.path.join(self._filesystem_path,
".Radicale.cache", "history") ".Radicale.cache", "history")
try: 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): if not is_safe_filesystem_path_component(href):
continue continue
if os.path.isfile(os.path.join(self._filesystem_path, href)): if os.path.isfile(os.path.join(self._filesystem_path, href)):
@ -1285,7 +1261,10 @@ class Collection(BaseCollection):
return token, changes return token, changes
def list(self): 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 is_safe_filesystem_path_component(href):
if not href.startswith(".Radicale"): if not href.startswith(".Radicale"):
logger.debug("Skipping item %r in %r", href, self.path) 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", cache_folder = os.path.join(self._filesystem_path, ".Radicale.cache",
"item") "item")
self._clean_cache(cache_folder, ( self._clean_cache(cache_folder, (
href for href in scandir(cache_folder) if not e.name for e in os.scandir(cache_folder) if not
os.path.isfile(os.path.join(self._filesystem_path, href)))) os.path.isfile(os.path.join(self._filesystem_path, e.name))))
def _get_with_metadata(self, href, verify_href=True): def _get_with_metadata(self, href, verify_href=True):
"""Like ``get`` but additonally returns the following metadata: """Like ``get`` but additonally returns the following metadata:

View File

@ -74,7 +74,7 @@ setup(
"md5": "passlib", "md5": "passlib",
"bcrypt": "passlib[bcrypt]"}, "bcrypt": "passlib[bcrypt]"},
keywords=["calendar", "addressbook", "CalDAV", "CardDAV"], keywords=["calendar", "addressbook", "CalDAV", "CardDAV"],
python_requires=">=3.3", python_requires=">=3.5.2",
classifiers=[ classifiers=[
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
"Environment :: Console", "Environment :: Console",
@ -84,8 +84,7 @@ setup(
"License :: OSI Approved :: GNU General Public License (GPL)", "License :: OSI Approved :: GNU General Public License (GPL)",
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Topic :: Office/Business :: Groupware"]) "Topic :: Office/Business :: Groupware"])