diff --git a/CHANGELOG.md b/CHANGELOG.md index 6913dac0..3614d251 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Change Log +## [v2.0.0-beta.3](https://github.com/ably/ably-python/tree/v2.0.0-beta.3) + +This new beta release of the ably-python realtime client implements a number of new features to improve the stability of realtime connections, allowing the client to reconnect during a temporary disconnection, use fallback hosts when necessary, and catch up on messages missed while the client was disconnected. + +[Full Changelog](https://github.com/ably/ably-python/compare/v2.0.0-beta.2...v2.0.0-beta.3) + +- Resend protocol messages for pending channels upon resume [\#347](https://github.com/ably/ably-python/issues/347) +- Attempt to resume connection when disconnected unexpectedly [\#346](https://github.com/ably/ably-python/issues/346) +- Handle `CONNECTED` messages once connected [\#345](https://github.com/ably/ably-python/issues/345) +- Implement `maxIdleInterval` [\#344](https://github.com/ably/ably-python/issues/344) +- Implement realtime connectivity check [\#343](https://github.com/ably/ably-python/issues/343) +- Use fallback realtime hosts when encountering an appropriate error [\#342](https://github.com/ably/ably-python/issues/342) +- Add `fallbackHosts` client option for realtime clients [\#341](https://github.com/ably/ably-python/issues/341) +- Implement `connectionStateTtl` [\#340](https://github.com/ably/ably-python/issues/340) +- Implement `disconnectedRetryTimeout` [\#339](https://github.com/ably/ably-python/issues/339) +- Handle recoverable connection opening errors [\#338](https://github.com/ably/ably-python/issues/338) +- Implement `channelRetryTimeout` [\#442](https://github.com/ably/ably-python/issues/436) +- Queue protocol messages when connection state is `CONNECTING` or `DISCONNECTED` [\#418](https://github.com/ably/ably-python/issues/418) +- Propagate connection interruptions to realtime channels [\#417](https://github.com/ably/ably-python/issues/417) +- Spec compliance: `Realtime.connect` should be sync [\#413](https://github.com/ably/ably-python/issues/413) +- Emit `update` event on additional `ATTACHED` message [\#386](https://github.com/ably/ably-python/issues/386) +- Set the `ATTACH_RESUME` flag on unclean attach [\#385](https://github.com/ably/ably-python/issues/385) +- Handle fatal resume error [\#384](https://github.com/ably/ably-python/issues/384) +- Handle invalid resume response [\#383](https://github.com/ably/ably-python/issues/383) +- Handle clean resume response [\#382](https://github.com/ably/ably-python/issues/382) +- Send resume query param when reconnecting within `connectionStateTtl` [\#381](https://github.com/ably/ably-python/issues/381) +- Immediately reattempt connection when unexpectedly disconnected [\#380](https://github.com/ably/ably-python/issues/380) +- Clear connection state when `connectionStateTtl` elapsed [\#379](https://github.com/ably/ably-python/issues/379) +- Refactor websocket async tasks into WebSocketTransport class [\#373](https://github.com/ably/ably-python/issues/373) +- Send version transport param [\#368](https://github.com/ably/ably-python/issues/368) +- Clear `Connection.error_reason` when `Connection.connect` is called [\#367](https://github.com/ably/ably-python/issues/367) + ## [v2.0.0-beta.2](https://github.com/ably/ably-python/tree/v2.0.0-beta.2) [Full Changelog](https://github.com/ably/ably-python/compare/v2.0.0-beta.1...v2.0.0-beta.2) diff --git a/README.md b/README.md index 919b3331..585b71ee 100644 --- a/README.md +++ b/README.md @@ -197,16 +197,51 @@ await client.time() await client.close() ``` -### Using the Realtime API -The python realtime client currently only supports basic authentication. +## Realtime client (beta) + +We currently have a preview version of our first ever Python realtime client available for beta testing. +Currently the realtime client only supports authentication using basic auth and message subscription. +Realtime publishing, token authentication, and realtime presence are upcoming but not yet supported. +Check out the [roadmap](./roadmap.md) to see our plan for the realtime client. + +### Installing the realtime client + +The beta realtime client is available as a [PyPI](https://pypi.org/project/ably/2.0.0b3/) package. + +``` +pip install ably==2.0.0b3 +``` + +### Using the realtime client + #### Creating a client ```python from ably import AblyRealtime async def main(): + # Create a client using an Ably API key client = AblyRealtime('api:key') ``` +#### Subscribe to connection state changes + +```python +# subscribe to 'failed' connection state +client.connection.on('failed', listener) + +# subscribe to 'connected' connection state +client.connection.on('connected', listener) + +# subscribe to all connection state changes +client.connection.on(listener) + +# wait for the next state change +await client.connection.once_async() + +# wait for the connection to become connected +await client.connection.once_async('connected') +``` + #### Get a realtime channel instance ```python channel = client.channels.get('channel_name') @@ -234,18 +269,6 @@ channel.unsubscribe('event', listener) channel.unsubscribe() ``` -#### Subscribe to connection state change -```python -# subscribe to 'failed' connection state -client.connection.on('failed', listener) - -# subscribe to 'connected' connection state -client.connection.on('connected', listener) - -# subscribe to all connection state changes -client.connection.on(listener) -``` - #### Attach to a channel ```python await channel.attach() @@ -259,7 +282,7 @@ await channel.detach() ```python # Establish a realtime connection. # Explicitly calling connect() is unnecessary unless the autoConnect attribute of the ClientOptions object is false -await client.connect() +client.connect() # Close a connection await client.close() diff --git a/ably/__init__.py b/ably/__init__.py index 1d0d927c..88c0f542 100644 --- a/ably/__init__.py +++ b/ably/__init__.py @@ -15,4 +15,4 @@ logger.addHandler(logging.NullHandler()) api_version = '1.2' -lib_version = '2.0.0-beta.2' +lib_version = '2.0.0-beta.3' diff --git a/pyproject.toml b/pyproject.toml index b0044934..5d16edbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ably" -version = "2.0.0-beta.2" +version = "2.0.0-beta.3" description = "Python REST and Realtime client library SDK for Ably realtime messaging service" license = "Apache-2.0" authors = ["Ably "] diff --git a/test/ably/rest/resthttp_test.py b/test/ably/rest/resthttp_test.py index ad1fe043..bab45344 100644 --- a/test/ably/rest/resthttp_test.py +++ b/test/ably/rest/resthttp_test.py @@ -78,26 +78,19 @@ def make_url(host): expected_hosts_set.remove(prep_request_tuple[0].headers.get('host')) await ably.close() - @pytest.mark.skip(reason="skipped due to httpx changes") + @respx.mock async def test_no_host_fallback_nor_retries_if_custom_host(self): custom_host = 'example.org' ably = AblyRest(token="foo", rest_host=custom_host) - custom_url = "%s://%s:%d/" % ( - ably.http.preferred_scheme, - custom_host, - ably.http.preferred_port) + mock_route = respx.get("https://example.org").mock(side_effect=httpx.RequestError('')) - with mock.patch('httpx.Request', wraps=httpx.Request) as request_mock: - with mock.patch('httpx.AsyncClient.send', side_effect=httpx.RequestError('')) as send_mock: - with pytest.raises(httpx.RequestError): - await ably.http.make_request('GET', '/', skip_auth=True) + with pytest.raises(httpx.RequestError): + await ably.http.make_request('GET', '/', skip_auth=True) + + assert mock_route.call_count == 1 + assert respx.calls.call_count == 1 - assert send_mock.call_count == 1 - assert request_mock.call_args == mock.call(mock.ANY, - custom_url, - content=mock.ANY, - headers=mock.ANY) await ably.close() # RSC15f @@ -137,7 +130,7 @@ async def side_effect(*args, **kwargs): await client.aclose() await ably.close() - @pytest.mark.skip(reason="skipped due to httpx changes") + @respx.mock async def test_no_retry_if_not_500_to_599_http_code(self): default_host = Options().get_rest_host() ably = AblyRest(token="foo") @@ -147,20 +140,16 @@ async def test_no_retry_if_not_500_to_599_http_code(self): default_host, ably.http.preferred_port) - def raise_ably_exception(*args, **kwargs): - raise AblyException(message="", status_code=600, code=50500) + mock_response = httpx.Response(600, json={'message': "", 'status_code': 600, 'code': 50500}) - with mock.patch('httpx.Request', wraps=httpx.Request) as request_mock: - with mock.patch('ably.util.exceptions.AblyException.raise_for_response', - side_effect=raise_ably_exception) as send_mock: - with pytest.raises(AblyException): - await ably.http.make_request('GET', '/', skip_auth=True) + mock_route = respx.get(default_url).mock(return_value=mock_response) + + with pytest.raises(AblyException): + await ably.http.make_request('GET', '/', skip_auth=True) + + assert mock_route.call_count == 1 + assert respx.calls.call_count == 1 - assert send_mock.call_count == 1 - assert request_mock.call_args == mock.call(mock.ANY, - default_url, - content=mock.ANY, - headers=mock.ANY) await ably.close() async def test_500_errors(self):