diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml
index 75c1e08f..0643cb0a 100644
--- a/.github/workflows/issues-stale.yml
+++ b/.github/workflows/issues-stale.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Stale
- uses: actions/stale@v6
+ uses: actions/stale@v7
with:
stale-issue-message: >
This issue is stale because it has been open for 30 days with no activity.
@@ -30,7 +30,7 @@ jobs:
days-before-close: 5
- name: Invalid Template
- uses: actions/stale@v6
+ uses: actions/stale@v7
with:
stale-issue-message: >
Invalid issues template.
diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml
index 728cd8c9..34ceb357 100644
--- a/.github/workflows/issues.yml
+++ b/.github/workflows/issues.yml
@@ -10,6 +10,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Label Issues
- uses: dessant/label-actions@v2
+ uses: dessant/label-actions@v3
with:
github-token: ${{ github.token }}
diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml
index 3f6af320..125cae51 100644
--- a/.github/workflows/publish-docker.yml
+++ b/.github/workflows/publish-docker.yml
@@ -13,7 +13,7 @@ jobs:
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
steps:
- name: Checkout Code
- uses: actions/checkout@v3.1.0
+ uses: actions/checkout@v3.2.0
- name: Prepare
id: prepare
@@ -47,7 +47,7 @@ jobs:
version: latest
- name: Cache Docker Layers
- uses: actions/cache@v3.0.11
+ uses: actions/cache@v3.2.0
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml
index 1c5f075b..7002834b 100644
--- a/.github/workflows/publish-installers.yml
+++ b/.github/workflows/publish-installers.yml
@@ -24,7 +24,7 @@ jobs:
steps:
- name: Checkout Code
- uses: actions/checkout@v3.1.0
+ uses: actions/checkout@v3.2.0
- name: Set Release Version
id: get_version
@@ -52,7 +52,7 @@ jobs:
echo $GITHUB_SHA > version.txt
- name: Set Up Python
- uses: actions/setup-python@v4.3.0
+ uses: actions/setup-python@v4.3.1
with:
python-version: '3.9'
cache: pip
@@ -103,7 +103,7 @@ jobs:
uses: technote-space/workflow-conclusion-action@v3.0
- name: Checkout Code
- uses: actions/checkout@v3.1.0
+ uses: actions/checkout@v3.2.0
- name: Set Release Version
id: get_version
diff --git a/.github/workflows/publish-snap.yml b/.github/workflows/publish-snap.yml
index 5cd43a1b..7ad8fe95 100644
--- a/.github/workflows/publish-snap.yml
+++ b/.github/workflows/publish-snap.yml
@@ -20,7 +20,7 @@ jobs:
- armhf
steps:
- name: Checkout Code
- uses: actions/checkout@v3.1.0
+ uses: actions/checkout@v3.2.0
- name: Prepare
id: prepare
@@ -38,7 +38,7 @@ jobs:
uses: docker/setup-qemu-action@v2
- name: Build Snap Package
- uses: diddlesnaps/snapcraft-multiarch-action@v1.5.0
+ uses: diddlesnaps/snapcraft-multiarch-action@v1
id: build
with:
architecture: ${{ matrix.architecture }}
diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml
index d15b9140..d7c8e45d 100644
--- a/.github/workflows/pull-requests.yml
+++ b/.github/workflows/pull-requests.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
- uses: actions/checkout@v3.1.0
+ uses: actions/checkout@v3.2.0
- name: Comment on Pull Request
uses: mshick/add-pr-comment@v2
diff --git a/data/interfaces/newsletters/recently_added.html b/data/interfaces/newsletters/recently_added.html
index f4f6e8e8..e74d8463 100644
--- a/data/interfaces/newsletters/recently_added.html
+++ b/data/interfaces/newsletters/recently_added.html
@@ -24,6 +24,7 @@
+
Tautulli Newsletter - ${subject}
diff --git a/data/interfaces/newsletters/recently_added.internal.html b/data/interfaces/newsletters/recently_added.internal.html
index 8253e281..edf7edc6 100644
--- a/data/interfaces/newsletters/recently_added.internal.html
+++ b/data/interfaces/newsletters/recently_added.internal.html
@@ -24,6 +24,7 @@
+
Tautulli Newsletter - ${subject}
diff --git a/lib/cheroot/__init__.py b/lib/cheroot/__init__.py
index 30d38cab..aac9cd98 100644
--- a/lib/cheroot/__init__.py
+++ b/lib/cheroot/__init__.py
@@ -1,15 +1,12 @@
"""High-performance, pure-Python HTTP server used by CherryPy."""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
try:
- import pkg_resources
+ from importlib import metadata
except ImportError:
- pass
+ import importlib_metadata as metadata # noqa: WPS440
try:
- __version__ = pkg_resources.get_distribution('cheroot').version
+ __version__ = metadata.version('cheroot')
except Exception:
__version__ = 'unknown'
diff --git a/lib/cheroot/_compat.py b/lib/cheroot/_compat.py
index 10dcdefa..20c993de 100644
--- a/lib/cheroot/_compat.py
+++ b/lib/cheroot/_compat.py
@@ -1,19 +1,9 @@
# pylint: disable=unused-import
"""Compatibility code for using Cheroot with various versions of Python."""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
import os
import platform
-import re
-import six
-
-try:
- import selectors # lgtm [py/unused-import]
-except ImportError:
- import selectors2 as selectors # noqa: F401 # lgtm [py/unused-import]
try:
import ssl
@@ -22,20 +12,6 @@ try:
except ImportError:
IS_ABOVE_OPENSSL10 = None
-# contextlib.suppress was added in Python 3.4
-try:
- from contextlib import suppress
-except ImportError:
- from contextlib import contextmanager
-
- @contextmanager
- def suppress(*exceptions):
- """Return a context manager that suppresses the `exceptions`."""
- try:
- yield
- except exceptions:
- pass
-
IS_CI = bool(os.getenv('CI'))
IS_GITHUB_ACTIONS_WORKFLOW = bool(os.getenv('GITHUB_WORKFLOW'))
@@ -53,53 +29,23 @@ PLATFORM_ARCH = platform.machine()
IS_PPC = PLATFORM_ARCH.startswith('ppc')
-if not six.PY2:
- def ntob(n, encoding='ISO-8859-1'):
- """Return the native string as bytes in the given encoding."""
- assert_native(n)
- # In Python 3, the native string type is unicode
- return n.encode(encoding)
+def ntob(n, encoding='ISO-8859-1'):
+ """Return the native string as bytes in the given encoding."""
+ assert_native(n)
+ # In Python 3, the native string type is unicode
+ return n.encode(encoding)
- def ntou(n, encoding='ISO-8859-1'):
- """Return the native string as Unicode with the given encoding."""
- assert_native(n)
- # In Python 3, the native string type is unicode
- return n
- def bton(b, encoding='ISO-8859-1'):
- """Return the byte string as native string in the given encoding."""
- return b.decode(encoding)
-else:
- # Python 2
- def ntob(n, encoding='ISO-8859-1'):
- """Return the native string as bytes in the given encoding."""
- assert_native(n)
- # In Python 2, the native string type is bytes. Assume it's already
- # in the given encoding, which for ISO-8859-1 is almost always what
- # was intended.
- return n
+def ntou(n, encoding='ISO-8859-1'):
+ """Return the native string as Unicode with the given encoding."""
+ assert_native(n)
+ # In Python 3, the native string type is unicode
+ return n
- def ntou(n, encoding='ISO-8859-1'):
- """Return the native string as Unicode with the given encoding."""
- assert_native(n)
- # In Python 2, the native string type is bytes.
- # First, check for the special encoding 'escape'. The test suite uses
- # this to signal that it wants to pass a string with embedded \uXXXX
- # escapes, but without having to prefix it with u'' for Python 2,
- # but no prefix for Python 3.
- if encoding == 'escape':
- return re.sub(
- r'\\u([0-9a-zA-Z]{4})',
- lambda m: six.unichr(int(m.group(1), 16)),
- n.decode('ISO-8859-1'),
- )
- # Assume it's already in the given encoding, which for ISO-8859-1
- # is almost always what was intended.
- return n.decode(encoding)
- def bton(b, encoding='ISO-8859-1'):
- """Return the byte string as native string in the given encoding."""
- return b
+def bton(b, encoding='ISO-8859-1'):
+ """Return the byte string as native string in the given encoding."""
+ return b.decode(encoding)
def assert_native(n):
@@ -113,17 +59,6 @@ def assert_native(n):
raise TypeError('n must be a native str (got %s)' % type(n).__name__)
-if not six.PY2:
- """Python 3 has :py:class:`memoryview` builtin."""
- # Python 2.7 has it backported, but socket.write() does
- # str(memoryview(b'0' * 100)) ->
- # instead of accessing it correctly.
- memoryview = memoryview
-else:
- """Link :py:class:`memoryview` to buffer under Python 2."""
- memoryview = buffer # noqa: F821
-
-
def extract_bytes(mv):
r"""Retrieve bytes out of the given input buffer.
@@ -138,7 +73,7 @@ def extract_bytes(mv):
or :py:class:`bytes`
"""
if isinstance(mv, memoryview):
- return bytes(mv) if six.PY2 else mv.tobytes()
+ return mv.tobytes()
if isinstance(mv, bytes):
return mv
diff --git a/lib/cheroot/_compat.pyi b/lib/cheroot/_compat.pyi
new file mode 100644
index 00000000..023bad8c
--- /dev/null
+++ b/lib/cheroot/_compat.pyi
@@ -0,0 +1,21 @@
+from typing import Any, ContextManager, Optional, Type, Union
+
+def suppress(*exceptions: Type[BaseException]) -> ContextManager[None]: ...
+
+IS_ABOVE_OPENSSL10: Optional[bool]
+IS_CI: bool
+IS_GITHUB_ACTIONS_WORKFLOW: bool
+IS_PYPY: bool
+SYS_PLATFORM: str
+IS_WINDOWS: bool
+IS_LINUX: bool
+IS_MACOS: bool
+PLATFORM_ARCH: str
+IS_PPC: bool
+
+def ntob(n: str, encoding: str = ...) -> bytes: ...
+def ntou(n: str, encoding: str = ...) -> str: ...
+def bton(b: bytes, encoding: str = ...) -> str: ...
+def assert_native(n: str) -> None: ...
+
+def extract_bytes(mv: Union[memoryview, bytes]) -> bytes: ...
diff --git a/lib/cheroot/cli.py b/lib/cheroot/cli.py
index 4607e226..cd168e91 100644
--- a/lib/cheroot/cli.py
+++ b/lib/cheroot/cli.py
@@ -28,18 +28,14 @@ Basic usage:
"""
import argparse
-from importlib import import_module
import os
import sys
-
-import six
+import urllib.parse # noqa: WPS301
+from importlib import import_module
+from contextlib import suppress
from . import server
from . import wsgi
-from ._compat import suppress
-
-
-__metaclass__ = type
class BindLocation:
@@ -143,7 +139,7 @@ def parse_wsgi_bind_location(bind_addr_string):
return AbstractSocket(bind_addr_string[1:])
# try and match for an IP/hostname and port
- match = six.moves.urllib.parse.urlparse(
+ match = urllib.parse.urlparse(
'//{addr}'.format(addr=bind_addr_string),
)
try:
diff --git a/lib/cheroot/connections.py b/lib/cheroot/connections.py
index 181e3731..9b6366e5 100644
--- a/lib/cheroot/connections.py
+++ b/lib/cheroot/connections.py
@@ -1,22 +1,17 @@
"""Utilities to manage open connections."""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
import io
import os
import socket
import threading
import time
+import selectors
+from contextlib import suppress
from . import errors
-from ._compat import selectors
-from ._compat import suppress
from ._compat import IS_WINDOWS
from .makefile import MakeFile
-import six
-
try:
import fcntl
except ImportError:
@@ -310,8 +305,7 @@ class ConnectionManager:
msg,
]
- sock_to_make = s if not six.PY2 else s._sock
- wfile = mf(sock_to_make, 'wb', io.DEFAULT_BUFFER_SIZE)
+ wfile = mf(s, 'wb', io.DEFAULT_BUFFER_SIZE)
try:
wfile.write(''.join(buf).encode('ISO-8859-1'))
except socket.error as ex:
@@ -327,10 +321,7 @@ class ConnectionManager:
conn = self.server.ConnectionClass(self.server, s, mf)
- if not isinstance(
- self.server.bind_addr,
- (six.text_type, six.binary_type),
- ):
+ if not isinstance(self.server.bind_addr, (str, bytes)):
# optional values
# Until we do DNS lookups, omit REMOTE_HOST
if addr is None: # sometimes this can happen
diff --git a/lib/cheroot/errors.py b/lib/cheroot/errors.py
index e00629f8..046263ad 100644
--- a/lib/cheroot/errors.py
+++ b/lib/cheroot/errors.py
@@ -1,17 +1,14 @@
# -*- coding: utf-8 -*-
"""Collection of exceptions raised and/or processed by Cheroot."""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
import errno
import sys
class MaxSizeExceeded(Exception):
- """Exception raised when a client sends more data then acceptable within limit.
+ """Exception raised when a client sends more data then allowed under limit.
- Depends on ``request.body.maxbytes`` config option if used within CherryPy
+ Depends on ``request.body.maxbytes`` config option if used within CherryPy.
"""
diff --git a/lib/cheroot/errors.pyi b/lib/cheroot/errors.pyi
index e78a7585..18669568 100644
--- a/lib/cheroot/errors.pyi
+++ b/lib/cheroot/errors.pyi
@@ -1,4 +1,4 @@
-from typing import Any, List, Set, Tuple
+from typing import List, Set, Tuple, Type
class MaxSizeExceeded(Exception): ...
class NoSSLError(Exception): ...
@@ -10,4 +10,4 @@ socket_error_eintr: List[int]
socket_errors_to_ignore: List[int]
socket_errors_nonblocking: List[int]
acceptable_sock_shutdown_error_codes: Set[int]
-acceptable_sock_shutdown_exceptions: Tuple[Exception]
+acceptable_sock_shutdown_exceptions: Tuple[Type[Exception], ...]
diff --git a/lib/cheroot/makefile.py b/lib/cheroot/makefile.py
index 1383c658..77878c13 100644
--- a/lib/cheroot/makefile.py
+++ b/lib/cheroot/makefile.py
@@ -1,21 +1,9 @@
"""Socket file object."""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
import socket
-try:
- # prefer slower Python-based io module
- import _pyio as io
-except ImportError:
- # Python 2.6
- import io
-
-import six
-
-from . import errors
-from ._compat import extract_bytes, memoryview
+# prefer slower Python-based io module
+import _pyio as io
# Write only 16K at a time to sockets
@@ -48,400 +36,41 @@ class BufferedWriter(io.BufferedWriter):
del self._write_buf[:n]
-class MakeFile_PY2(getattr(socket, '_fileobject', object)):
- """Faux file object attached to a socket object."""
+class StreamReader(io.BufferedReader):
+ """Socket stream reader."""
- def __init__(self, *args, **kwargs):
- """Initialize faux file object."""
+ def __init__(self, sock, mode='r', bufsize=io.DEFAULT_BUFFER_SIZE):
+ """Initialize socket stream reader."""
+ super().__init__(socket.SocketIO(sock, mode), bufsize)
self.bytes_read = 0
+
+ def read(self, *args, **kwargs):
+ """Capture bytes read."""
+ val = super().read(*args, **kwargs)
+ self.bytes_read += len(val)
+ return val
+
+ def has_data(self):
+ """Return true if there is buffered data to read."""
+ return len(self._read_buf) > self._read_pos
+
+
+class StreamWriter(BufferedWriter):
+ """Socket stream writer."""
+
+ def __init__(self, sock, mode='w', bufsize=io.DEFAULT_BUFFER_SIZE):
+ """Initialize socket stream writer."""
+ super().__init__(socket.SocketIO(sock, mode), bufsize)
self.bytes_written = 0
- socket._fileobject.__init__(self, *args, **kwargs)
- self._refcount = 0
- def _reuse(self):
- self._refcount += 1
-
- def _drop(self):
- if self._refcount < 0:
- self.close()
- else:
- self._refcount -= 1
-
- def write(self, data):
- """Send entire data contents for non-blocking sockets."""
- bytes_sent = 0
- data_mv = memoryview(data)
- payload_size = len(data_mv)
- while bytes_sent < payload_size:
- try:
- bytes_sent += self.send(
- data_mv[bytes_sent:bytes_sent + SOCK_WRITE_BLOCKSIZE],
- )
- except socket.error as e:
- if e.args[0] not in errors.socket_errors_nonblocking:
- raise
-
- def send(self, data):
- """Send some part of message to the socket."""
- bytes_sent = self._sock.send(extract_bytes(data))
- self.bytes_written += bytes_sent
- return bytes_sent
-
- def flush(self):
- """Write all data from buffer to socket and reset write buffer."""
- if self._wbuf:
- buffer = ''.join(self._wbuf)
- self._wbuf = []
- self.write(buffer)
-
- def recv(self, size):
- """Receive message of a size from the socket."""
- while True:
- try:
- data = self._sock.recv(size)
- self.bytes_read += len(data)
- return data
- except socket.error as e:
- what = (
- e.args[0] not in errors.socket_errors_nonblocking
- and e.args[0] not in errors.socket_error_eintr
- )
- if what:
- raise
-
- class FauxSocket:
- """Faux socket with the minimal interface required by pypy."""
-
- def _reuse(self):
- pass
-
- _fileobject_uses_str_type = six.PY2 and isinstance(
- socket._fileobject(FauxSocket())._rbuf, six.string_types,
- )
-
- # FauxSocket is no longer needed
- del FauxSocket
-
- if not _fileobject_uses_str_type: # noqa: C901 # FIXME
- def read(self, size=-1):
- """Read data from the socket to buffer."""
- # Use max, disallow tiny reads in a loop as they are very
- # inefficient.
- # We never leave read() with any leftover data from a new recv()
- # call in our internal buffer.
- rbufsize = max(self._rbufsize, self.default_bufsize)
- # Our use of StringIO rather than lists of string objects returned
- # by recv() minimizes memory usage and fragmentation that occurs
- # when rbufsize is large compared to the typical return value of
- # recv().
- buf = self._rbuf
- buf.seek(0, 2) # seek end
- if size < 0:
- # Read until EOF
- # reset _rbuf. we consume it via buf.
- self._rbuf = io.BytesIO()
- while True:
- data = self.recv(rbufsize)
- if not data:
- break
- buf.write(data)
- return buf.getvalue()
- else:
- # Read until size bytes or EOF seen, whichever comes first
- buf_len = buf.tell()
- if buf_len >= size:
- # Already have size bytes in our buffer? Extract and
- # return.
- buf.seek(0)
- rv = buf.read(size)
- self._rbuf = io.BytesIO()
- self._rbuf.write(buf.read())
- return rv
-
- # reset _rbuf. we consume it via buf.
- self._rbuf = io.BytesIO()
- while True:
- left = size - buf_len
- # recv() will malloc the amount of memory given as its
- # parameter even though it often returns much less data
- # than that. The returned data string is short lived
- # as we copy it into a StringIO and free it. This avoids
- # fragmentation issues on many platforms.
- data = self.recv(left)
- if not data:
- break
- n = len(data)
- if n == size and not buf_len:
- # Shortcut. Avoid buffer data copies when:
- # - We have no data in our buffer.
- # AND
- # - Our call to recv returned exactly the
- # number of bytes we were asked to read.
- return data
- if n == left:
- buf.write(data)
- del data # explicit free
- break
- assert n <= left, 'recv(%d) returned %d bytes' % (left, n)
- buf.write(data)
- buf_len += n
- del data # explicit free
- # assert buf_len == buf.tell()
- return buf.getvalue()
-
- def readline(self, size=-1):
- """Read line from the socket to buffer."""
- buf = self._rbuf
- buf.seek(0, 2) # seek end
- if buf.tell() > 0:
- # check if we already have it in our buffer
- buf.seek(0)
- bline = buf.readline(size)
- if bline.endswith('\n') or len(bline) == size:
- self._rbuf = io.BytesIO()
- self._rbuf.write(buf.read())
- return bline
- del bline
- if size < 0:
- # Read until \n or EOF, whichever comes first
- if self._rbufsize <= 1:
- # Speed up unbuffered case
- buf.seek(0)
- buffers = [buf.read()]
- # reset _rbuf. we consume it via buf.
- self._rbuf = io.BytesIO()
- data = None
- recv = self.recv
- while data != '\n':
- data = recv(1)
- if not data:
- break
- buffers.append(data)
- return ''.join(buffers)
-
- buf.seek(0, 2) # seek end
- # reset _rbuf. we consume it via buf.
- self._rbuf = io.BytesIO()
- while True:
- data = self.recv(self._rbufsize)
- if not data:
- break
- nl = data.find('\n')
- if nl >= 0:
- nl += 1
- buf.write(data[:nl])
- self._rbuf.write(data[nl:])
- del data
- break
- buf.write(data)
- return buf.getvalue()
-
- else:
- # Read until size bytes or \n or EOF seen, whichever comes
- # first
- buf.seek(0, 2) # seek end
- buf_len = buf.tell()
- if buf_len >= size:
- buf.seek(0)
- rv = buf.read(size)
- self._rbuf = io.BytesIO()
- self._rbuf.write(buf.read())
- return rv
- # reset _rbuf. we consume it via buf.
- self._rbuf = io.BytesIO()
- while True:
- data = self.recv(self._rbufsize)
- if not data:
- break
- left = size - buf_len
- # did we just receive a newline?
- nl = data.find('\n', 0, left)
- if nl >= 0:
- nl += 1
- # save the excess data to _rbuf
- self._rbuf.write(data[nl:])
- if buf_len:
- buf.write(data[:nl])
- break
- else:
- # Shortcut. Avoid data copy through buf when
- # returning a substring of our first recv().
- return data[:nl]
- n = len(data)
- if n == size and not buf_len:
- # Shortcut. Avoid data copy through buf when
- # returning exactly all of our first recv().
- return data
- if n >= left:
- buf.write(data[:left])
- self._rbuf.write(data[left:])
- break
- buf.write(data)
- buf_len += n
- # assert buf_len == buf.tell()
- return buf.getvalue()
-
- def has_data(self):
- """Return true if there is buffered data to read."""
- return bool(self._rbuf.getvalue())
-
- else:
- def read(self, size=-1):
- """Read data from the socket to buffer."""
- if size < 0:
- # Read until EOF
- buffers = [self._rbuf]
- self._rbuf = ''
- if self._rbufsize <= 1:
- recv_size = self.default_bufsize
- else:
- recv_size = self._rbufsize
-
- while True:
- data = self.recv(recv_size)
- if not data:
- break
- buffers.append(data)
- return ''.join(buffers)
- else:
- # Read until size bytes or EOF seen, whichever comes first
- data = self._rbuf
- buf_len = len(data)
- if buf_len >= size:
- self._rbuf = data[size:]
- return data[:size]
- buffers = []
- if data:
- buffers.append(data)
- self._rbuf = ''
- while True:
- left = size - buf_len
- recv_size = max(self._rbufsize, left)
- data = self.recv(recv_size)
- if not data:
- break
- buffers.append(data)
- n = len(data)
- if n >= left:
- self._rbuf = data[left:]
- buffers[-1] = data[:left]
- break
- buf_len += n
- return ''.join(buffers)
-
- def readline(self, size=-1):
- """Read line from the socket to buffer."""
- data = self._rbuf
- if size < 0:
- # Read until \n or EOF, whichever comes first
- if self._rbufsize <= 1:
- # Speed up unbuffered case
- assert data == ''
- buffers = []
- while data != '\n':
- data = self.recv(1)
- if not data:
- break
- buffers.append(data)
- return ''.join(buffers)
- nl = data.find('\n')
- if nl >= 0:
- nl += 1
- self._rbuf = data[nl:]
- return data[:nl]
- buffers = []
- if data:
- buffers.append(data)
- self._rbuf = ''
- while True:
- data = self.recv(self._rbufsize)
- if not data:
- break
- buffers.append(data)
- nl = data.find('\n')
- if nl >= 0:
- nl += 1
- self._rbuf = data[nl:]
- buffers[-1] = data[:nl]
- break
- return ''.join(buffers)
- else:
- # Read until size bytes or \n or EOF seen, whichever comes
- # first
- nl = data.find('\n', 0, size)
- if nl >= 0:
- nl += 1
- self._rbuf = data[nl:]
- return data[:nl]
- buf_len = len(data)
- if buf_len >= size:
- self._rbuf = data[size:]
- return data[:size]
- buffers = []
- if data:
- buffers.append(data)
- self._rbuf = ''
- while True:
- data = self.recv(self._rbufsize)
- if not data:
- break
- buffers.append(data)
- left = size - buf_len
- nl = data.find('\n', 0, left)
- if nl >= 0:
- nl += 1
- self._rbuf = data[nl:]
- buffers[-1] = data[:nl]
- break
- n = len(data)
- if n >= left:
- self._rbuf = data[left:]
- buffers[-1] = data[:left]
- break
- buf_len += n
- return ''.join(buffers)
-
- def has_data(self):
- """Return true if there is buffered data to read."""
- return bool(self._rbuf)
+ def write(self, val, *args, **kwargs):
+ """Capture bytes written."""
+ res = super().write(val, *args, **kwargs)
+ self.bytes_written += len(val)
+ return res
-if not six.PY2:
- class StreamReader(io.BufferedReader):
- """Socket stream reader."""
-
- def __init__(self, sock, mode='r', bufsize=io.DEFAULT_BUFFER_SIZE):
- """Initialize socket stream reader."""
- super().__init__(socket.SocketIO(sock, mode), bufsize)
- self.bytes_read = 0
-
- def read(self, *args, **kwargs):
- """Capture bytes read."""
- val = super().read(*args, **kwargs)
- self.bytes_read += len(val)
- return val
-
- def has_data(self):
- """Return true if there is buffered data to read."""
- return len(self._read_buf) > self._read_pos
-
- class StreamWriter(BufferedWriter):
- """Socket stream writer."""
-
- def __init__(self, sock, mode='w', bufsize=io.DEFAULT_BUFFER_SIZE):
- """Initialize socket stream writer."""
- super().__init__(socket.SocketIO(sock, mode), bufsize)
- self.bytes_written = 0
-
- def write(self, val, *args, **kwargs):
- """Capture bytes written."""
- res = super().write(val, *args, **kwargs)
- self.bytes_written += len(val)
- return res
-
- def MakeFile(sock, mode='r', bufsize=io.DEFAULT_BUFFER_SIZE):
- """File object attached to a socket object."""
- cls = StreamReader if 'r' in mode else StreamWriter
- return cls(sock, mode, bufsize)
-else:
- StreamReader = StreamWriter = MakeFile = MakeFile_PY2
+def MakeFile(sock, mode='r', bufsize=io.DEFAULT_BUFFER_SIZE):
+ """File object attached to a socket object."""
+ cls = StreamReader if 'r' in mode else StreamWriter
+ return cls(sock, mode, bufsize)
diff --git a/lib/cheroot/makefile.pyi b/lib/cheroot/makefile.pyi
index 11748505..3f5ea275 100644
--- a/lib/cheroot/makefile.pyi
+++ b/lib/cheroot/makefile.pyi
@@ -5,19 +5,6 @@ SOCK_WRITE_BLOCKSIZE: int
class BufferedWriter(io.BufferedWriter):
def write(self, b): ...
-class MakeFile_PY2:
- bytes_read: int
- bytes_written: int
- def __init__(self, *args, **kwargs) -> None: ...
- def write(self, data) -> None: ...
- def send(self, data): ...
- def flush(self) -> None: ...
- def recv(self, size): ...
- class FauxSocket: ...
- def read(self, size: int = ...): ...
- def readline(self, size: int = ...): ...
- def has_data(self): ...
-
class StreamReader(io.BufferedReader):
bytes_read: int
def __init__(self, sock, mode: str = ..., bufsize=...) -> None: ...
diff --git a/lib/cheroot/server.py b/lib/cheroot/server.py
index d92988ab..6b8e37a9 100644
--- a/lib/cheroot/server.py
+++ b/lib/cheroot/server.py
@@ -65,9 +65,6 @@ And now for a trivial doctest to exercise the test suite
True
"""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
import os
import io
import re
@@ -78,20 +75,14 @@ import time
import traceback as traceback_
import logging
import platform
+import queue
import contextlib
import threading
-
-try:
- from functools import lru_cache
-except ImportError:
- from backports.functools_lru_cache import lru_cache
-
-import six
-from six.moves import queue
-from six.moves import urllib
+import urllib.parse
+from functools import lru_cache
from . import connections, errors, __version__
-from ._compat import bton, ntou
+from ._compat import bton
from ._compat import IS_PPC
from .workers import threadpool
from .makefile import MakeFile, StreamWriter
@@ -606,8 +597,8 @@ class ChunkedRFile:
def read_trailer_lines(self):
"""Read HTTP headers and yield them.
- Returns:
- Generator: yields CRLF separated lines.
+ :yields: CRLF separated lines
+ :ytype: bytes
"""
if not self.closed:
@@ -817,10 +808,6 @@ class HTTPRequest:
return False
try:
- if six.PY2: # FIXME: Figure out better way to do this
- # Ref: https://stackoverflow.com/a/196392/595220 (like this?)
- """This is a dummy check for unicode in URI."""
- ntou(bton(uri, 'ascii'), 'ascii')
scheme, authority, path, qs, fragment = urllib.parse.urlsplit(uri)
except UnicodeError:
self.simple_response('400 Bad Request', 'Malformed Request-URI')
@@ -1120,7 +1107,7 @@ class HTTPRequest:
buf.append(CRLF)
if msg:
- if isinstance(msg, six.text_type):
+ if isinstance(msg, str):
msg = msg.encode('ISO-8859-1')
buf.append(msg)
@@ -1422,10 +1409,7 @@ class HTTPConnection:
https://github.com/daveti/tcpSockHack
msdn.microsoft.com/en-us/commandline/wsl/release_notes#build-15025
"""
- six.raise_from( # 3.6+: raise RuntimeError from socket_err
- RuntimeError,
- socket_err,
- )
+ raise RuntimeError from socket_err
else:
pid, uid, gid = struct.unpack(PEERCRED_STRUCT_DEF, peer_creds)
return pid, uid, gid
@@ -1589,7 +1573,7 @@ class HTTPServer:
"""
keep_alive_conn_limit = 10
- """The maximum number of waiting keep-alive connections that will be kept open.
+ """Maximum number of waiting keep-alive connections that will be kept open.
Default is 10. Set to None to have unlimited connections."""
@@ -1762,13 +1746,13 @@ class HTTPServer:
if os.getenv('LISTEN_PID', None):
# systemd socket activation
self.socket = socket.fromfd(3, socket.AF_INET, socket.SOCK_STREAM)
- elif isinstance(self.bind_addr, (six.text_type, six.binary_type)):
+ elif isinstance(self.bind_addr, (str, bytes)):
# AF_UNIX socket
try:
self.bind_unix_socket(self.bind_addr)
except socket.error as serr:
msg = '%s -- (%s: %s)' % (msg, self.bind_addr, serr)
- six.raise_from(socket.error(msg), serr)
+ raise socket.error(msg) from serr
else:
# AF_INET or AF_INET6 socket
# Get the correct address family for our host (allows IPv6
@@ -2007,10 +1991,7 @@ class HTTPServer:
* https://gavv.github.io/blog/ephemeral-port-reuse/
"""
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- if nodelay and not isinstance(
- bind_addr,
- (six.text_type, six.binary_type),
- ):
+ if nodelay and not isinstance(bind_addr, (str, bytes)):
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
if ssl_adapter is not None:
@@ -2059,7 +2040,7 @@ class HTTPServer:
"""
return bind_addr[:2]
- if isinstance(bind_addr, six.binary_type):
+ if isinstance(bind_addr, bytes):
bind_addr = bton(bind_addr)
return bind_addr
@@ -2109,10 +2090,7 @@ class HTTPServer:
sock = getattr(self, 'socket', None)
if sock:
- if not isinstance(
- self.bind_addr,
- (six.text_type, six.binary_type),
- ):
+ if not isinstance(self.bind_addr, (str, bytes)):
# Touch our own socket to make accept() return immediately.
try:
host, port = sock.getsockname()[:2]
@@ -2179,7 +2157,7 @@ ssl_adapters = {
def get_ssl_adapter_class(name='builtin'):
"""Return an SSL adapter class for the given name."""
adapter = ssl_adapters[name.lower()]
- if isinstance(adapter, six.string_types):
+ if isinstance(adapter, str):
last_dot = adapter.rfind('.')
attr_name = adapter[last_dot + 1:]
mod_path = adapter[:last_dot]
diff --git a/lib/cheroot/ssl/__init__.py b/lib/cheroot/ssl/__init__.py
index d45fd7f1..19b587d0 100644
--- a/lib/cheroot/ssl/__init__.py
+++ b/lib/cheroot/ssl/__init__.py
@@ -1,15 +1,9 @@
"""Implementation of the SSL adapter base interface."""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
from abc import ABCMeta, abstractmethod
-from six import add_metaclass
-
-@add_metaclass(ABCMeta)
-class Adapter:
+class Adapter(metaclass=ABCMeta):
"""Base class for SSL driver library adapters.
Required methods:
diff --git a/lib/cheroot/ssl/builtin.py b/lib/cheroot/ssl/builtin.py
index ff987a71..b22d4ae6 100644
--- a/lib/cheroot/ssl/builtin.py
+++ b/lib/cheroot/ssl/builtin.py
@@ -7,12 +7,10 @@ To use this module, set ``HTTPServer.ssl_adapter`` to an instance of
``BuiltinSSLAdapter``.
"""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
import socket
import sys
import threading
+from contextlib import suppress
try:
import ssl
@@ -27,18 +25,13 @@ except ImportError:
except ImportError:
DEFAULT_BUFFER_SIZE = -1
-import six
-
from . import Adapter
from .. import errors
-from .._compat import IS_ABOVE_OPENSSL10, suppress
+from .._compat import IS_ABOVE_OPENSSL10
from ..makefile import StreamReader, StreamWriter
from ..server import HTTPServer
-if six.PY2:
- generic_socket_error = socket.error
-else:
- generic_socket_error = OSError
+generic_socket_error = OSError
def _assert_ssl_exc_contains(exc, *msgs):
diff --git a/lib/cheroot/ssl/builtin.pyi b/lib/cheroot/ssl/builtin.pyi
index fdc656e0..72e45001 100644
--- a/lib/cheroot/ssl/builtin.pyi
+++ b/lib/cheroot/ssl/builtin.pyi
@@ -1,7 +1,6 @@
from typing import Any
from . import Adapter
-generic_socket_error: OSError
DEFAULT_BUFFER_SIZE: int
class BuiltinSSLAdapter(Adapter):
@@ -14,5 +13,5 @@ class BuiltinSSLAdapter(Adapter):
def context(self, context) -> None: ...
def bind(self, sock): ...
def wrap(self, sock): ...
- def get_environ(self): ...
+ def get_environ(self, sock): ...
def makefile(self, sock, mode: str = ..., bufsize: int = ...): ...
diff --git a/lib/cheroot/ssl/pyopenssl.py b/lib/cheroot/ssl/pyopenssl.py
index adc9a1ba..548200f7 100644
--- a/lib/cheroot/ssl/pyopenssl.py
+++ b/lib/cheroot/ssl/pyopenssl.py
@@ -50,16 +50,11 @@ will be read, and the context will be automatically created from them.
pyopenssl
"""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
import socket
import sys
import threading
import time
-import six
-
try:
import OpenSSL.version
from OpenSSL import SSL
@@ -229,8 +224,7 @@ class SSLConnectionProxyMeta:
return type(name, bases, nmspc)
-@six.add_metaclass(SSLConnectionProxyMeta)
-class SSLConnection:
+class SSLConnection(metaclass=SSLConnectionProxyMeta):
r"""A thread-safe wrapper for an ``SSL.Connection``.
:param tuple args: the arguments to create the wrapped \
diff --git a/lib/cheroot/ssl/pyopenssl.pyi b/lib/cheroot/ssl/pyopenssl.pyi
index d5b93471..107675c9 100644
--- a/lib/cheroot/ssl/pyopenssl.pyi
+++ b/lib/cheroot/ssl/pyopenssl.pyi
@@ -1,9 +1,9 @@
from . import Adapter
from ..makefile import StreamReader, StreamWriter
from OpenSSL import SSL
-from typing import Any
+from typing import Any, Type
-ssl_conn_type: SSL.Connection
+ssl_conn_type: Type[SSL.Connection]
class SSLFileobjectMixin:
ssl_timeout: int
@@ -13,13 +13,13 @@ class SSLFileobjectMixin:
def sendall(self, *args, **kwargs): ...
def send(self, *args, **kwargs): ...
-class SSLFileobjectStreamReader(SSLFileobjectMixin, StreamReader): ... # type:ignore
-class SSLFileobjectStreamWriter(SSLFileobjectMixin, StreamWriter): ... # type:ignore
+class SSLFileobjectStreamReader(SSLFileobjectMixin, StreamReader): ... # type:ignore[misc]
+class SSLFileobjectStreamWriter(SSLFileobjectMixin, StreamWriter): ... # type:ignore[misc]
class SSLConnectionProxyMeta:
def __new__(mcl, name, bases, nmspc): ...
-class SSLConnection():
+class SSLConnection:
def __init__(self, *args) -> None: ...
class pyOpenSSLAdapter(Adapter):
@@ -28,3 +28,4 @@ class pyOpenSSLAdapter(Adapter):
def wrap(self, sock): ...
def get_environ(self): ...
def makefile(self, sock, mode: str = ..., bufsize: int = ...): ...
+ def get_context(self) -> SSL.Context: ...
diff --git a/lib/cheroot/test/_pytest_plugin.py b/lib/cheroot/test/_pytest_plugin.py
index 012211df..8ff3b02c 100644
--- a/lib/cheroot/test/_pytest_plugin.py
+++ b/lib/cheroot/test/_pytest_plugin.py
@@ -8,6 +8,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
import pytest
+import six
pytest_version = tuple(map(int, pytest.__version__.split('.')))
@@ -43,8 +44,17 @@ def pytest_load_initial_conftests(early_config, parser, args):
'= 2.15.4.
+ # Refs:
+ # * https://github.com/PyCQA/pylint/issues/6592
+ # * https://github.com/PyCQA/pylint/pull/7395
+ # pylint: disable-next=too-many-function-args
_munge('/привіт'): hello,
+ # pylint: disable-next=too-many-function-args
_munge('/Юххууу'): hello,
'/\xa0Ðblah key 0 900 4 data': hello,
'/*': asterisk,
@@ -151,7 +149,6 @@ def test_parse_acceptable_uri(test_client, uri):
assert actual_status == HTTP_OK
-@pytest.mark.xfail(six.PY2, reason='Fails on Python 2')
def test_parse_uri_unsafe_uri(test_client):
"""Test that malicious URI does not allow HTTP injection.
@@ -263,6 +260,8 @@ def test_no_content_length(test_client):
assert actual_status == HTTP_OK
assert actual_resp_body == b'Hello world!'
+ c.close() # deal with the resource warning
+
def test_content_length_required(test_client):
"""Test POST query with body failing because of missing Content-Length."""
@@ -278,6 +277,8 @@ def test_content_length_required(test_client):
actual_status = response.status
assert actual_status == HTTP_LENGTH_REQUIRED
+ c.close() # deal with the resource warning
+
@pytest.mark.xfail(
reason='https://github.com/cherrypy/cheroot/issues/106',
@@ -350,6 +351,8 @@ def test_malformed_http_method(test_client):
actual_resp_body = response.read(21)
assert actual_resp_body == b'Malformed method name'
+ c.close() # deal with the resource warning
+
def test_malformed_header(test_client):
"""Check that broken HTTP header results in Bad Request."""
@@ -366,6 +369,8 @@ def test_malformed_header(test_client):
actual_resp_body = response.read(20)
assert actual_resp_body == b'Illegal header line.'
+ c.close() # deal with the resource warning
+
def test_request_line_split_issue_1220(test_client):
"""Check that HTTP request line of exactly 256 chars length is OK."""
diff --git a/lib/cheroot/test/test_dispatch.py b/lib/cheroot/test/test_dispatch.py
index 9974fdab..c42014fa 100644
--- a/lib/cheroot/test/test_dispatch.py
+++ b/lib/cheroot/test/test_dispatch.py
@@ -1,8 +1,4 @@
"""Tests for the HTTP server."""
-# -*- coding: utf-8 -*-
-# vim: set fileencoding=utf-8 :
-
-from __future__ import absolute_import, division, print_function
from cheroot.wsgi import PathInfoDispatcher
diff --git a/lib/cheroot/test/test_makefile.py b/lib/cheroot/test/test_makefile.py
index cdded07e..57f6f57e 100644
--- a/lib/cheroot/test/test_makefile.py
+++ b/lib/cheroot/test/test_makefile.py
@@ -3,9 +3,6 @@
from cheroot import makefile
-__metaclass__ = type
-
-
class MockSocket:
"""A mock socket."""
diff --git a/lib/cheroot/test/test_server.py b/lib/cheroot/test/test_server.py
index 8305c78c..5e0a6832 100644
--- a/lib/cheroot/test/test_server.py
+++ b/lib/cheroot/test/test_server.py
@@ -1,23 +1,18 @@
"""Tests for the HTTP server."""
-# -*- coding: utf-8 -*-
-# vim: set fileencoding=utf-8 :
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
import os
+import queue
import socket
import tempfile
import threading
import uuid
+import urllib.parse # noqa: WPS301
import pytest
import requests
import requests_unixsocket
-import six
from pypytools.gc.custom import DefaultGc
-from six.moves import queue, urllib
from .._compat import bton, ntob
from .._compat import IS_LINUX, IS_MACOS, IS_WINDOWS, SYS_PLATFORM
@@ -259,12 +254,12 @@ def peercreds_enabled_server(http_server, unix_sock_file):
@unix_only_sock_test
@non_macos_sock_test
-def test_peercreds_unix_sock(peercreds_enabled_server):
+def test_peercreds_unix_sock(http_request_timeout, peercreds_enabled_server):
"""Check that ``PEERCRED`` lookup works when enabled."""
httpserver = peercreds_enabled_server
bind_addr = httpserver.bind_addr
- if isinstance(bind_addr, six.binary_type):
+ if isinstance(bind_addr, bytes):
bind_addr = bind_addr.decode()
# pylint: disable=possibly-unused-variable
@@ -275,11 +270,17 @@ def test_peercreds_unix_sock(peercreds_enabled_server):
expected_peercreds = '|'.join(map(str, expected_peercreds))
with requests_unixsocket.monkeypatch():
- peercreds_resp = requests.get(unix_base_uri + PEERCRED_IDS_URI)
+ peercreds_resp = requests.get(
+ unix_base_uri + PEERCRED_IDS_URI,
+ timeout=http_request_timeout,
+ )
peercreds_resp.raise_for_status()
assert peercreds_resp.text == expected_peercreds
- peercreds_text_resp = requests.get(unix_base_uri + PEERCRED_TEXTS_URI)
+ peercreds_text_resp = requests.get(
+ unix_base_uri + PEERCRED_TEXTS_URI,
+ timeout=http_request_timeout,
+ )
assert peercreds_text_resp.status_code == 500
@@ -290,14 +291,17 @@ def test_peercreds_unix_sock(peercreds_enabled_server):
)
@unix_only_sock_test
@non_macos_sock_test
-def test_peercreds_unix_sock_with_lookup(peercreds_enabled_server):
+def test_peercreds_unix_sock_with_lookup(
+ http_request_timeout,
+ peercreds_enabled_server,
+):
"""Check that ``PEERCRED`` resolution works when enabled."""
httpserver = peercreds_enabled_server
httpserver.peercreds_resolve_enabled = True
bind_addr = httpserver.bind_addr
- if isinstance(bind_addr, six.binary_type):
+ if isinstance(bind_addr, bytes):
bind_addr = bind_addr.decode()
# pylint: disable=possibly-unused-variable
@@ -312,7 +316,10 @@ def test_peercreds_unix_sock_with_lookup(peercreds_enabled_server):
)
expected_textcreds = '!'.join(map(str, expected_textcreds))
with requests_unixsocket.monkeypatch():
- peercreds_text_resp = requests.get(unix_base_uri + PEERCRED_TEXTS_URI)
+ peercreds_text_resp = requests.get(
+ unix_base_uri + PEERCRED_TEXTS_URI,
+ timeout=http_request_timeout,
+ )
peercreds_text_resp.raise_for_status()
assert peercreds_text_resp.text == expected_textcreds
@@ -363,7 +370,10 @@ def test_high_number_of_file_descriptors(native_server_client, resource_limit):
assert any(fn >= resource_limit for fn in native_process_conn.filenos)
-if not IS_WINDOWS:
+ISSUE511 = IS_MACOS
+
+
+if not IS_WINDOWS and not ISSUE511:
test_high_number_of_file_descriptors = pytest.mark.forked(
test_high_number_of_file_descriptors,
)
diff --git a/lib/cheroot/test/test_ssl.py b/lib/cheroot/test/test_ssl.py
index 8da330df..c55e156f 100644
--- a/lib/cheroot/test/test_ssl.py
+++ b/lib/cheroot/test/test_ssl.py
@@ -1,9 +1,4 @@
"""Tests for TLS support."""
-# -*- coding: utf-8 -*-
-# vim: set fileencoding=utf-8 :
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
import functools
import json
@@ -14,11 +9,11 @@ import sys
import threading
import time
import traceback
+import http.client
import OpenSSL.SSL
import pytest
import requests
-import six
import trustme
from .._compat import bton, ntob, ntou
@@ -49,9 +44,6 @@ IS_PYOPENSSL_SSL_VERSION_1_0 = (
OpenSSL.SSL.SSLeay_version(OpenSSL.SSL.SSLEAY_VERSION).
startswith(b'OpenSSL 1.0.')
)
-PY27 = sys.version_info[:2] == (2, 7)
-PY34 = sys.version_info[:2] == (3, 4)
-PY3 = not six.PY2
PY310_PLUS = sys.version_info[:2] >= (3, 10)
@@ -64,13 +56,12 @@ _stdlib_to_openssl_verify = {
fails_under_py3 = pytest.mark.xfail(
- not six.PY2,
reason='Fails under Python 3+',
)
fails_under_py3_in_pypy = pytest.mark.xfail(
- not six.PY2 and IS_PYPY,
+ IS_PYPY,
reason='Fails under PyPy3',
)
@@ -213,6 +204,7 @@ def thread_exceptions():
),
)
def test_ssl_adapters(
+ http_request_timeout,
tls_http_server, adapter_type,
tls_certificate,
tls_certificate_chain_pem_path,
@@ -241,6 +233,7 @@ def test_ssl_adapters(
resp = requests.get(
'https://{host!s}:{port!s}/'.format(host=interface, port=port),
+ timeout=http_request_timeout,
verify=tls_ca_certificate_pem_path,
)
@@ -276,8 +269,9 @@ def test_ssl_adapters(
reason='Fails under PyPy in CI for unknown reason',
strict=False,
)
-def test_tls_client_auth( # noqa: C901 # FIXME
+def test_tls_client_auth( # noqa: C901, WPS213 # FIXME
# FIXME: remove twisted logic, separate tests
+ http_request_timeout,
mocker,
tls_http_server, adapter_type,
ca,
@@ -331,6 +325,9 @@ def test_tls_client_auth( # noqa: C901 # FIXME
requests.get,
'https://{host!s}:{port!s}/'.format(host=interface, port=port),
+ # Don't wait for the first byte forever:
+ timeout=http_request_timeout,
+
# Server TLS certificate verification:
verify=tls_ca_certificate_pem_path,
@@ -348,12 +345,13 @@ def test_tls_client_auth( # noqa: C901 # FIXME
and tls_verify_mode == ssl.CERT_REQUIRED
and tls_client_identity == 'localhost'
and is_trusted_cert
- ) or PY34:
+ ):
pytest.xfail(
'OpenSSL 1.0 has problems with verifying client certs',
)
assert is_req_successful
assert resp.text == 'Hello world!'
+ resp.close()
return
# xfail some flaky tests
@@ -366,29 +364,16 @@ def test_tls_client_auth( # noqa: C901 # FIXME
if issue_237:
pytest.xfail('Test sometimes fails')
- expected_ssl_errors = (
- requests.exceptions.SSLError,
- OpenSSL.SSL.Error,
- ) if PY34 else (
- requests.exceptions.SSLError,
- )
+ expected_ssl_errors = requests.exceptions.SSLError,
if IS_WINDOWS or IS_GITHUB_ACTIONS_WORKFLOW:
expected_ssl_errors += requests.exceptions.ConnectionError,
with pytest.raises(expected_ssl_errors) as ssl_err:
- make_https_request()
-
- if PY34 and isinstance(ssl_err, OpenSSL.SSL.Error):
- pytest.xfail(
- 'OpenSSL behaves wierdly under Python 3.4 '
- 'because of an outdated urllib3',
- )
+ make_https_request().close()
try:
err_text = ssl_err.value.args[0].reason.args[0].args[0]
except AttributeError:
- if PY34:
- pytest.xfail('OpenSSL behaves wierdly under Python 3.4')
- elif IS_WINDOWS or IS_GITHUB_ACTIONS_WORKFLOW:
+ if IS_WINDOWS or IS_GITHUB_ACTIONS_WORKFLOW:
err_text = str(ssl_err.value)
else:
raise
@@ -400,9 +385,8 @@ def test_tls_client_auth( # noqa: C901 # FIXME
'sslv3 alert bad certificate' if IS_LIBRESSL_BACKEND
else 'tlsv1 alert unknown ca',
)
- if not six.PY2:
- if IS_MACOS and IS_PYPY and adapter_type == 'pyopenssl':
- expected_substrings = ('tlsv1 alert unknown ca',)
+ if IS_MACOS and IS_PYPY and adapter_type == 'pyopenssl':
+ expected_substrings = ('tlsv1 alert unknown ca',)
if (
tls_verify_mode in (
ssl.CERT_REQUIRED,
@@ -469,9 +453,9 @@ def test_tls_client_auth( # noqa: C901 # FIXME
pytest.param(
'builtin',
marks=pytest.mark.xfail(
- IS_GITHUB_ACTIONS_WORKFLOW and IS_MACOS and PY310_PLUS,
+ IS_MACOS and PY310_PLUS,
reason='Unclosed TLS resource warnings happen on macOS '
- 'under Python 3.10',
+ 'under Python 3.10 (#508)',
strict=False,
),
),
@@ -492,6 +476,7 @@ def test_ssl_env( # noqa: C901 # FIXME
thread_exceptions,
recwarn,
mocker,
+ http_request_timeout,
tls_http_server, adapter_type,
ca, tls_verify_mode, tls_certificate,
tls_certificate_chain_pem_path,
@@ -532,13 +517,10 @@ def test_ssl_env( # noqa: C901 # FIXME
resp = requests.get(
'https://' + interface + ':' + str(port) + '/env',
+ timeout=http_request_timeout,
verify=tls_ca_certificate_pem_path,
cert=cl_pem if use_client_cert else None,
)
- if PY34 and resp.status_code != 200:
- pytest.xfail(
- 'Python 3.4 has problems with verifying client certs',
- )
env = json.loads(resp.content.decode('utf-8'))
@@ -620,7 +602,7 @@ def test_https_over_http_error(http_server, ip_addr):
httpserver = http_server.send((ip_addr, EPHEMERAL_PORT))
interface, _host, port = _get_conn_data(httpserver.bind_addr)
with pytest.raises(ssl.SSLError) as ssl_err:
- six.moves.http_client.HTTPSConnection(
+ http.client.HTTPSConnection(
'{interface}:{port}'.format(
interface=interface,
port=port,
@@ -633,20 +615,10 @@ def test_https_over_http_error(http_server, ip_addr):
assert expected_substring in ssl_err.value.args[-1]
-http_over_https_error_builtin_marks = []
-if IS_WINDOWS and six.PY2:
- http_over_https_error_builtin_marks.append(
- pytest.mark.flaky(reruns=5, reruns_delay=2),
- )
-
-
@pytest.mark.parametrize(
'adapter_type',
(
- pytest.param(
- 'builtin',
- marks=http_over_https_error_builtin_marks,
- ),
+ 'builtin',
'pyopenssl',
),
)
@@ -657,7 +629,9 @@ if IS_WINDOWS and six.PY2:
pytest.param(ANY_INTERFACE_IPV6, marks=missing_ipv6),
),
)
+@pytest.mark.flaky(reruns=3, reruns_delay=2)
def test_http_over_https_error(
+ http_request_timeout,
tls_http_server, adapter_type,
ca, ip_addr,
tls_certificate,
@@ -697,36 +671,12 @@ def test_http_over_https_error(
expect_fallback_response_over_plain_http = (
(
adapter_type == 'pyopenssl'
- and (IS_ABOVE_OPENSSL10 or not six.PY2)
)
- or PY27
- ) or (
- IS_GITHUB_ACTIONS_WORKFLOW
- and IS_WINDOWS
- and six.PY2
- and not IS_WIN2016
)
- if (
- IS_GITHUB_ACTIONS_WORKFLOW
- and IS_WINDOWS
- and six.PY2
- and IS_WIN2016
- and adapter_type == 'builtin'
- and ip_addr is ANY_INTERFACE_IPV6
- ):
- expect_fallback_response_over_plain_http = True
- if (
- IS_GITHUB_ACTIONS_WORKFLOW
- and IS_WINDOWS
- and six.PY2
- and not IS_WIN2016
- and adapter_type == 'builtin'
- and ip_addr is not ANY_INTERFACE_IPV6
- ):
- expect_fallback_response_over_plain_http = False
if expect_fallback_response_over_plain_http:
resp = requests.get(
'http://{host!s}:{port!s}/'.format(host=fqdn, port=port),
+ timeout=http_request_timeout,
)
assert resp.status_code == 400
assert resp.text == (
@@ -738,6 +688,7 @@ def test_http_over_https_error(
with pytest.raises(requests.exceptions.ConnectionError) as ssl_err:
requests.get( # FIXME: make stdlib ssl behave like PyOpenSSL
'http://{host!s}:{port!s}/'.format(host=fqdn, port=port),
+ timeout=http_request_timeout,
)
if IS_LINUX:
diff --git a/lib/cheroot/test/test_wsgi.py b/lib/cheroot/test/test_wsgi.py
index 91dfb71e..14005a84 100644
--- a/lib/cheroot/test/test_wsgi.py
+++ b/lib/cheroot/test/test_wsgi.py
@@ -37,6 +37,7 @@ def simple_wsgi_server():
yield locals()
+@pytest.mark.flaky(reruns=3, reruns_delay=2)
def test_connection_keepalive(simple_wsgi_server):
"""Test the connection keepalive works (duh)."""
session = Session(base_url=simple_wsgi_server['url'])
@@ -59,6 +60,7 @@ def test_connection_keepalive(simple_wsgi_server):
]
failures = sum(task.result() for task in tasks)
+ session.close()
assert not failures
diff --git a/lib/cheroot/test/webtest.py b/lib/cheroot/test/webtest.py
index 118014a6..1630c8ef 100644
--- a/lib/cheroot/test/webtest.py
+++ b/lib/cheroot/test/webtest.py
@@ -15,9 +15,6 @@ the traceback to stdout, and keep any assertions you have from running
be of further significance to your tests).
"""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
import pprint
import re
import socket
@@ -29,9 +26,8 @@ import json
import unittest # pylint: disable=deprecated-module,preferred-module
import warnings
import functools
-
-from six.moves import http_client, map, urllib_parse
-import six
+import http.client
+import urllib.parse
from more_itertools.more import always_iterable
import jaraco.functools
@@ -105,7 +101,7 @@ class WebCase(unittest.TestCase):
HOST = '127.0.0.1'
PORT = 8000
- HTTP_CONN = http_client.HTTPConnection
+ HTTP_CONN = http.client.HTTPConnection
PROTOCOL = 'HTTP/1.1'
scheme = 'http'
@@ -127,7 +123,7 @@ class WebCase(unittest.TestCase):
* from :py:mod:`python:http.client`.
"""
cls_name = '{scheme}Connection'.format(scheme=self.scheme.upper())
- return getattr(http_client, cls_name)
+ return getattr(http.client, cls_name)
def get_conn(self, auto_open=False):
"""Return a connection to our HTTP server."""
@@ -201,9 +197,9 @@ class WebCase(unittest.TestCase):
"""
ServerError.on = False
- if isinstance(url, six.text_type):
+ if isinstance(url, str):
url = url.encode('utf-8')
- if isinstance(body, six.text_type):
+ if isinstance(body, str):
body = body.encode('utf-8')
# for compatibility, support raise_subcls is None
@@ -386,7 +382,7 @@ class WebCase(unittest.TestCase):
def assertBody(self, value, msg=None):
"""Fail if value != self.body."""
- if isinstance(value, six.text_type):
+ if isinstance(value, str):
value = value.encode(self.encoding)
if value != self.body:
if msg is None:
@@ -397,7 +393,7 @@ class WebCase(unittest.TestCase):
def assertInBody(self, value, msg=None):
"""Fail if value not in self.body."""
- if isinstance(value, six.text_type):
+ if isinstance(value, str):
value = value.encode(self.encoding)
if value not in self.body:
if msg is None:
@@ -406,7 +402,7 @@ class WebCase(unittest.TestCase):
def assertNotInBody(self, value, msg=None):
"""Fail if value in self.body."""
- if isinstance(value, six.text_type):
+ if isinstance(value, str):
value = value.encode(self.encoding)
if value in self.body:
if msg is None:
@@ -415,7 +411,7 @@ class WebCase(unittest.TestCase):
def assertMatchesBody(self, pattern, msg=None, flags=0):
"""Fail if value (a regex pattern) is not in self.body."""
- if isinstance(pattern, six.text_type):
+ if isinstance(pattern, str):
pattern = pattern.encode(self.encoding)
if re.search(pattern, self.body, flags) is None:
if msg is None:
@@ -464,25 +460,7 @@ def shb(response):
"""Return status, headers, body the way we like from a response."""
resp_status_line = '%s %s' % (response.status, response.reason)
- if not six.PY2:
- return resp_status_line, response.getheaders(), response.read()
-
- h = []
- key, value = None, None
- for line in response.msg.headers:
- if line:
- if line[0] in ' \t':
- value += line.strip()
- else:
- if key and value:
- h.append((key, value))
- key, value = line.split(':', 1)
- key = key.strip()
- value = value.strip()
- if key and value:
- h.append((key, value))
-
- return resp_status_line, h, response.read()
+ return resp_status_line, response.getheaders(), response.read()
# def openURL(*args, raise_subcls=(), **kwargs):
@@ -514,7 +492,7 @@ def openURL(*args, **kwargs):
def _open_url_once(
url, headers=None, method='GET', body=None,
- host='127.0.0.1', port=8000, http_conn=http_client.HTTPConnection,
+ host='127.0.0.1', port=8000, http_conn=http.client.HTTPConnection,
protocol='HTTP/1.1', ssl_context=None,
):
"""Open the given HTTP resource and return status, headers, and body."""
@@ -530,7 +508,7 @@ def _open_url_once(
conn = http_conn(interface(host), port, **kw)
conn._http_vsn_str = protocol
conn._http_vsn = int(''.join([x for x in protocol if x.isdigit()]))
- if not six.PY2 and isinstance(url, bytes):
+ if isinstance(url, bytes):
url = url.decode()
conn.putrequest(
method.upper(), url, skip_host=True,
@@ -572,10 +550,10 @@ def strip_netloc(url):
>>> strip_netloc('/foo/bar?bing#baz')
'/foo/bar?bing'
"""
- parsed = urllib_parse.urlparse(url)
+ parsed = urllib.parse.urlparse(url)
_scheme, _netloc, path, params, query, _fragment = parsed
stripped = '', '', path, params, query, ''
- return urllib_parse.urlunparse(stripped)
+ return urllib.parse.urlunparse(stripped)
# Add any exceptions which your web framework handles
diff --git a/lib/cheroot/testing.py b/lib/cheroot/testing.py
index c9a6ac99..169142bf 100644
--- a/lib/cheroot/testing.py
+++ b/lib/cheroot/testing.py
@@ -1,16 +1,13 @@
"""Pytest fixtures and other helpers for doing testing by end-users."""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-from contextlib import closing
+from contextlib import closing, contextmanager
import errno
import socket
import threading
import time
+import http.client
import pytest
-from six.moves import http_client
import cheroot.server
from cheroot.test import webtest
@@ -33,6 +30,7 @@ config = {
}
+@contextmanager
def cheroot_server(server_factory):
"""Set up and tear down a Cheroot server instance."""
conf = config[server_factory].copy()
@@ -64,14 +62,14 @@ def cheroot_server(server_factory):
@pytest.fixture
def wsgi_server():
"""Set up and tear down a Cheroot WSGI server instance."""
- for srv in cheroot_server(cheroot.wsgi.Server):
+ with cheroot_server(cheroot.wsgi.Server) as srv:
yield srv
@pytest.fixture
def native_server():
"""Set up and tear down a Cheroot HTTP server instance."""
- for srv in cheroot_server(cheroot.server.HTTPServer):
+ with cheroot_server(cheroot.server.HTTPServer) as srv:
yield srv
@@ -89,9 +87,9 @@ class _TestClient:
port=self._port,
)
conn_cls = (
- http_client.HTTPConnection
+ http.client.HTTPConnection
if self.server_instance.ssl_adapter is None else
- http_client.HTTPSConnection
+ http.client.HTTPSConnection
)
return conn_cls(name)
diff --git a/lib/cheroot/workers/threadpool.py b/lib/cheroot/workers/threadpool.py
index 795ebc6d..2a9878dc 100644
--- a/lib/cheroot/workers/threadpool.py
+++ b/lib/cheroot/workers/threadpool.py
@@ -5,17 +5,12 @@
joinable
"""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
import collections
import threading
import time
import socket
import warnings
-
-from six.moves import queue
+import queue
from jaraco.functools import pass_none
@@ -178,7 +173,7 @@ class ThreadPool:
for worker in self._threads:
worker.name = (
'CP Server {worker_name!s}'.
- format(worker_name=worker.name),
+ format(worker_name=worker.name)
)
worker.start()
for worker in self._threads:
@@ -228,7 +223,7 @@ class ThreadPool:
worker = WorkerThread(self.server)
worker.name = (
'CP Server {worker_name!s}'.
- format(worker_name=worker.name),
+ format(worker_name=worker.name)
)
worker.start()
return worker
diff --git a/lib/cheroot/wsgi.py b/lib/cheroot/wsgi.py
index 583d52a9..82faca3e 100644
--- a/lib/cheroot/wsgi.py
+++ b/lib/cheroot/wsgi.py
@@ -25,14 +25,8 @@ as you want in one instance by using a PathInfoDispatcher::
server = wsgi.Server(addr, d)
"""
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
import sys
-import six
-from six.moves import filter
-
from . import server
from .workers import threadpool
from ._compat import ntob, bton
@@ -140,7 +134,7 @@ class Gateway(server.Gateway):
response = self.req.server.wsgi_app(self.env, self.start_response)
try:
for chunk in filter(None, response):
- if not isinstance(chunk, six.binary_type):
+ if not isinstance(chunk, bytes):
raise ValueError('WSGI Applications must yield bytes')
self.write(chunk)
finally:
@@ -149,7 +143,7 @@ class Gateway(server.Gateway):
if hasattr(response, 'close'):
response.close()
- def start_response(self, status, headers, exc_info=None):
+ def start_response(self, status, headers, exc_info=None): # noqa: WPS238
"""WSGI callable to begin the HTTP response."""
# "The application may call start_response more than once,
# if and only if the exc_info argument is provided."
@@ -164,10 +158,8 @@ class Gateway(server.Gateway):
# sent, start_response must raise an error, and should raise the
# exc_info tuple."
if self.req.sent_headers:
- try:
- six.reraise(*exc_info)
- finally:
- exc_info = None
+ value = exc_info[1]
+ raise value
self.req.status = self._encode_status(status)
@@ -196,8 +188,6 @@ class Gateway(server.Gateway):
must be of type "str" but are restricted to code points in the
"Latin-1" set.
"""
- if six.PY2:
- return status
if not isinstance(status, str):
raise TypeError('WSGI response status is not of type str.')
return status.encode('ISO-8859-1')
@@ -273,7 +263,7 @@ class Gateway_10(Gateway):
'wsgi.version': self.version,
}
- if isinstance(req.server.bind_addr, six.string_types):
+ if isinstance(req.server.bind_addr, str):
# AF_UNIX. This isn't really allowed by WSGI, which doesn't
# address unix domain sockets. But it's better than nothing.
env['SERVER_PORT'] = ''
@@ -332,10 +322,10 @@ class Gateway_u0(Gateway_10):
"""Return a new environ dict targeting the given wsgi.version."""
req = self.req
env_10 = super(Gateway_u0, self).get_environ()
- env = dict(map(self._decode_key, env_10.items()))
+ env = dict(env_10.items())
# Request-URI
- enc = env.setdefault(six.u('wsgi.url_encoding'), six.u('utf-8'))
+ enc = env.setdefault('wsgi.url_encoding', 'utf-8')
try:
env['PATH_INFO'] = req.path.decode(enc)
env['QUERY_STRING'] = req.qs.decode(enc)
@@ -345,25 +335,10 @@ class Gateway_u0(Gateway_10):
env['PATH_INFO'] = env_10['PATH_INFO']
env['QUERY_STRING'] = env_10['QUERY_STRING']
- env.update(map(self._decode_value, env.items()))
+ env.update(env.items())
return env
- @staticmethod
- def _decode_key(item):
- k, v = item
- if six.PY2:
- k = k.decode('ISO-8859-1')
- return k, v
-
- @staticmethod
- def _decode_value(item):
- k, v = item
- skip_keys = 'REQUEST_URI', 'wsgi.input'
- if not six.PY2 or not isinstance(v, bytes) or k in skip_keys:
- return k, v
- return k, v.decode('ISO-8859-1')
-
wsgi_gateways = Gateway.gateway_map()
diff --git a/lib/cheroot/wsgi.pyi b/lib/cheroot/wsgi.pyi
index b4851a3d..96075633 100644
--- a/lib/cheroot/wsgi.pyi
+++ b/lib/cheroot/wsgi.pyi
@@ -40,3 +40,10 @@ class PathInfoDispatcher:
apps: Any
def __init__(self, apps): ...
def __call__(self, environ, start_response): ...
+
+
+WSGIServer = Server
+WSGIGateway = Gateway
+WSGIGateway_u0 = Gateway_u0
+WSGIGateway_10 = Gateway_10
+WSGIPathInfoDispatcher = PathInfoDispatcher
diff --git a/lib/importlib_resources/_common.py b/lib/importlib_resources/_common.py
index 9f19784d..6a338c61 100644
--- a/lib/importlib_resources/_common.py
+++ b/lib/importlib_resources/_common.py
@@ -203,5 +203,6 @@ def _write_contents(target, source):
for item in source.iterdir():
_write_contents(child, item)
else:
- child.open('wb').write(source.read_bytes())
+ with child.open('wb') as fp:
+ fp.write(source.read_bytes())
return child
diff --git a/lib/more_itertools/more.py b/lib/more_itertools/more.py
old mode 100644
new mode 100755
diff --git a/lib/tempora/__init__.py b/lib/tempora/__init__.py
index 6652b5ae..cece8bb7 100644
--- a/lib/tempora/__init__.py
+++ b/lib/tempora/__init__.py
@@ -6,6 +6,10 @@ import re
import numbers
import functools
import contextlib
+from numbers import Number
+from typing import Union, Tuple, Iterable
+from typing import cast
+
from jaraco.functools import once
@@ -33,7 +37,7 @@ hours_per_month = hours_per_day * days_per_year / 12
@once
-def _needs_year_help():
+def _needs_year_help() -> bool:
"""
Some versions of Python render %Y with only three characters :(
https://bugs.python.org/issue39103
@@ -41,14 +45,19 @@ def _needs_year_help():
return len(datetime.date(900, 1, 1).strftime('%Y')) != 4
-def ensure_datetime(ob):
+AnyDatetime = Union[datetime.datetime, datetime.date, datetime.time]
+StructDatetime = Union[Tuple[int, ...], time.struct_time]
+
+
+def ensure_datetime(ob: AnyDatetime) -> datetime.datetime:
"""
Given a datetime or date or time object from the ``datetime``
module, always return a datetime using default values.
"""
if isinstance(ob, datetime.datetime):
return ob
- date = time = ob
+ date = cast(datetime.date, ob)
+ time = cast(datetime.time, ob)
if isinstance(ob, datetime.date):
time = datetime.time()
if isinstance(ob, datetime.time):
@@ -56,7 +65,13 @@ def ensure_datetime(ob):
return datetime.datetime.combine(date, time)
-def strftime(fmt, t):
+def infer_datetime(ob: Union[AnyDatetime, StructDatetime]) -> datetime.datetime:
+ if isinstance(ob, (time.struct_time, tuple)):
+ ob = datetime.datetime(*ob[:6]) # type: ignore
+ return ensure_datetime(ob)
+
+
+def strftime(fmt: str, t: Union[AnyDatetime, tuple, time.struct_time]) -> str:
"""
Portable strftime.
@@ -115,15 +130,11 @@ def strftime(fmt, t):
>>> strftime('%Y', datetime.time())
'1900'
"""
- if isinstance(t, (time.struct_time, tuple)):
- t = datetime.datetime(*t[:6])
- t = ensure_datetime(t)
+ t = infer_datetime(t)
subs = (
('%s', '%03d' % (t.microsecond // 1000)),
('%µ', '%03d' % (t.microsecond % 1000)),
- )
- if _needs_year_help(): # pragma: nocover
- subs += (('%Y', '%04d' % t.year),)
+ ) + (('%Y', '%04d' % t.year),) * _needs_year_help()
def doSub(s, sub):
return s.replace(*sub)
@@ -324,10 +335,10 @@ def calculate_prorated_values():
"""
rate = input("Enter the rate (3/hour, 50/month)> ")
for period, value in _prorated_values(rate):
- print("per {period}: {value}".format(**locals()))
+ print(f"per {period}: {value}")
-def _prorated_values(rate):
+def _prorated_values(rate: str) -> Iterable[Tuple[str, Number]]:
"""
Given a rate (a string in units per unit time), and return that same
rate for various time periods.
@@ -341,7 +352,8 @@ def _prorated_values(rate):
year: 175316.333
"""
- res = re.match(r'(?P[\d.]+)/(?P\w+)$', rate).groupdict()
+ match = re.match(r'(?P[\d.]+)/(?P\w+)$', rate)
+ res = cast(re.Match, match).groupdict()
value = float(res['value'])
value_per_second = value / get_period_seconds(res['period'])
for period in ('minute', 'hour', 'day', 'month', 'year'):
diff --git a/lib/tempora/schedule.py b/lib/tempora/schedule.py
index a94c9819..b6ad8aac 100644
--- a/lib/tempora/schedule.py
+++ b/lib/tempora/schedule.py
@@ -130,7 +130,7 @@ class PeriodicCommand(DelayedCommand):
raise ValueError(
"A PeriodicCommand must have a positive, " "non-zero delay."
)
- super(PeriodicCommand, self).__setattr__(key, value)
+ super().__setattr__(key, value)
class PeriodicCommandFixedDelay(PeriodicCommand):
diff --git a/lib/tempora/timing.py b/lib/tempora/timing.py
index 6b3147a9..c43a3d94 100644
--- a/lib/tempora/timing.py
+++ b/lib/tempora/timing.py
@@ -115,7 +115,7 @@ class Timer(Stopwatch):
def __init__(self, target=float('Inf')):
self.target = self._accept(target)
- super(Timer, self).__init__()
+ super().__init__()
@staticmethod
def _accept(target):
diff --git a/lib/tzdata/__init__.py b/lib/tzdata/__init__.py
index c3851325..96456857 100644
--- a/lib/tzdata/__init__.py
+++ b/lib/tzdata/__init__.py
@@ -1,6 +1,6 @@
# IANA versions like 2020a are not valid PEP 440 identifiers; the recommended
# way to translate the version is to use YYYY.n where `n` is a 0-based index.
-__version__ = "2022.6"
+__version__ = "2022.7"
# This exposes the original IANA version number.
-IANA_VERSION = "2022f"
+IANA_VERSION = "2022g"
diff --git a/lib/tzdata/zoneinfo/America/Bogota b/lib/tzdata/zoneinfo/America/Bogota
index 6cb53d4e..85b90333 100644
Binary files a/lib/tzdata/zoneinfo/America/Bogota and b/lib/tzdata/zoneinfo/America/Bogota differ
diff --git a/lib/tzdata/zoneinfo/America/Cambridge_Bay b/lib/tzdata/zoneinfo/America/Cambridge_Bay
index 0a222524..1092f4b6 100644
Binary files a/lib/tzdata/zoneinfo/America/Cambridge_Bay and b/lib/tzdata/zoneinfo/America/Cambridge_Bay differ
diff --git a/lib/tzdata/zoneinfo/America/Ciudad_Juarez b/lib/tzdata/zoneinfo/America/Ciudad_Juarez
new file mode 100644
index 00000000..f636ee64
Binary files /dev/null and b/lib/tzdata/zoneinfo/America/Ciudad_Juarez differ
diff --git a/lib/tzdata/zoneinfo/America/Godthab b/lib/tzdata/zoneinfo/America/Godthab
index 4ddc99d8..79d7a454 100644
Binary files a/lib/tzdata/zoneinfo/America/Godthab and b/lib/tzdata/zoneinfo/America/Godthab differ
diff --git a/lib/tzdata/zoneinfo/America/Inuvik b/lib/tzdata/zoneinfo/America/Inuvik
index af3107db..86639f6e 100644
Binary files a/lib/tzdata/zoneinfo/America/Inuvik and b/lib/tzdata/zoneinfo/America/Inuvik differ
diff --git a/lib/tzdata/zoneinfo/America/Iqaluit b/lib/tzdata/zoneinfo/America/Iqaluit
index eb2c99cc..95e055cb 100644
Binary files a/lib/tzdata/zoneinfo/America/Iqaluit and b/lib/tzdata/zoneinfo/America/Iqaluit differ
diff --git a/lib/tzdata/zoneinfo/America/Nuuk b/lib/tzdata/zoneinfo/America/Nuuk
index 4ddc99d8..79d7a454 100644
Binary files a/lib/tzdata/zoneinfo/America/Nuuk and b/lib/tzdata/zoneinfo/America/Nuuk differ
diff --git a/lib/tzdata/zoneinfo/America/Ojinaga b/lib/tzdata/zoneinfo/America/Ojinaga
index 560b8674..2fc74e94 100644
Binary files a/lib/tzdata/zoneinfo/America/Ojinaga and b/lib/tzdata/zoneinfo/America/Ojinaga differ
diff --git a/lib/tzdata/zoneinfo/America/Pangnirtung b/lib/tzdata/zoneinfo/America/Pangnirtung
index 5be6f9b0..95e055cb 100644
Binary files a/lib/tzdata/zoneinfo/America/Pangnirtung and b/lib/tzdata/zoneinfo/America/Pangnirtung differ
diff --git a/lib/tzdata/zoneinfo/America/Rankin_Inlet b/lib/tzdata/zoneinfo/America/Rankin_Inlet
index 92e2ed2d..6d1d90de 100644
Binary files a/lib/tzdata/zoneinfo/America/Rankin_Inlet and b/lib/tzdata/zoneinfo/America/Rankin_Inlet differ
diff --git a/lib/tzdata/zoneinfo/America/Resolute b/lib/tzdata/zoneinfo/America/Resolute
index a84d1dfd..97eb8a9c 100644
Binary files a/lib/tzdata/zoneinfo/America/Resolute and b/lib/tzdata/zoneinfo/America/Resolute differ
diff --git a/lib/tzdata/zoneinfo/America/Whitehorse b/lib/tzdata/zoneinfo/America/Whitehorse
index 878b6a92..40baa9ab 100644
Binary files a/lib/tzdata/zoneinfo/America/Whitehorse and b/lib/tzdata/zoneinfo/America/Whitehorse differ
diff --git a/lib/tzdata/zoneinfo/America/Yellowknife b/lib/tzdata/zoneinfo/America/Yellowknife
index c779cef9..ff3eb878 100644
Binary files a/lib/tzdata/zoneinfo/America/Yellowknife and b/lib/tzdata/zoneinfo/America/Yellowknife differ
diff --git a/lib/tzdata/zoneinfo/Asia/Kuala_Lumpur b/lib/tzdata/zoneinfo/Asia/Kuala_Lumpur
index 350d77e2..dbbdea3c 100644
Binary files a/lib/tzdata/zoneinfo/Asia/Kuala_Lumpur and b/lib/tzdata/zoneinfo/Asia/Kuala_Lumpur differ
diff --git a/lib/tzdata/zoneinfo/Asia/Singapore b/lib/tzdata/zoneinfo/Asia/Singapore
index 350d77e2..dbbdea3c 100644
Binary files a/lib/tzdata/zoneinfo/Asia/Singapore and b/lib/tzdata/zoneinfo/Asia/Singapore differ
diff --git a/lib/tzdata/zoneinfo/Canada/Yukon b/lib/tzdata/zoneinfo/Canada/Yukon
index 878b6a92..40baa9ab 100644
Binary files a/lib/tzdata/zoneinfo/Canada/Yukon and b/lib/tzdata/zoneinfo/Canada/Yukon differ
diff --git a/lib/tzdata/zoneinfo/Singapore b/lib/tzdata/zoneinfo/Singapore
index 350d77e2..dbbdea3c 100644
Binary files a/lib/tzdata/zoneinfo/Singapore and b/lib/tzdata/zoneinfo/Singapore differ
diff --git a/lib/tzdata/zoneinfo/iso3166.tab b/lib/tzdata/zoneinfo/iso3166.tab
index a4ff61a4..911af5e8 100644
--- a/lib/tzdata/zoneinfo/iso3166.tab
+++ b/lib/tzdata/zoneinfo/iso3166.tab
@@ -3,13 +3,13 @@
# This file is in the public domain, so clarified as of
# 2009-05-17 by Arthur David Olson.
#
-# From Paul Eggert (2015-05-02):
+# From Paul Eggert (2022-11-18):
# This file contains a table of two-letter country codes. Columns are
# separated by a single tab. Lines beginning with '#' are comments.
# All text uses UTF-8 encoding. The columns of the table are as follows:
#
# 1. ISO 3166-1 alpha-2 country code, current as of
-# ISO 3166-1 N976 (2018-11-06). See: Updates on ISO 3166-1
+# ISO 3166-1 N1087 (2022-09-02). See: Updates on ISO 3166-1
# https://isotc.iso.org/livelink/livelink/Open/16944257
# 2. The usual English name for the coded region,
# chosen so that alphabetic sorting of subsets produces helpful lists.
@@ -238,7 +238,7 @@ SY Syria
SZ Eswatini (Swaziland)
TC Turks & Caicos Is
TD Chad
-TF French Southern & Antarctic Lands
+TF French Southern Territories
TG Togo
TH Thailand
TJ Tajikistan
diff --git a/lib/tzdata/zoneinfo/tzdata.zi b/lib/tzdata/zoneinfo/tzdata.zi
index a36449f4..3db1460e 100644
--- a/lib/tzdata/zoneinfo/tzdata.zi
+++ b/lib/tzdata/zoneinfo/tzdata.zi
@@ -1,4 +1,4 @@
-# version 2022f
+# version 2022g
# This zic input file is in the public domain.
R d 1916 o - Jun 14 23s 1 S
R d 1916 1919 - O Su>=1 23s 0 -
@@ -1040,7 +1040,7 @@ Z Asia/Singapore 6:55:25 - LMT 1901
7:20 - +0720 1941 S
7:30 - +0730 1942 F 16
9 - +09 1945 S 12
-7:30 - +0730 1982
+7:30 - +0730 1981 D 31 16u
8 - +08
Z Asia/Colombo 5:19:24 - LMT 1880
5:19:32 - MMT 1906
@@ -1754,7 +1754,8 @@ Z America/Scoresbysund -1:27:52 - LMT 1916 Jul 28
-1 E -01/+00
Z America/Nuuk -3:26:56 - LMT 1916 Jul 28
-3 - -03 1980 Ap 6 2
--3 E -03/-02
+-3 E -03/-02 2023 Mar 25 22
+-2 - -02
Z America/Thule -4:35:8 - LMT 1916 Jul 28
-4 Th A%sT
Z Europe/Tallinn 1:39 - LMT 1880
@@ -3044,16 +3045,11 @@ R Y 1919 o - N 1 0 0 S
R Y 1942 o - F 9 2 1 W
R Y 1945 o - Au 14 23u 1 P
R Y 1945 o - S 30 2 0 S
-R Y 1965 o - Ap lastSu 0 2 DD
-R Y 1965 o - O lastSu 2 0 S
-R Y 1980 1986 - Ap lastSu 2 1 D
-R Y 1980 2006 - O lastSu 2 0 S
+R Y 1972 1986 - Ap lastSu 2 1 D
+R Y 1972 2006 - O lastSu 2 0 S
R Y 1987 2006 - Ap Su>=1 2 1 D
-Z America/Pangnirtung 0 - -00 1921
--4 Y A%sT 1995 Ap Su>=1 2
--5 C E%sT 1999 O 31 2
--6 C C%sT 2000 O 29 2
--5 C E%sT
+R Yu 1965 o - Ap lastSu 0 2 DD
+R Yu 1965 o - O lastSu 2 0 S
Z America/Iqaluit 0 - -00 1942 Au
-5 Y E%sT 1999 O 31 2
-6 C C%sT 2000 O 29 2
@@ -3082,13 +3078,15 @@ Z America/Inuvik 0 - -00 1953
-7 Y M%sT 1980
-7 C M%sT
Z America/Whitehorse -9:0:12 - LMT 1900 Au 20
--9 Y Y%sT 1967 May 28
--8 Y P%sT 1980
+-9 Y Y%sT 1965
+-9 Yu Y%sT 1966 F 27
+-8 - PST 1980
-8 C P%sT 2020 N
-7 - MST
Z America/Dawson -9:17:40 - LMT 1900 Au 20
--9 Y Y%sT 1973 O 28
--8 Y P%sT 1980
+-9 Y Y%sT 1965
+-9 Yu Y%sT 1973 O 28
+-8 - PST 1980
-8 C P%sT 2020 N
-7 - MST
R m 1931 o - May 1 23 1 D
@@ -3132,6 +3130,17 @@ Z America/Mexico_City -6:36:36 - LMT 1922 Ja 1 7u
-6 m C%sT 2001 S 30 2
-6 - CST 2002 F 20
-6 m C%sT
+Z America/Ciudad_Juarez -7:5:56 - LMT 1922 Ja 1 7u
+-7 - MST 1927 Jun 10 23
+-6 - CST 1930 N 15
+-7 m M%sT 1932 Ap
+-6 - CST 1996
+-6 m C%sT 1998
+-6 - CST 1998 Ap Su>=1 3
+-7 m M%sT 2010
+-7 u M%sT 2022 O 30 2
+-6 - CST 2022 N 30
+-7 u M%sT
Z America/Ojinaga -6:57:40 - LMT 1922 Ja 1 7u
-7 - MST 1927 Jun 10 23
-6 - CST 1930 N 15
@@ -3141,7 +3150,8 @@ Z America/Ojinaga -6:57:40 - LMT 1922 Ja 1 7u
-6 - CST 1998 Ap Su>=1 3
-7 m M%sT 2010
-7 u M%sT 2022 O 30 2
--6 - CST
+-6 - CST 2022 N 30
+-6 u C%sT
Z America/Chihuahua -7:4:20 - LMT 1922 Ja 1 7u
-7 - MST 1927 Jun 10 23
-6 - CST 1930 N 15
@@ -3771,7 +3781,7 @@ Z Antarctica/Palmer 0 - -00 1965
-4 x -04/-03 2016 D 4
-3 - -03
R CO 1992 o - May 3 0 1 -
-R CO 1993 o - Ap 4 0 0 -
+R CO 1993 o - F 6 24 0 -
Z America/Bogota -4:56:16 - LMT 1884 Mar 13
-4:56:16 - BMT 1914 N 23
-5 CO -05/-04
@@ -4154,6 +4164,7 @@ L America/Tijuana America/Ensenada
L America/Indiana/Indianapolis America/Fort_Wayne
L America/Toronto America/Montreal
L America/Toronto America/Nipigon
+L America/Iqaluit America/Pangnirtung
L America/Rio_Branco America/Porto_Acre
L America/Winnipeg America/Rainy_River
L America/Argentina/Cordoba America/Rosario
diff --git a/lib/tzdata/zoneinfo/zone.tab b/lib/tzdata/zoneinfo/zone.tab
index 2636e21a..6e5adb9f 100644
--- a/lib/tzdata/zoneinfo/zone.tab
+++ b/lib/tzdata/zoneinfo/zone.tab
@@ -114,8 +114,7 @@ CA +4606-06447 America/Moncton Atlantic - New Brunswick
CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas)
CA +5125-05707 America/Blanc-Sablon AST - QC (Lower North Shore)
CA +4339-07923 America/Toronto Eastern - ON, QC (most areas)
-CA +6344-06828 America/Iqaluit Eastern - NU (most east areas)
-CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung)
+CA +6344-06828 America/Iqaluit Eastern - NU (most areas)
CA +484531-0913718 America/Atikokan EST - ON (Atikokan); NU (Coral H)
CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba
CA +744144-0944945 America/Resolute Central - NU (Resolute)
@@ -277,17 +276,18 @@ MT +3554+01431 Europe/Malta
MU -2010+05730 Indian/Mauritius
MV +0410+07330 Indian/Maldives
MW -1547+03500 Africa/Blantyre
-MX +1924-09909 America/Mexico_City Central Time
-MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo
-MX +2058-08937 America/Merida Central Time - Campeche, Yucatan
-MX +2540-10019 America/Monterrey Central Time - Durango; Coahuila, Nuevo Leon, Tamaulipas (most areas)
-MX +2550-09730 America/Matamoros Central Time US - Coahuila, Nuevo Leon, Tamaulipas (US border)
-MX +2313-10625 America/Mazatlan Mountain Time - Baja California Sur, Nayarit, Sinaloa
-MX +2838-10605 America/Chihuahua Mountain Time - Chihuahua (most areas)
-MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border)
-MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora
-MX +3232-11701 America/Tijuana Pacific Time US - Baja California
-MX +2048-10515 America/Bahia_Banderas Central Time - Bahia de Banderas
+MX +1924-09909 America/Mexico_City Central Mexico
+MX +2105-08646 America/Cancun Quintana Roo
+MX +2058-08937 America/Merida Campeche, Yucatan
+MX +2540-10019 America/Monterrey Durango; Coahuila, Nuevo Leon, Tamaulipas (most areas)
+MX +2550-09730 America/Matamoros Coahuila, Nuevo Leon, Tamaulipas (US border)
+MX +2838-10605 America/Chihuahua Chihuahua (most areas)
+MX +3144-10629 America/Ciudad_Juarez Chihuahua (US border - west)
+MX +2934-10425 America/Ojinaga Chihuahua (US border - east)
+MX +2313-10625 America/Mazatlan Baja California Sur, Nayarit (most areas), Sinaloa
+MX +2048-10515 America/Bahia_Banderas Bahia de Banderas
+MX +2904-11058 America/Hermosillo Sonora
+MX +3232-11701 America/Tijuana Baja California
MY +0310+10142 Asia/Kuala_Lumpur Malaysia (peninsula)
MY +0133+11020 Asia/Kuching Sabah, Sarawak
MZ -2558+03235 Africa/Maputo
diff --git a/lib/tzdata/zoneinfo/zone1970.tab b/lib/tzdata/zoneinfo/zone1970.tab
index 75372e3f..a9b36d36 100644
--- a/lib/tzdata/zoneinfo/zone1970.tab
+++ b/lib/tzdata/zoneinfo/zone1970.tab
@@ -102,8 +102,7 @@ CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton)
CA +4606-06447 America/Moncton Atlantic - New Brunswick
CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas)
CA,BS +4339-07923 America/Toronto Eastern - ON, QC (most areas), Bahamas
-CA +6344-06828 America/Iqaluit Eastern - NU (most east areas)
-CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung)
+CA +6344-06828 America/Iqaluit Eastern - NU (most areas)
CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba
CA +744144-0944945 America/Resolute Central - NU (Resolute)
CA +624900-0920459 America/Rankin_Inlet Central - NU (central)
@@ -214,17 +213,18 @@ MQ +1436-06105 America/Martinique
MT +3554+01431 Europe/Malta
MU -2010+05730 Indian/Mauritius
MV,TF +0410+07330 Indian/Maldives Maldives, Kerguelen, St Paul I, Amsterdam I
-MX +1924-09909 America/Mexico_City Central Time
-MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo
-MX +2058-08937 America/Merida Central Time - Campeche, Yucatán
-MX +2540-10019 America/Monterrey Central Time - Durango; Coahuila, Nuevo León, Tamaulipas (most areas)
-MX +2550-09730 America/Matamoros Central Time US - Coahuila, Nuevo León, Tamaulipas (US border)
-MX +2313-10625 America/Mazatlan Mountain Time - Baja California Sur, Nayarit, Sinaloa
-MX +2838-10605 America/Chihuahua Mountain Time - Chihuahua (most areas)
-MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border)
-MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora
-MX +3232-11701 America/Tijuana Pacific Time US - Baja California
-MX +2048-10515 America/Bahia_Banderas Central Time - Bahía de Banderas
+MX +1924-09909 America/Mexico_City Central Mexico
+MX +2105-08646 America/Cancun Quintana Roo
+MX +2058-08937 America/Merida Campeche, Yucatán
+MX +2540-10019 America/Monterrey Durango; Coahuila, Nuevo León, Tamaulipas (most areas)
+MX +2550-09730 America/Matamoros Coahuila, Nuevo León, Tamaulipas (US border)
+MX +2838-10605 America/Chihuahua Chihuahua (most areas)
+MX +3144-10629 America/Ciudad_Juarez Chihuahua (US border - west)
+MX +2934-10425 America/Ojinaga Chihuahua (US border - east)
+MX +2313-10625 America/Mazatlan Baja California Sur, Nayarit (most areas), Sinaloa
+MX +2048-10515 America/Bahia_Banderas Bahía de Banderas
+MX +2904-11058 America/Hermosillo Sonora
+MX +3232-11701 America/Tijuana Baja California
MY,BN +0133+11020 Asia/Kuching Sabah, Sarawak, Brunei
MZ,BI,BW,CD,MW,RW,ZM,ZW -2558+03235 Africa/Maputo Central Africa Time
NA -2234+01706 Africa/Windhoek
diff --git a/lib/tzdata/zones b/lib/tzdata/zones
index d8ec444a..8d9892ed 100644
--- a/lib/tzdata/zones
+++ b/lib/tzdata/zones
@@ -239,7 +239,6 @@ America/Edmonton
America/Vancouver
America/Dawson_Creek
America/Fort_Nelson
-America/Pangnirtung
America/Iqaluit
America/Resolute
America/Rankin_Inlet
@@ -253,6 +252,7 @@ America/Merida
America/Matamoros
America/Monterrey
America/Mexico_City
+America/Ciudad_Juarez
America/Ojinaga
America/Chihuahua
America/Hermosillo
@@ -554,6 +554,7 @@ America/Ensenada
America/Fort_Wayne
America/Montreal
America/Nipigon
+America/Pangnirtung
America/Porto_Acre
America/Rainy_River
America/Rosario
diff --git a/lib/urllib3/_version.py b/lib/urllib3/_version.py
index 6fbc84b3..308d7f28 100644
--- a/lib/urllib3/_version.py
+++ b/lib/urllib3/_version.py
@@ -1,2 +1,2 @@
# This file is protected via CODEOWNERS
-__version__ = "1.26.12"
+__version__ = "1.26.13"
diff --git a/lib/urllib3/connectionpool.py b/lib/urllib3/connectionpool.py
index 96339e90..70873927 100644
--- a/lib/urllib3/connectionpool.py
+++ b/lib/urllib3/connectionpool.py
@@ -862,7 +862,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
)
# Check if we should retry the HTTP response.
- has_retry_after = bool(response.getheader("Retry-After"))
+ has_retry_after = bool(response.headers.get("Retry-After"))
if retries.is_retry(method, response.status, has_retry_after):
try:
retries = retries.increment(method, url, response=response, _pool=self)
diff --git a/lib/urllib3/contrib/pyopenssl.py b/lib/urllib3/contrib/pyopenssl.py
index 50a07d59..1ed214b1 100644
--- a/lib/urllib3/contrib/pyopenssl.py
+++ b/lib/urllib3/contrib/pyopenssl.py
@@ -47,10 +47,10 @@ compression in Python 2 (see `CRIME attack`_).
"""
from __future__ import absolute_import
+import OpenSSL.crypto
import OpenSSL.SSL
from cryptography import x509
from cryptography.hazmat.backends.openssl import backend as openssl_backend
-from cryptography.hazmat.backends.openssl.x509 import _Certificate
try:
from cryptography.x509 import UnsupportedExtension
@@ -228,9 +228,8 @@ def get_subj_alt_name(peer_cert):
if hasattr(peer_cert, "to_cryptography"):
cert = peer_cert.to_cryptography()
else:
- # This is technically using private APIs, but should work across all
- # relevant versions before PyOpenSSL got a proper API for this.
- cert = _Certificate(openssl_backend, peer_cert._x509)
+ der = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, peer_cert)
+ cert = x509.load_der_x509_certificate(der, openssl_backend)
# We want to find the SAN extension. Ask Cryptography to locate it (it's
# faster than looping in Python)
diff --git a/lib/urllib3/response.py b/lib/urllib3/response.py
index 01f08eee..8f1b4fa8 100644
--- a/lib/urllib3/response.py
+++ b/lib/urllib3/response.py
@@ -3,6 +3,7 @@ from __future__ import absolute_import
import io
import logging
import sys
+import warnings
import zlib
from contextlib import contextmanager
from socket import error as SocketError
@@ -663,9 +664,21 @@ class HTTPResponse(io.IOBase):
# Backwards-compatibility methods for http.client.HTTPResponse
def getheaders(self):
+ warnings.warn(
+ "HTTPResponse.getheaders() is deprecated and will be removed "
+ "in urllib3 v2.1.0. Instead access HTTResponse.headers directly.",
+ category=DeprecationWarning,
+ stacklevel=2,
+ )
return self.headers
def getheader(self, name, default=None):
+ warnings.warn(
+ "HTTPResponse.getheader() is deprecated and will be removed "
+ "in urllib3 v2.1.0. Instead use HTTResponse.headers.get(name, default).",
+ category=DeprecationWarning,
+ stacklevel=2,
+ )
return self.headers.get(name, default)
# Backwards compatibility for http.cookiejar
diff --git a/lib/urllib3/util/retry.py b/lib/urllib3/util/retry.py
index 3398323f..2490d5e5 100644
--- a/lib/urllib3/util/retry.py
+++ b/lib/urllib3/util/retry.py
@@ -394,7 +394,7 @@ class Retry(object):
def get_retry_after(self, response):
"""Get the value of Retry-After in seconds."""
- retry_after = response.getheader("Retry-After")
+ retry_after = response.headers.get("Retry-After")
if retry_after is None:
return None
diff --git a/lib/urllib3/util/url.py b/lib/urllib3/util/url.py
index b667c160..94f1b8d4 100644
--- a/lib/urllib3/util/url.py
+++ b/lib/urllib3/util/url.py
@@ -63,7 +63,7 @@ IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT + "$")
BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT[2:-2] + "$")
ZONE_ID_RE = re.compile("(" + ZONE_ID_PAT + r")\]$")
-_HOST_PORT_PAT = ("^(%s|%s|%s)(?::([0-9]{0,5}))?$") % (
+_HOST_PORT_PAT = ("^(%s|%s|%s)(?::0*([0-9]{0,5}))?$") % (
REG_NAME_PAT,
IPV4_PAT,
IPV6_ADDRZ_PAT,
diff --git a/lib/zipp/__init__.py b/lib/zipp/__init__.py
index c1f3632e..ad01e27e 100644
--- a/lib/zipp/__init__.py
+++ b/lib/zipp/__init__.py
@@ -4,6 +4,8 @@ import zipfile
import itertools
import contextlib
import pathlib
+import re
+import fnmatch
from .py310compat import text_encoding
@@ -243,6 +245,18 @@ class Path:
self.root = FastLookup.make(root)
self.at = at
+ def __eq__(self, other):
+ """
+ >>> Path(zipfile.ZipFile(io.BytesIO(), 'w')) == 'foo'
+ False
+ """
+ if self.__class__ is not other.__class__:
+ return NotImplemented
+ return (self.root, self.at) == (other.root, other.at)
+
+ def __hash__(self):
+ return hash((self.root, self.at))
+
def open(self, mode='r', *args, pwd=None, **kwargs):
"""
Open this entry as text or binary following the semantics
@@ -313,6 +327,38 @@ class Path:
subs = map(self._next, self.root.namelist())
return filter(self._is_child, subs)
+ def match(self, path_pattern):
+ return pathlib.Path(self.at).match(path_pattern)
+
+ def is_symlink(self):
+ """
+ Return whether this path is a symlink. Always false (python/cpython#82102).
+ """
+ return False
+
+ def _descendants(self):
+ for child in self.iterdir():
+ yield child
+ if child.is_dir():
+ yield from child._descendants()
+
+ def glob(self, pattern):
+ if not pattern:
+ raise ValueError("Unacceptable pattern: {!r}".format(pattern))
+
+ matches = re.compile(fnmatch.translate(pattern)).fullmatch
+ return (
+ child
+ for child in self._descendants()
+ if matches(str(child.relative_to(self)))
+ )
+
+ def rglob(self, pattern):
+ return self.glob(f'**/{pattern}')
+
+ def relative_to(self, other, *extra):
+ return posixpath.relpath(str(self), str(other.joinpath(*extra)))
+
def __str__(self):
return posixpath.join(self.root.filename, self.at)
diff --git a/package/requirements-package.txt b/package/requirements-package.txt
index 7dc0227b..6260d288 100644
--- a/package/requirements-package.txt
+++ b/package/requirements-package.txt
@@ -1,6 +1,6 @@
apscheduler==3.9.1.post1
importlib-metadata==5.0.0
-importlib-resources==5.10.0
+importlib-resources==5.10.1
pyinstaller==5.6.2
pyopenssl==22.1.0
pycryptodomex==3.15.0
diff --git a/plexpy/common.py b/plexpy/common.py
index b6800d3c..33b4cc00 100644
--- a/plexpy/common.py
+++ b/plexpy/common.py
@@ -400,6 +400,12 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Player', 'type': 'str', 'value': 'player', 'description': 'The name of the player being used for playback.'},
{'name': 'Initial Stream', 'type': 'int', 'value': 'initial_stream', 'description': 'If the stream is the initial stream of a continuous streaming session.', 'example': '0 or 1'},
{'name': 'IP Address', 'type': 'str', 'value': 'ip_address', 'description': 'The IP address of the device being used for playback.'},
+ {'name': 'Started Datestamp', 'type': 'str', 'value': 'started_datestamp', 'description': 'The date (in date format) when the stream started.'},
+ {'name': 'Started Timestamp', 'type': 'str', 'value': 'started_timestamp', 'description': 'The time (in time format) when the stream started.'},
+ {'name': 'Started Unix Time', 'type': 'int', 'value': 'started_unixtime', 'description': 'The unix timestamp when the stream started.'},
+ {'name': 'Stopped Datestamp', 'type': 'str', 'value': 'stopped_datestamp', 'description': 'The date (in date format) when the stream stopped.'},
+ {'name': 'Stopped Timestamp', 'type': 'str', 'value': 'stopped_timestamp', 'description': 'The time (in time format) when the stream stopped.'},
+ {'name': 'Stopped Unix Time', 'type': 'int', 'value': 'stopped_unixtime', 'description': 'The unix timestamp when the stream stopped.'},
{'name': 'Stream Duration', 'type': 'int', 'value': 'stream_duration', 'description': 'The duration (in minutes) for the stream.'},
{'name': 'Stream Duration (sec)', 'type': 'int', 'value': 'stream_duration_sec', 'description': 'The duration (in seconds) for the stream.'},
{'name': 'Stream Time', 'type': 'str', 'value': 'stream_time', 'description': 'The duration (in time format) of the stream.'},
diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py
index 315bcb52..7c16de00 100644
--- a/plexpy/notification_handler.py
+++ b/plexpy/notification_handler.py
@@ -985,6 +985,12 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
'product': notify_params['product'],
'player': notify_params['player'],
'ip_address': notify_params.get('ip_address', 'N/A'),
+ 'started_datestamp': arrow.get(notify_params['started']).format(date_format),
+ 'started_timestamp': arrow.get(notify_params['started']).format(time_format),
+ 'started_unixtime': notify_params['started'],
+ 'stopped_datestamp': arrow.get(notify_params['stopped']).format(date_format) if notify_params['stopped'] else '',
+ 'stopped_timestamp': arrow.get(notify_params['stopped']).format(time_format) if notify_params['stopped'] else '',
+ 'stopped_unixtime': notify_params['stopped'],
'stream_duration': stream_duration,
'stream_duration_sec': stream_duration_sec,
'stream_time': arrow.get(stream_duration_sec).format(duration_format),
diff --git a/requirements.txt b/requirements.txt
index 5be5f6ff..e31185a6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,7 +7,7 @@ backports.zoneinfo==0.2.1
beautifulsoup4==4.11.1
bleach==5.0.1
certifi==2022.12.7
-cheroot==8.6.0
+cheroot==9.0.0
cherrypy==18.8.0
cloudinary==1.30.0
distro==1.8.0
@@ -19,7 +19,7 @@ html5lib==1.1
httpagentparser==1.9.5
idna==3.4
importlib-metadata==5.0.0
-importlib-resources==5.10.0
+importlib-resources==5.10.1
git+https://github.com/Tautulli/ipwhois.git@master#egg=ipwhois
IPy==1.01
Mako==1.2.4
@@ -41,15 +41,15 @@ rumps==0.4.0; platform_system == "Darwin"
simplejson==3.18.0
six==1.16.0
soupsieve==2.3.2.post1
-tempora==5.0.2
+tempora==5.1.0
tokenize-rt==5.0.0
-tzdata==2022.6
+tzdata==2022.7
tzlocal==4.2
-urllib3==1.26.12
+urllib3==1.26.13
webencodings==0.5.1
websocket-client==1.4.2
xmltodict==0.13.0
-zipp==3.10.0
+zipp==3.11.0
# configobj==5.1.0
# sgmllib3k==1.0.0