Hello community, here is the log from the commit of package python-h2 for openSUSE:Factory checked in at 2019-03-29 20:38:42 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-h2 (Old) and /work/SRC/openSUSE:Factory/.python-h2.new.25356 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-h2" Fri Mar 29 20:38:42 2019 rev:3 rq:689263 version:3.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-h2/python-h2.changes 2018-12-27 00:26:27.347807172 +0100 +++ /work/SRC/openSUSE:Factory/.python-h2.new.25356/python-h2.changes 2019-03-29 20:38:43.850680088 +0100 @@ -1,0 +2,25 @@ +Thu Mar 28 04:06:48 UTC 2019 - John Vandenberg <jay...@gmail.com> + +- Set URL to more useful https://github.com/python-hyper/hyper-h2 +- Update to v3.1.0 + * API Changes (Backward-Incompatible) + + h2.connection.H2Connection.data_to_send first and only + argument ``amt`` was renamed to ``amount`` + + Support for Python 3.3 has been removed. + * API Changes (Backward-Compatible) + + h2.connection.H2Connection.send_data now supports ``data`` + parameter being a ``memoryview`` object. + + Refactor ping-related events: a h2.events.PingReceived event is + fired when a PING frame is received and a h2.events.PingAckReceived + event is fired when a PING frame with an ACK flag is received + + h2.events.PingAcknowledged is deprecated in favour of the + identical h2.events.PingAckReceived + + Added ENABLE_CONNECT_PROTOCOL to h2.settings.SettingCodes + + Support CONNECT requests with a ``:protocol`` pseudo header + thereby supporting RFC 8441. + + A limit to the number of closed streams kept in memory by the + connection is applied. It can be configured by + h2.connection.H2Connection.MAX_CLOSED_STREAMS + * Debug logging when stream_id is None is now fixed and no longer errors. + +------------------------------------------------------------------- Old: ---- h2-3.0.1.tar.gz New: ---- h2-3.1.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-h2.spec ++++++ --- /var/tmp/diff_new_pack.0WqcQq/_old 2019-03-29 20:38:44.462680400 +0100 +++ /var/tmp/diff_new_pack.0WqcQq/_new 2019-03-29 20:38:44.470680404 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-h2 # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,25 +18,25 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-h2 -Version: 3.0.1 +Version: 3.1.0 Release: 0 Summary: HTTP/2 State-Machine based protocol implementation License: MIT Group: Development/Languages/Python -URL: http://hyper.rtfd.org +URL: https://github.com/python-hyper/hyper-h2 Source0: https://files.pythonhosted.org/packages/source/h/h2/h2-%{version}.tar.gz # test requirements BuildRequires: %{python_module coverage} -BuildRequires: %{python_module hpack} -BuildRequires: %{python_module hyperframe >= 5.0} +BuildRequires: %{python_module hpack >= 2.3} +BuildRequires: %{python_module hyperframe >= 5.2.0} BuildRequires: %{python_module hypothesis} BuildRequires: %{python_module pytest-cov} BuildRequires: %{python_module pytest-xdist} BuildRequires: %{python_module pytest} BuildRequires: fdupes BuildRequires: python-rpm-macros -Requires: python-hpack >= 2.2 -Requires: python-hyperframe >= 5.0 +Requires: python-hpack >= 2.3 +Requires: python-hyperframe >= 5.2.0 BuildArch: noarch %python_subpackages ++++++ h2-3.0.1.tar.gz -> h2-3.1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/CONTRIBUTORS.rst new/h2-3.1.0/CONTRIBUTORS.rst --- old/h2-3.0.1/CONTRIBUTORS.rst 2017-04-03 09:47:32.000000000 +0200 +++ new/h2-3.1.0/CONTRIBUTORS.rst 2019-01-22 18:41:43.000000000 +0100 @@ -111,4 +111,5 @@ - Fred Thomsen (@fredthomsen) - Added logging. - + - Enhance equality testing of ``h2.settings.Settings`` objects with + ``hypothesis``. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/HISTORY.rst new/h2-3.1.0/HISTORY.rst --- old/h2-3.0.1/HISTORY.rst 2017-04-03 09:58:48.000000000 +0200 +++ new/h2-3.1.0/HISTORY.rst 2019-01-22 18:41:49.000000000 +0100 @@ -1,6 +1,38 @@ Release History =============== +3.1.0 (2019-01-22) +------------------ + +API Changes (Backward-Incompatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``h2.connection.H2Connection.data_to_send`` first and only argument ``amt`` + was renamed to ``amount``. +- Support for Python 3.3 has been removed. + +API Changes (Backward-Compatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``h2.connection.H2Connection.send_data`` now supports ``data`` parameter + being a ``memoryview`` object. +- Refactor ping-related events: a ``h2.events.PingReceived`` event is fired + when a PING frame is received and a ``h2.events.PingAckReceived`` event is + fired when a PING frame with an ACK flag is received. + ``h2.events.PingAcknowledged`` is deprecated in favour of the identical + ``h2.events.PingAckReceived``. +- Added ``ENABLE_CONNECT_PROTOCOL`` to ``h2.settings.SettingCodes``. +- Support ``CONNECT`` requests with a ``:protocol`` pseudo header + thereby supporting RFC 8441. +- A limit to the number of closed streams kept in memory by the + connection is applied. It can be configured by + ``h2.connection.H2Connection.MAX_CLOSED_STREAMS``. + +Bugfixes +~~~~~~~~ + +- Debug logging when stream_id is None is now fixed and no longer errors. + 3.0.1 (2017-04-03) ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/PKG-INFO new/h2-3.1.0/PKG-INFO --- old/h2-3.0.1/PKG-INFO 2017-04-03 10:07:26.000000000 +0200 +++ new/h2-3.1.0/PKG-INFO 2019-01-22 18:42:26.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: h2 -Version: 3.0.1 +Version: 3.1.0 Summary: HTTP/2 State-Machine based protocol implementation Home-page: http://hyper.rtfd.org Author: Cory Benfield @@ -76,6 +76,38 @@ Release History =============== + 3.1.0 (2019-01-22) + ------------------ + + API Changes (Backward-Incompatible) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + - ``h2.connection.H2Connection.data_to_send`` first and only argument ``amt`` + was renamed to ``amount``. + - Support for Python 3.3 has been removed. + + API Changes (Backward-Compatible) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + - ``h2.connection.H2Connection.send_data`` now supports ``data`` parameter + being a ``memoryview`` object. + - Refactor ping-related events: a ``h2.events.PingReceived`` event is fired + when a PING frame is received and a ``h2.events.PingAckReceived`` event is + fired when a PING frame with an ACK flag is received. + ``h2.events.PingAcknowledged`` is deprecated in favour of the identical + ``h2.events.PingAckReceived``. + - Added ``ENABLE_CONNECT_PROTOCOL`` to ``h2.settings.SettingCodes``. + - Support ``CONNECT`` requests with a ``:protocol`` pseudo header + thereby supporting RFC 8441. + - A limit to the number of closed streams kept in memory by the + connection is applied. It can be configured by + ``h2.connection.H2Connection.MAX_CLOSED_STREAMS``. + + Bugfixes + ~~~~~~~~ + + - Debug logging when stream_id is None is now fixed and no longer errors. + 3.0.1 (2017-04-03) ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/docs/source/api.rst new/h2-3.1.0/docs/source/api.rst --- old/h2-3.0.1/docs/source/api.rst 2017-04-03 09:57:46.000000000 +0200 +++ new/h2-3.1.0/docs/source/api.rst 2019-01-22 18:41:43.000000000 +0100 @@ -53,7 +53,10 @@ .. autoclass:: h2.events.RemoteSettingsChanged :members: -.. autoclass:: h2.events.PingAcknowledged +.. autoclass:: h2.events.PingReceived + :members: + +.. autoclass:: h2.events.PingAckReceived :members: .. autoclass:: h2.events.StreamEnded diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/docs/source/basic-usage.rst new/h2-3.1.0/docs/source/basic-usage.rst --- old/h2-3.0.1/docs/source/basic-usage.rst 2017-03-16 10:02:02.000000000 +0100 +++ new/h2-3.1.0/docs/source/basic-usage.rst 2019-01-22 18:41:43.000000000 +0100 @@ -251,9 +251,11 @@ .. code-block:: python import h2.connection + import h2.config def handle(sock): - conn = h2.connection.H2Connection(client_side=False) + config = h2.config.H2Configuration(client_side=False) + conn = h2.connection.H2Connection(config=config) while True: data = sock.recv(65535) @@ -267,9 +269,11 @@ import socket import h2.connection + import h2.config def handle(sock): - conn = h2.connection.H2Connection(client_side=False) + config = h2.config.H2Configuration(client_side=False) + conn = h2.connection.H2Connection(config=config) while True: data = sock.recv(65535) @@ -331,7 +335,8 @@ .. code-block:: python def handle(sock): - conn = h2.connection.H2Connection(client_side=False) + config = h2.config.H2Configuration(client_side=False) + conn = h2.connection.H2Connection(config=config) conn.initiate_connection() sock.sendall(conn.data_to_send()) @@ -359,9 +364,11 @@ import socket import h2.connection + import h2.config def handle(sock): - conn = h2.connection.H2Connection(client_side=False) + config = h2.config.H2Configuration(client_side=False) + conn = h2.connection.H2Connection(config=config) conn.initiate_connection() sock.sendall(conn.data_to_send()) @@ -493,9 +500,11 @@ .. code-block:: python import h2.events + import h2.config def handle(sock): - conn = h2.connection.H2Connection(client_side=False) + config = h2.config.H2Configuration(client_side=False) + conn = h2.connection.H2Connection(config=config) conn.initiate_connection() sock.sendall(conn.data_to_send()) @@ -528,6 +537,7 @@ import h2.connection import h2.events + import h2.config def send_response(conn, event): stream_id = event.stream_id @@ -545,7 +555,8 @@ ) def handle(sock): - conn = h2.connection.H2Connection(client_side=False) + config = h2.config.H2Configuration(client_side=False) + conn = h2.connection.H2Connection(config=config) conn.initiate_connection() sock.sendall(conn.data_to_send()) @@ -633,6 +644,7 @@ import h2.connection import h2.events + import h2.config def send_response(conn, event): stream_id = event.stream_id @@ -654,7 +666,8 @@ ) def handle(sock): - conn = h2.connection.H2Connection(client_side=False) + config = h2.config.H2Configuration(client_side=False) + conn = h2.connection.H2Connection(config=config) conn.initiate_connection() sock.sendall(conn.data_to_send()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/docs/source/conf.py new/h2-3.1.0/docs/source/conf.py --- old/h2-3.0.1/docs/source/conf.py 2017-04-03 09:58:57.000000000 +0200 +++ new/h2-3.1.0/docs/source/conf.py 2019-01-22 18:41:49.000000000 +0100 @@ -55,9 +55,9 @@ # built documents. # # The short X.Y version. -version = '3.0.1' +version = '3.1.0' # The full version, including alpha/beta/rc tags. -release = '3.0.1' +release = '3.1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/docs/source/negotiating-http2.rst new/h2-3.1.0/docs/source/negotiating-http2.rst --- old/h2-3.0.1/docs/source/negotiating-http2.rst 2017-04-03 09:47:32.000000000 +0200 +++ new/h2-3.1.0/docs/source/negotiating-http2.rst 2019-01-22 18:41:43.000000000 +0100 @@ -5,15 +5,19 @@ .. _starting-alpn: -HTTPS URLs (ALPN and NPN) +HTTPS URLs (ALPN) ------------------------- -Starting HTTP/2 for HTTPS URLs is outlined in `RFC 7540 Section 3.3`_. In this case, the client and server use a TLS extension to negotiate HTTP/2: typically either or both of `NPN`_ or `ALPN`_. How to use NPN and ALPN is currently not covered in this document: please consult the documentation for either the :mod:`ssl module <python:ssl>` in the standard library, or the :mod:`PyOpenSSL <pyopenssl:OpenSSL.SSL>` third-party modules, for more on this topic. +Starting HTTP/2 for HTTPS URLs is outlined in `RFC 7540 Section 3.3`_. In this case, the client and server use a TLS extension to negotiate HTTP/2: `ALPN`_. How to use ALPN is currently not covered in this document: please consult the documentation for either the :mod:`ssl module <python:ssl>` in the standard library, or the :mod:`PyOpenSSL <pyopenssl:OpenSSL.SSL>` third-party modules, for more on this topic. This method is the simplest to use once the TLS connection is established. To use it with Hyper-h2, after you've established the connection and confirmed that HTTP/2 has been negotiated with `ALPN`_, create a :class:`H2Connection <h2.connection.H2Connection>` object and call :meth:`H2Connection.initiate_connection <h2.connection.H2Connection.initiate_connection>`. This will ensure that the appropriate preamble data is placed in the data buffer. You should then immediately send the data returned by :meth:`H2Connection.data_to_send <h2.connection.H2Connection.data_to_send>` on your TLS connection. At this point, you're free to use all the HTTP/2 functionality provided by Hyper-h2. +.. note:: + Although Hyper-h2 is not concerned with negotiating protocol versions, it is important to note that support for `ALPN`_ is not available in the standard library of Python versions < 2.7.9. + As a consequence, clients may encounter various errors due to protocol versions mismatch. + Server Setup Example ~~~~~~~~~~~~~~~~~~~~ @@ -90,11 +94,10 @@ Prior Knowledge --------------- -It's possible that you as a client know that a particular server supports HTTP/2, and that you do not need to perform any of the negotiations described above. In that case, you may follow the steps in :ref:`starting-alpn`, ignoring all references to ALPN and NPN: there's no need to perform the upgrade dance described in :ref:`starting-upgrade`. +It's possible that you as a client know that a particular server supports HTTP/2, and that you do not need to perform any of the negotiations described above. In that case, you may follow the steps in :ref:`starting-alpn`, ignoring all references to ALPN: there's no need to perform the upgrade dance described in :ref:`starting-upgrade`. .. _RFC 7540: https://tools.ietf.org/html/rfc7540 .. _RFC 7540 Section 3.2: https://tools.ietf.org/html/rfc7540#section-3.2 .. _RFC 7540 Section 3.3: https://tools.ietf.org/html/rfc7540#section-3.3 -.. _NPN: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation .. _ALPN: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation .. _RFC 7230 Section 6.7: https://tools.ietf.org/html/rfc7230#section-6.7 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/examples/curio/curio-server.py new/h2-3.1.0/examples/curio/curio-server.py --- old/h2-3.0.1/examples/curio/curio-server.py 2017-04-03 09:57:46.000000000 +0200 +++ new/h2-3.1.0/examples/curio/curio-server.py 2019-01-22 18:41:43.000000000 +0100 @@ -12,7 +12,7 @@ import os import sys -from curio import Kernel, Event, spawn, socket, ssl +from curio import Event, spawn, socket, ssl, run import h2.config import h2.connection @@ -23,7 +23,7 @@ READ_CHUNK_SIZE = 8192 -def create_listening_ssl_socket(address, certfile, keyfile): +async def create_listening_ssl_socket(address, certfile, keyfile): """ Create and return a listening TLS socket on a given address. """ @@ -37,7 +37,7 @@ sock = socket.socket() sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock = ssl_context.wrap_socket(sock) + sock = await ssl_context.wrap_socket(sock) sock.bind(address) sock.listen() @@ -48,7 +48,7 @@ """ Create an HTTP/2 server at the given address. """ - sock = create_listening_ssl_socket(address, certfile, keyfile) + sock = await create_listening_ssl_socket(address, certfile, keyfile) print("Now listening on %s:%d" % address) async with sock: @@ -196,13 +196,11 @@ if __name__ == '__main__': host = sys.argv[2] if len(sys.argv) > 2 else "localhost" - kernel = Kernel(with_monitor=True) print("Try GETting:") print(" On OSX after 'brew install curl --with-c-ares --with-libidn --with-nghttp2 --with-openssl':") print("/usr/local/opt/curl/bin/curl --tlsv1.2 --http2 -k https://localhost:5000/bundle.js") print("Or open a browser to: https://localhost:5000/") print(" (Accept all the warnings)") - kernel.run(h2_server((host, 5000), - sys.argv[1], - "{}.crt.pem".format(host), - "{}.key".format(host))) + run(h2_server((host, 5000), sys.argv[1], + "{}.crt.pem".format(host), + "{}.key".format(host)), with_monitor=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/examples/eventlet/eventlet-server.py new/h2-3.1.0/examples/eventlet/eventlet-server.py --- old/h2-3.0.1/examples/eventlet/eventlet-server.py 2017-04-03 09:57:46.000000000 +0200 +++ new/h2-3.1.0/examples/eventlet/eventlet-server.py 2019-01-22 18:41:43.000000000 +0100 @@ -51,7 +51,7 @@ response_headers = ( (':status', '200'), ('content-type', 'application/json'), - ('content-length', len(data)), + ('content-length', str(len(data))), ('server', 'eventlet-h2'), ) self.conn.send_headers(stream_id, response_headers) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/examples/fragments/client_upgrade_fragment.py new/h2-3.1.0/examples/fragments/client_upgrade_fragment.py --- old/h2-3.0.1/examples/fragments/client_upgrade_fragment.py 2017-03-16 10:02:02.000000000 +0100 +++ new/h2-3.1.0/examples/fragments/client_upgrade_fragment.py 2019-01-22 18:41:43.000000000 +0100 @@ -38,7 +38,7 @@ b"GET / HTTP/1.1\r\n" + b"Host: localhost\r\n" + b"Upgrade: h2c\r\n" + - b"HTTP2-Settings: " + settings + "\r\n" + b"HTTP2-Settings: " + settings + b"\r\n" + b"\r\n" ) connection.sendall(request) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/examples/fragments/server_upgrade_fragment.py new/h2-3.1.0/examples/fragments/server_upgrade_fragment.py --- old/h2-3.0.1/examples/fragments/server_upgrade_fragment.py 2017-04-03 09:57:46.000000000 +0200 +++ new/h2-3.1.0/examples/fragments/server_upgrade_fragment.py 2019-01-22 18:41:43.000000000 +0100 @@ -45,14 +45,14 @@ data += connection.recv(8192) match = re.search(b'Upgrade: h2c\r\n', data) - if match is not None: + if match is None: raise RuntimeError("HTTP/2 upgrade not requested!") # We need to look for the HTTP2-Settings header field. Again, in production # code you shouldn't use regular expressions for this, but it's good enough # for the example. match = re.search(b'HTTP2-Settings: (\\S+)\r\n', data) - if match is not None: + if match is None: raise RuntimeError("HTTP2-Settings header field not present!") return match.group(1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/h2/__init__.py new/h2-3.1.0/h2/__init__.py --- old/h2-3.0.1/h2/__init__.py 2017-04-03 09:58:51.000000000 +0200 +++ new/h2-3.1.0/h2/__init__.py 2019-01-22 18:41:49.000000000 +0100 @@ -5,4 +5,4 @@ A HTTP/2 implementation. """ -__version__ = '3.0.1' +__version__ = '3.1.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/h2/connection.py new/h2-3.1.0/h2/connection.py --- old/h2-3.0.1/h2/connection.py 2017-04-03 09:57:49.000000000 +0200 +++ new/h2-3.1.0/h2/connection.py 2019-01-22 18:41:43.000000000 +0100 @@ -21,7 +21,7 @@ from .config import H2Configuration from .errors import ErrorCodes, _error_code_from_int from .events import ( - WindowUpdated, RemoteSettingsChanged, PingAcknowledged, + WindowUpdated, RemoteSettingsChanged, PingReceived, PingAckReceived, SettingsAcknowledged, ConnectionTerminated, PriorityUpdated, AlternativeServiceAvailable, UnknownFrameReceived ) @@ -33,7 +33,7 @@ from .frame_buffer import FrameBuffer from .settings import Settings, SettingCodes from .stream import H2Stream, StreamClosedBy -from .utilities import guard_increment_window +from .utilities import SizeLimitDict, guard_increment_window from .windows import WindowManager @@ -281,6 +281,9 @@ # The initial default value of SETTINGS_MAX_HEADER_LIST_SIZE. DEFAULT_MAX_HEADER_LIST_SIZE = 2**16 + # Keep in memory limited amount of results for streams closes + MAX_CLOSED_STREAMS = 2**16 + def __init__(self, config=None): self.state_machine = H2ConnectionStateMachine() self.streams = {} @@ -325,7 +328,7 @@ ) self.remote_settings = Settings(client=not self.config.client_side) - # The curent value of the connection flow control windows on the + # The current value of the connection flow control windows on the # connection. self.outbound_flow_control_window = ( self.remote_settings.initial_window_size @@ -355,7 +358,9 @@ # Also used to determine whether we should consider a frame received # while a stream is closed as either a stream error or a connection # error. - self._closed_streams = {} + self._closed_streams = SizeLimitDict( + size_limit=self.MAX_CLOSED_STREAMS + ) # The flow control window manager for the connection. self._inbound_flow_control_window_manager = WindowManager( @@ -909,16 +914,21 @@ frames = stream.increase_flow_control_window( increment ) + + self.config.logger.debug( + "Increase stream ID %d flow control window by %d", + stream_id, increment + ) else: self._inbound_flow_control_window_manager.window_opened(increment) f = WindowUpdateFrame(0) f.window_increment = increment frames = [f] - self.config.logger.debug( - "Increase stream ID %d flow control window by %d", - stream_id, increment - ) + self.config.logger.debug( + "Increase connection flow control window by %d", increment + ) + self._prepare_for_sending(frames) def push_stream(self, stream_id, promised_stream_id, request_headers): @@ -1327,7 +1337,7 @@ self._prepare_for_sending(frames) - def data_to_send(self, amt=None): + def data_to_send(self, amount=None): """ Returns some data for sending out of the internal data buffer. @@ -1336,19 +1346,19 @@ or less if that much data is not available. It does not perform any I/O, and so uses a different name. - :param amt: (optional) The maximum amount of data to return. If not + :param amount: (optional) The maximum amount of data to return. If not set, or set to ``None``, will return as much data as possible. - :type amt: ``int`` + :type amount: ``int`` :returns: A bytestring containing the data to send on the wire. :rtype: ``bytes`` """ - if amt is None: + if amount is None: data = self._data_to_send self._data_to_send = b'' return data else: - data = self._data_to_send[:amt] - self._data_to_send = self._data_to_send[amt:] + data = self._data_to_send[:amount] + self._data_to_send = self._data_to_send[amount:] return data def clear_outbound_data_buffer(self): @@ -1717,15 +1727,19 @@ flags = [] if 'ACK' in frame.flags: - evt = PingAcknowledged() - evt.ping_data = frame.opaque_data - events.append(evt) + evt = PingAckReceived() else: + evt = PingReceived() + + # automatically ACK the PING with the same 'opaque data' f = PingFrame(0) f.flags = {'ACK'} f.opaque_data = frame.opaque_data flags.append(f) + evt.ping_data = frame.opaque_data + events.append(evt) + return flags, events def _receive_rst_stream_frame(self, frame): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/h2/events.py new/h2-3.1.0/h2/events.py --- old/h2-3.0.1/h2/events.py 2017-04-03 09:57:46.000000000 +0200 +++ new/h2-3.1.0/h2/events.py 2019-01-22 18:41:43.000000000 +0100 @@ -356,22 +356,51 @@ ) +class PingReceived(Event): + """ + The PingReceived event is fired whenever a PING is received. It contains + the 'opaque data' of the PING frame. A ping acknowledgment with the same + 'opaque data' is automatically emitted after receiving a ping. + + .. versionadded:: 3.1.0 + """ + def __init__(self): + #: The data included on the ping. + self.ping_data = None + + def __repr__(self): + return "<PingReceived ping_data:%s>" % ( + _bytes_representation(self.ping_data), + ) + + class PingAcknowledged(Event): """ - The PingAcknowledged event is fired whenever a user-emitted PING is - acknowledged. This contains the data in the ACK'ed PING, allowing the - user to correlate PINGs and calculate RTT. + Same as PingAckReceived. + + .. deprecated:: 3.1.0 """ def __init__(self): #: The data included on the ping. self.ping_data = None def __repr__(self): - return "<PingAcknowledged ping_data:%s>" % ( + return "<PingAckReceived ping_data:%s>" % ( _bytes_representation(self.ping_data), ) +class PingAckReceived(PingAcknowledged): + """ + The PingAckReceived event is fired whenever a PING acknowledgment is + received. It contains the 'opaque data' of the PING+ACK frame, allowing the + user to correlate PINGs and calculate RTT. + + .. versionadded:: 3.1.0 + """ + pass + + class StreamEnded(Event): """ The StreamEnded event is fired whenever a stream is ended by a remote diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/h2/settings.py new/h2-3.1.0/h2/settings.py --- old/h2-3.0.1/h2/settings.py 2017-04-03 09:57:46.000000000 +0200 +++ new/h2-3.1.0/h2/settings.py 2019-01-22 18:41:43.000000000 +0100 @@ -15,6 +15,12 @@ from h2.errors import ErrorCodes from h2.exceptions import InvalidSettingsValueError +try: + from collections.abc import MutableMapping +except ImportError: # pragma: no cover + # Python 2.7 compatibility + from collections import MutableMapping + class SettingCodes(enum.IntEnum): """ @@ -49,6 +55,10 @@ #: and value in octets plus an overhead of 32 octets for each header field. MAX_HEADER_LIST_SIZE = SettingsFrame.MAX_HEADER_LIST_SIZE + #: This setting can be used to enable the connect protocol. To enable on a + #: client set this to 1. + ENABLE_CONNECT_PROTOCOL = SettingsFrame.ENABLE_CONNECT_PROTOCOL + def _setting_code_from_int(code): """ @@ -88,7 +98,7 @@ ) -class Settings(collections.MutableMapping): +class Settings(MutableMapping): """ An object that encapsulates HTTP/2 settings state. @@ -135,6 +145,7 @@ SettingCodes.ENABLE_PUSH: collections.deque([int(client)]), SettingCodes.INITIAL_WINDOW_SIZE: collections.deque([65535]), SettingCodes.MAX_FRAME_SIZE: collections.deque([16384]), + SettingCodes.ENABLE_CONNECT_PROTOCOL: collections.deque([0]), } if initial_values is not None: for key, value in initial_values.items(): @@ -243,6 +254,18 @@ def max_header_list_size(self, value): self[SettingCodes.MAX_HEADER_LIST_SIZE] = value + @property + def enable_connect_protocol(self): + """ + The current value of the :data:`ENABLE_CONNECT_PROTOCOL + <h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL>` setting. + """ + return self[SettingCodes.ENABLE_CONNECT_PROTOCOL] + + @enable_connect_protocol.setter + def enable_connect_protocol(self, value): + self[SettingCodes.ENABLE_CONNECT_PROTOCOL] = value + # Implement the MutableMapping API. def __getitem__(self, key): val = self._settings[key][0] @@ -292,7 +315,7 @@ return NotImplemented -def _validate_setting(setting, value): +def _validate_setting(setting, value): # noqa: C901 """ Confirms that a specific setting has a well-formed value. If the setting is invalid, returns an error code. Otherwise, returns 0 (NO_ERROR). @@ -309,5 +332,8 @@ elif setting == SettingCodes.MAX_HEADER_LIST_SIZE: if value < 0: return ErrorCodes.PROTOCOL_ERROR + elif setting == SettingCodes.ENABLE_CONNECT_PROTOCOL: + if value not in (0, 1): + return ErrorCodes.PROTOCOL_ERROR return 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/h2/stream.py new/h2-3.1.0/h2/stream.py --- old/h2-3.0.1/h2/stream.py 2017-04-03 09:57:46.000000000 +0200 +++ new/h2-3.1.0/h2/stream.py 2019-01-22 18:41:43.000000000 +0100 @@ -788,7 +788,7 @@ self.max_outbound_frame_size = None self.request_method = None - # The curent value of the outbound stream flow control window + # The current value of the outbound stream flow control window self.outbound_flow_control_window = outbound_window_size # The flow control manager. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/h2/utilities.py new/h2-3.1.0/h2/utilities.py --- old/h2-3.0.1/h2/utilities.py 2017-04-03 09:57:46.000000000 +0200 +++ new/h2-3.1.0/h2/utilities.py 2019-01-22 18:41:43.000000000 +0100 @@ -33,6 +33,7 @@ b':authority', u':authority', b':path', u':path', b':status', u':status', + b':protocol', u':protocol', ]) @@ -47,13 +48,19 @@ b':scheme', u':scheme', b':path', u':path', b':authority', u':authority', - b':method', u':method' + b':method', u':method', + b':protocol', u':protocol', ]) _RESPONSE_ONLY_HEADERS = frozenset([b':status', u':status']) +# A Set of pseudo headers that are only valid if the method is +# CONNECT, see RFC 8441 ยง 5 +_CONNECT_REQUEST_ONLY_HEADERS = frozenset([b':protocol', u':protocol']) + + if sys.version_info[0] == 2: # Python 2.X _WHITESPACE = frozenset(whitespace) else: # Python 3.3+ @@ -323,6 +330,7 @@ """ seen_pseudo_header_fields = set() seen_regular_header = False + method = None for header in headers: if _custom_startswith(header[0], b':', u':'): @@ -344,6 +352,12 @@ "Received custom pseudo-header field %s" % header[0] ) + if header[0] in (b':method', u':method'): + if not isinstance(header[1], bytes): + method = header[1].encode('utf-8') + else: + method = header[1] + else: seen_regular_header = True @@ -351,11 +365,12 @@ # Check the pseudo-headers we got to confirm they're acceptable. _check_pseudo_header_field_acceptability( - seen_pseudo_header_fields, hdr_validation_flags + seen_pseudo_header_fields, method, hdr_validation_flags ) def _check_pseudo_header_field_acceptability(pseudo_headers, + method, hdr_validation_flags): """ Given the set of pseudo-headers present in a header block and the @@ -394,6 +409,13 @@ "Encountered response-only headers %s" % invalid_request_headers ) + if method != b'CONNECT': + invalid_headers = pseudo_headers & _CONNECT_REQUEST_ONLY_HEADERS + if invalid_headers: + raise ProtocolError( + "Encountered connect-request-only headers %s" % + invalid_headers + ) def _validate_host_authority_header(headers): @@ -617,3 +639,22 @@ headers = _check_path_header(headers, hdr_validation_flags) return headers + + +class SizeLimitDict(collections.OrderedDict): + + def __init__(self, *args, **kwargs): + self._size_limit = kwargs.pop("size_limit", None) + super(SizeLimitDict, self).__init__(*args, **kwargs) + + self._check_size_limit() + + def __setitem__(self, key, value): + super(SizeLimitDict, self).__setitem__(key, value) + + self._check_size_limit() + + def _check_size_limit(self): + if self._size_limit is not None: + while len(self) > self._size_limit: + self.popitem(last=False) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/h2.egg-info/PKG-INFO new/h2-3.1.0/h2.egg-info/PKG-INFO --- old/h2-3.0.1/h2.egg-info/PKG-INFO 2017-04-03 10:07:26.000000000 +0200 +++ new/h2-3.1.0/h2.egg-info/PKG-INFO 2019-01-22 18:42:26.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: h2 -Version: 3.0.1 +Version: 3.1.0 Summary: HTTP/2 State-Machine based protocol implementation Home-page: http://hyper.rtfd.org Author: Cory Benfield @@ -76,6 +76,38 @@ Release History =============== + 3.1.0 (2019-01-22) + ------------------ + + API Changes (Backward-Incompatible) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + - ``h2.connection.H2Connection.data_to_send`` first and only argument ``amt`` + was renamed to ``amount``. + - Support for Python 3.3 has been removed. + + API Changes (Backward-Compatible) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + - ``h2.connection.H2Connection.send_data`` now supports ``data`` parameter + being a ``memoryview`` object. + - Refactor ping-related events: a ``h2.events.PingReceived`` event is fired + when a PING frame is received and a ``h2.events.PingAckReceived`` event is + fired when a PING frame with an ACK flag is received. + ``h2.events.PingAcknowledged`` is deprecated in favour of the identical + ``h2.events.PingAckReceived``. + - Added ``ENABLE_CONNECT_PROTOCOL`` to ``h2.settings.SettingCodes``. + - Support ``CONNECT`` requests with a ``:protocol`` pseudo header + thereby supporting RFC 8441. + - A limit to the number of closed streams kept in memory by the + connection is applied. It can be configured by + ``h2.connection.H2Connection.MAX_CLOSED_STREAMS``. + + Bugfixes + ~~~~~~~~ + + - Debug logging when stream_id is None is now fixed and no longer errors. + 3.0.1 (2017-04-03) ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/h2.egg-info/SOURCES.txt new/h2-3.1.0/h2.egg-info/SOURCES.txt --- old/h2-3.0.1/h2.egg-info/SOURCES.txt 2017-04-03 10:07:26.000000000 +0200 +++ new/h2-3.1.0/h2.egg-info/SOURCES.txt 2019-01-22 18:42:26.000000000 +0100 @@ -95,6 +95,7 @@ test/test_priority.py test/test_related_events.py test/test_rfc7838.py +test/test_rfc8441.py test/test_settings.py test/test_state_machines.py test/test_stream_reset.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/h2.egg-info/requires.txt new/h2-3.1.0/h2.egg-info/requires.txt --- old/h2-3.0.1/h2.egg-info/requires.txt 2017-04-03 10:07:26.000000000 +0200 +++ new/h2-3.1.0/h2.egg-info/requires.txt 2019-01-22 18:42:26.000000000 +0100 @@ -1,5 +1,5 @@ -hyperframe>=5.0, <6 -hpack>=2.3, <4 +hyperframe<6,>=5.2.0 +hpack<4,>=2.3 [:python_version == "2.7" or python_version == "3.3"] -enum34>=1.1.6, <2 +enum34<2,>=1.1.6 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/setup.py new/h2-3.1.0/setup.py --- old/h2-3.0.1/setup.py 2017-04-03 09:57:49.000000000 +0200 +++ new/h2-3.1.0/setup.py 2019-01-22 18:41:43.000000000 +0100 @@ -62,8 +62,8 @@ 'Programming Language :: Python :: Implementation :: PyPy', ], install_requires=[ - 'hyperframe>=5.0, <6', - 'hpack>=2.3, <4', + 'hyperframe>=5.2.0, <6', + 'hpack>=2.3,<4', ], extras_require={ ':python_version == "2.7" or python_version == "3.3"': ['enum34>=1.1.6, <2'], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/test/test_basic_logic.py new/h2-3.1.0/test/test_basic_logic.py --- old/h2-3.0.1/test/test_basic_logic.py 2017-04-03 09:57:46.000000000 +0200 +++ new/h2-3.1.0/test/test_basic_logic.py 2019-01-22 18:41:43.000000000 +0100 @@ -114,6 +114,23 @@ expected_data_frame_pad_length=0 ) + def test_sending_data_in_memoryview(self): + """ + Support memoryview for sending data. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers) + + # Clear the data, then send some data. + c.clear_outbound_data_buffer() + events = c.send_data(1, memoryview(b'some data')) + assert not events + data_to_send = c.data_to_send() + assert ( + data_to_send == b'\x00\x00\t\x00\x00\x00\x00\x00\x01some data' + ) + def test_sending_data_with_padding(self): """ Single data frames with padding are encoded correctly. @@ -1081,7 +1098,12 @@ c.clear_outbound_data_buffer() events = c.receive_data(sent_frame.serialize()) - assert not events + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.PingReceived) + assert event.ping_data == ping_data + assert c.data_to_send() == expected_data def test_receiving_settings_frame_event(self, frame_factory): @@ -1324,7 +1346,7 @@ def test_receiving_ping_acknowledgement(self, frame_factory): """ - Receiving a PING acknolwedgement fires a PingAcknolwedged event. + Receiving a PING acknowledgement fires a PingAckReceived event. """ c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) @@ -1339,7 +1361,8 @@ assert len(events) == 1 event = events[0] - assert isinstance(event, h2.events.PingAcknowledged) + assert isinstance(event, h2.events.PingAckReceived) + assert isinstance(event, h2.events.PingAcknowledged) # deprecated assert event.ping_data == ping_data def test_stream_ended_remotely(self, frame_factory): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/test/test_events.py new/h2-3.1.0/test/test_events.py --- old/h2-3.0.1/test/test_events.py 2017-04-03 09:57:46.000000000 +0200 +++ new/h2-3.1.0/test/test_events.py 2019-01-22 18:41:43.000000000 +0100 @@ -213,14 +213,23 @@ "new_value=32768)}>" ) - def test_pingacknowledged_repr(self): + def test_pingreceived_repr(self): """ - PingAcknowledged has a useful debug representation. + PingReceived has a useful debug representation. """ - e = h2.events.PingAcknowledged() + e = h2.events.PingReceived() e.ping_data = b'abcdefgh' - assert repr(e) == "<PingAcknowledged ping_data:6162636465666768>" + assert repr(e) == "<PingReceived ping_data:6162636465666768>" + + def test_pingackreceived_repr(self): + """ + PingAckReceived has a useful debug representation. + """ + e = h2.events.PingAckReceived() + e.ping_data = b'abcdefgh' + + assert repr(e) == "<PingAckReceived ping_data:6162636465666768>" def test_streamended_repr(self): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/test/test_invalid_headers.py new/h2-3.1.0/test/test_invalid_headers.py --- old/h2-3.0.1/test/test_invalid_headers.py 2017-04-03 09:57:49.000000000 +0200 +++ new/h2-3.1.0/test/test_invalid_headers.py 2019-01-22 18:41:43.000000000 +0100 @@ -54,6 +54,7 @@ base_request_headers + [('name', 'value with trailing space ')], [header for header in base_request_headers if header[0] != ':authority'], + [(':protocol', 'websocket')] + base_request_headers, ] server_config = h2.config.H2Configuration( client_side=False, header_encoding='utf-8' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/test/test_rfc8441.py new/h2-3.1.0/test/test_rfc8441.py --- old/h2-3.0.1/test/test_rfc8441.py 1970-01-01 01:00:00.000000000 +0100 +++ new/h2-3.1.0/test/test_rfc8441.py 2019-01-22 18:41:43.000000000 +0100 @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +""" +test_rfc8441 +~~~~~~~~~~~~ + +Test the RFC 8441 extended connect request support. +""" +import h2.config +import h2.connection +import h2.events + + +class TestRFC8441(object): + """ + Tests that the client supports sending an extended connect request + and the server supports receiving it. + """ + + def test_can_send_headers(self, frame_factory): + headers = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'CONNECT'), + (b':protocol', b'websocket'), + (b'user-agent', b'someua/0.0.1'), + ] + + client = h2.connection.H2Connection() + client.initiate_connection() + client.send_headers(stream_id=1, headers=headers) + + server = h2.connection.H2Connection( + config=h2.config.H2Configuration(client_side=False) + ) + events = server.receive_data(client.data_to_send()) + event = events[1] + assert isinstance(event, h2.events.RequestReceived) + assert event.stream_id == 1 + assert event.headers == headers diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/test/test_settings.py new/h2-3.1.0/test/test_settings.py --- old/h2-3.0.1/test/test_settings.py 2017-04-03 09:57:46.000000000 +0200 +++ new/h2-3.1.0/test/test_settings.py 2019-01-22 18:41:43.000000000 +0100 @@ -12,7 +12,9 @@ import h2.settings from hypothesis import given, assume -from hypothesis.strategies import integers +from hypothesis.strategies import ( + integers, booleans, fixed_dictionaries, builds +) class TestSettings(object): @@ -29,6 +31,7 @@ assert s[h2.settings.SettingCodes.ENABLE_PUSH] == 1 assert s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] == 65535 assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16384 + assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 0 def test_settings_defaults_server(self): """ @@ -40,6 +43,7 @@ assert s[h2.settings.SettingCodes.ENABLE_PUSH] == 0 assert s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] == 65535 assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16384 + assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 0 @pytest.mark.parametrize('client', [True, False]) def test_can_set_initial_values(self, client): @@ -52,6 +56,7 @@ h2.settings.SettingCodes.MAX_FRAME_SIZE: 16388, h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100, h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 2**16, + h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL: 1, } s = h2.settings.Settings(client=client, initial_values=overrides) @@ -61,6 +66,7 @@ assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16388 assert s[h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS] == 100 assert s[h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE] == 2**16 + assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 1 @pytest.mark.parametrize( 'setting,value', @@ -72,6 +78,7 @@ (h2.settings.SettingCodes.MAX_FRAME_SIZE, 1), (h2.settings.SettingCodes.MAX_FRAME_SIZE, 2**30), (h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE, -1), + (h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL, -1), ] ) def test_cannot_set_invalid_initial_values(self, setting, value): @@ -106,6 +113,7 @@ h2.settings.SettingCodes.ENABLE_PUSH: 0, h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 60, h2.settings.SettingCodes.MAX_FRAME_SIZE: 16385, + h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL: 1, } s.update(new_settings) @@ -169,16 +177,16 @@ Length is related only to the number of keys. """ s = h2.settings.Settings(client=True) - assert len(s) == 4 + assert len(s) == 5 s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 8000 - assert len(s) == 4 + assert len(s) == 5 s.acknowledge() - assert len(s) == 4 + assert len(s) == 5 del s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] - assert len(s) == 3 + assert len(s) == 4 def test_new_values_work(self): """ @@ -232,6 +240,9 @@ assert s.max_frame_size == s[h2.settings.SettingCodes.MAX_FRAME_SIZE] assert s.max_concurrent_streams == 2**32 + 1 # A sensible default. assert s.max_header_list_size is None + assert s.enable_connect_protocol == s[ + h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL + ] def test_settings_setters(self): """ @@ -245,6 +256,7 @@ s.max_frame_size = 16385 s.max_concurrent_streams = 4 s.max_header_list_size = 2**16 + s.enable_connect_protocol = 1 s.acknowledge() assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 0 @@ -253,6 +265,7 @@ assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16385 assert s[h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS] == 4 assert s[h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE] == 2**16 + assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 1 @given(integers()) def test_cannot_set_invalid_values_for_enable_push(self, val): @@ -361,131 +374,97 @@ with pytest.raises(KeyError): s[h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE] - -class TestSettingsEquality(object): - """ - A class defining tests for the standard implementation of == and != . - """ - - def an_instance(self): + @given(integers()) + def test_cannot_set_invalid_values_for_enable_connect_protocol(self, val): """ - Return an instance of the class under test. Each call to this method - must return a different object. All objects returned must be equal to - each other. + SETTINGS_ENABLE_CONNECT_PROTOCOL only allows two values: 0, 1. """ - overrides = { - h2.settings.SettingCodes.HEADER_TABLE_SIZE: 0, - h2.settings.SettingCodes.MAX_FRAME_SIZE: 16384, - h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 4, - h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 2**16, - } - return h2.settings.Settings(client=True, initial_values=overrides) + assume(val not in (0, 1)) + s = h2.settings.Settings() - def another_instance(self): - """ - Return an instance of the class under test. Each call to this method - must return a different object. The objects must not be equal to the - objects returned by an_instance. They may or may not be equal to - each other (they will not be compared against each other). - """ - overrides = { - h2.settings.SettingCodes.HEADER_TABLE_SIZE: 8080, - h2.settings.SettingCodes.MAX_FRAME_SIZE: 16388, - h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100, - h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 2**16, - } - return h2.settings.Settings(client=False, initial_values=overrides) + with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e: + s.enable_connect_protocol = val - def test_identical_eq(self): - """ - An object compares equal to itself using the == operator. - """ - o = self.an_instance() - assert (o == o) + s.acknowledge() + assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR + assert s.enable_connect_protocol == 0 - def test_identical_ne(self): - """ - An object doesn't compare not equal to itself using the != operator. - """ - o = self.an_instance() - assert not (o != o) + with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e: + s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] = val - def test_same_eq(self): - """ - Two objects that are equal to each other compare equal to each other - using the == operator. - """ - a = self.an_instance() - b = self.an_instance() - assert (a == b) + s.acknowledge() + assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR + assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 0 - def test_same_ne(self): - """ - Two objects that are equal to each other do not compare not equal to - each other using the != operator. - """ - a = self.an_instance() - b = self.an_instance() - assert not (a != b) - def test_different_eq(self): +class TestSettingsEquality(object): + """ + A class defining tests for the standard implementation of == and != . + """ + + SettingsStrategy = builds( + h2.settings.Settings, + client=booleans(), + initial_values=fixed_dictionaries({ + h2.settings.SettingCodes.HEADER_TABLE_SIZE: + integers(0, 2**32 - 1), + h2.settings.SettingCodes.ENABLE_PUSH: integers(0, 1), + h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: + integers(0, 2**31 - 1), + h2.settings.SettingCodes.MAX_FRAME_SIZE: + integers(2**14, 2**24 - 1), + h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: + integers(0, 2**32 - 1), + h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: + integers(0, 2**32 - 1), + }) + ) + + @given(settings=SettingsStrategy) + def test_equality_reflexive(self, settings): """ - Two objects that are not equal to each other do not compare equal to - each other using the == operator. + An object compares equal to itself using the == operator and the != + operator. """ - a = self.an_instance() - b = self.another_instance() - assert not (a == b) + assert (settings == settings) + assert not (settings != settings) - def test_different_ne(self): + @given(settings=SettingsStrategy, o_settings=SettingsStrategy) + def test_equality_multiple(self, settings, o_settings): """ - Two objects that are not equal to each other compare not equal to each - other using the != operator. + Two objects compare themselves using the == operator and the != + operator. """ - a = self.an_instance() - b = self.another_instance() - assert (a != b) + if settings == o_settings: + assert settings == o_settings + assert not (settings != o_settings) + else: + assert settings != o_settings + assert not (settings == o_settings) - def test_another_type_eq(self): + @given(settings=SettingsStrategy) + def test_another_type_equality(self, settings): """ The object does not compare equal to an object of an unrelated type (which does not implement the comparison) using the == operator. """ - a = self.an_instance() - b = object() - assert not (a == b) + obj = object() + assert (settings != obj) + assert not (settings == obj) - def test_another_type_ne(self): + @given(settings=SettingsStrategy) + def test_delegated_eq(self, settings): """ - The object compares not equal to an object of an unrelated type (which - does not implement the comparison) using the != operator. - """ - a = self.an_instance() - b = object() - assert (a != b) - - def test_delegated_eq(self): - """ - The result of comparison using == is delegated to the right-hand - operand if it is of an unrelated type. + The result of comparison is delegated to the right-hand operand if + it is of an unrelated type. """ class Delegate(object): def __eq__(self, other): return [self] - a = self.an_instance() - b = Delegate() - assert (a == b) == [b] - - def test_delegate_ne(self): - """ - The result of comparison using != is delegated to the right-hand - operand if it is of an unrelated type. - """ - class Delegate(object): def __ne__(self, other): return [self] - a = self.an_instance() - b = Delegate() - assert (a != b) == [b] + delg = Delegate() + assert (settings == delg) == [delg] + assert (settings != delg) == [delg] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/test/test_utility_functions.py new/h2-3.1.0/test/test_utility_functions.py --- old/h2-3.0.1/test/test_utility_functions.py 2017-04-03 09:57:46.000000000 +0200 +++ new/h2-3.1.0/test/test_utility_functions.py 2019-01-22 18:41:43.000000000 +0100 @@ -12,7 +12,7 @@ import h2.errors import h2.events import h2.exceptions -from h2.utilities import extract_method_header +from h2.utilities import SizeLimitDict, extract_method_header # These tests require a non-list-returning range function. try: @@ -176,3 +176,51 @@ ) def test_extract_header_method(self, headers): assert extract_method_header(headers) == b'GET' + + +def test_size_limit_dict_limit(): + dct = SizeLimitDict(size_limit=2) + + dct[1] = 1 + dct[2] = 2 + + assert len(dct) == 2 + assert dct[1] == 1 + assert dct[2] == 2 + + dct[3] = 3 + + assert len(dct) == 2 + assert dct[2] == 2 + assert dct[3] == 3 + assert 1 not in dct + + +def test_size_limit_dict_limit_init(): + initial_dct = { + 1: 1, + 2: 2, + 3: 3, + } + + dct = SizeLimitDict(initial_dct, size_limit=2) + + assert len(dct) == 2 + + +def test_size_limit_dict_no_limit(): + dct = SizeLimitDict(size_limit=None) + + dct[1] = 1 + dct[2] = 2 + + assert len(dct) == 2 + assert dct[1] == 1 + assert dct[2] == 2 + + dct[3] = 3 + + assert len(dct) == 3 + assert dct[1] == 1 + assert dct[2] == 2 + assert dct[3] == 3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/test_requirements.txt new/h2-3.1.0/test_requirements.txt --- old/h2-3.0.1/test_requirements.txt 2017-03-31 17:30:40.000000000 +0200 +++ new/h2-3.1.0/test_requirements.txt 2019-01-22 18:41:43.000000000 +0100 @@ -1,5 +1,5 @@ -pytest==3.0.7 -pytest-cov==2.4.0 -coverage==4.3.4 -pytest-xdist==1.15.0 -hypothesis==3.7.0 +pytest==3.4.2 +pytest-cov==2.5.1 +coverage==4.5.1 +pytest-xdist==1.22.2 +hypothesis diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/tox.ini new/h2-3.1.0/tox.ini --- old/h2-3.0.1/tox.ini 2017-04-03 09:57:46.000000000 +0200 +++ new/h2-3.1.0/tox.ini 2019-01-22 18:41:43.000000000 +0100 @@ -1,5 +1,5 @@ [tox] -envlist = py27, py33, py34, py35, py36, pypy, lint, packaging, docs +envlist = py27, py34, py35, py36, py37, pypy, lint, packaging, docs [testenv] deps= -r{toxinidir}/test_requirements.txt @@ -15,22 +15,17 @@ # This is a validation test that confirms that Twisted's test cases haven't # broken. deps = - # [tls,http2] syntax doesn't work here so we enumerate all dependencies. - git+https://github.com/twisted/twisted - pyopenssl - service_identity - idna - priority + twisted[tls, http2, conch] sphinx commands = python -m twisted.trial --reporter=text twisted [testenv:lint] -basepython=python3.4 -deps = flake8==3.3.0 +basepython=python3.7 +deps = flake8==3.5.0 commands = flake8 --max-complexity 10 h2 test [testenv:docs] -basepython=python3.5 +basepython=python3.7 deps = sphinx==1.4.9 changedir = {toxinidir}/docs whitelist_externals = rm @@ -40,15 +35,15 @@ [testenv:graphs] basepython=python2.7 -deps = graphviz==0.6 +deps = graphviz==0.8.2 commands = python visualizer/visualize.py -i docs/source/_static [testenv:packaging] -basepython=python2.7 +basepython=python3.7 deps = - check-manifest==0.35 - readme_renderer==17.2 + check-manifest==0.36 + readme-renderer==17.3 commands = check-manifest python setup.py check --metadata --restructuredtext --strict diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-3.0.1/utils/backport.sh new/h2-3.1.0/utils/backport.sh --- old/h2-3.0.1/utils/backport.sh 2017-04-03 09:57:46.000000000 +0200 +++ new/h2-3.1.0/utils/backport.sh 2019-01-22 18:41:43.000000000 +0100 @@ -16,6 +16,7 @@ if ! git pull --ff-only; then echo "Unable to update $TARGET_BRANCH" exit 2 +fi if ! git checkout -b "$PR_BRANCH"; then echo "Failed to open new branch $PR_BRANCH"