Skip to content

Commit

Permalink
Merge pull request #423 from ably/integration/realtime
Browse files Browse the repository at this point in the history
Merge `integration/realtime` into `main`
  • Loading branch information
owenpearson authored Jun 29, 2023
2 parents 8a7c40e + 7c4c9c6 commit 2e40e1b
Show file tree
Hide file tree
Showing 58 changed files with 4,598 additions and 579 deletions.
15 changes: 15 additions & 0 deletions .ably/capabilities.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ compliance:
Protocol:
JSON:
MessagePack:
Realtime:
Authentication:
Get Confirmed Client Identifier:
Channel:
Attach:
Retry Timeout:
State Events:
Subscribe:
Connection:
Disconnected Retry Timeout:
Lifecycle Control:
Ping:
State Events:
Suspended Retry Timeout:
REST:
Authentication:
Authorize:
Expand Down Expand Up @@ -54,6 +68,7 @@ compliance:
Remove:
Save:
Publish:
Request Identifiers:
Request Timeout:
Service:
Get Time:
Expand Down
171 changes: 166 additions & 5 deletions CHANGELOG.md

Large diffs are not rendered by default.

64 changes: 32 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ ably-python

## Overview

This is a Python client library for Ably. The library currently targets the [Ably 1.1 client library specification](https://ably.com/docs/client-lib-development-guide/features).
This is a Python client library for Ably. The library currently targets the [Ably 2.0 client library specification](https://sdk.ably.com/builds/ably/specification/main/features/).

## Running example

Expand Down Expand Up @@ -54,10 +54,11 @@ introduced by version 1.2.0.

## Usage

### Using the Rest API

All examples assume a client and/or channel has been created in one of the following ways:

With closing the client manually:

```python
from ably import AblyRest

Expand Down Expand Up @@ -196,27 +197,9 @@ await client.time()
await client.close()
```

## Realtime client (beta)

We currently have a preview version of our first ever Python realtime client available for beta testing.
Currently the realtime client supports basic and token-based authentication and message subscription.
Realtime publishing and realtime presence are upcoming but not yet supported.
The 2.0 beta version contains a few minor breaking changes, removing already soft-deprecated features from the 1.x branch.
Most users will not be affected by these changes since the library was already warning that these features were deprecated.
For information on how to migrate, please consult the [migration guide](./UPDATING.md).
Check out the [roadmap](./roadmap.md) to see our plan for the realtime client.

### Installing the realtime client
## Using the realtime client

The beta realtime client is available as a [PyPI](https://pypi.org/project/ably/2.0.0b6/) package.

```
pip install ably==2.0.0b6
```

### Using the realtime client

#### Creating a client using API key
### Create a client using an API key

```python
from ably import AblyRealtime
Expand All @@ -227,7 +210,7 @@ async def main():
client = AblyRealtime('api:key')
```

#### Create a client using an token auth
### Create a client using token auth

```python
# Create a client using kwargs, which must contain at least one auth option
Expand All @@ -241,7 +224,24 @@ async def main():
client = AblyRealtime(token_details=token_details)
```

#### Subscribe to connection state changes
### 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')
```

```python
# subscribe to 'failed' connection state
Expand All @@ -260,13 +260,13 @@ await client.connection.once_async()
await client.connection.once_async('connected')
```

#### Get a realtime channel instance
### Get a realtime channel instance

```python
channel = client.channels.get('channel_name')
```

#### Subscribing to messages on a channel
### Subscribing to messages on a channel

```python

Expand All @@ -282,7 +282,7 @@ await channel.subscribe(listener)

Note that `channel.subscribe` is a coroutine function and will resolve when the channel is attached

#### Unsubscribing from messages on a channel
### Unsubscribing from messages on a channel

```python
# unsubscribe the listener from the channel
Expand All @@ -292,19 +292,19 @@ channel.unsubscribe('event', listener)
channel.unsubscribe()
```

#### Attach to a channel
### Attach to a channel

```python
await channel.attach()
```

#### Detach from a channel
### Detach from a channel

```python
await channel.detach()
```

#### Managing a connection
### Managing a connection

```python
# Establish a realtime connection.
Expand Down Expand Up @@ -332,8 +332,8 @@ for the set of versions that currently undergo CI testing.

## Known Limitations

Currently, this SDK only supports [Ably REST](https://ably.com/docs/rest), although we currently have [a subscribe-only realtime client in beta](#Realtime-client-beta).
You can also use the [MQTT adapter](https://ably.com/docs/mqtt) to implement [Ably's Realtime](https://ably.com/docs/realtime) features using Python.
Currently, this SDK only supports [Ably REST](https://ably.com/docs/rest) and realtime message subscription as documented above.
However, you can use the [MQTT adapter](https://ably.com/docs/mqtt) to implement [Ably's Realtime](https://ably.com/docs/realtime) features using Python.

See [our roadmap for this SDK](roadmap.md) for more information.

Expand Down
5 changes: 3 additions & 2 deletions ably/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from ably.rest.rest import AblyRest
from ably.realtime.realtime import AblyRealtime
from ably.rest.auth import Auth
from ably.rest.push import Push
from ably.types.capability import Capability
Expand All @@ -13,5 +14,5 @@
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())

api_version = '1.2'
lib_version = '1.2.2'
api_version = '3'
lib_version = '2.0.0'
35 changes: 16 additions & 19 deletions ably/http/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from ably.rest.auth import Auth
from ably.http.httputils import HttpUtils
from ably.transport.defaults import Defaults
from ably.util.exceptions import AblyException, AblyAuthException
from ably.util.exceptions import AblyException
from ably.util.helper import is_token_error

log = logging.getLogger(__name__)

Expand All @@ -25,16 +26,16 @@ async def wrapper(rest, *args, **kwargs):
auth = rest.auth
token_details = auth.token_details
if token_details and auth.time_offset is not None and auth.token_details_has_expired():
await rest.reauth()
await auth.authorize()
retried = True
else:
retried = False

try:
return await func(rest, *args, **kwargs)
except AblyException as e:
if 40140 <= e.code < 40150 and not retried:
await rest.reauth()
if is_token_error(e) and not retried:
await auth.authorize()
return await func(rest, *args, **kwargs)

raise e
Expand All @@ -43,18 +44,19 @@ async def wrapper(rest, *args, **kwargs):


class Request:
def __init__(self, method='GET', url='/', headers=None, body=None,
def __init__(self, method='GET', url='/', version=None, headers=None, body=None,
skip_auth=False, raise_on_error=True):
self.__method = method
self.__headers = headers or {}
self.__body = body
self.__skip_auth = skip_auth
self.__url = url
self.__version = version
self.raise_on_error = raise_on_error

def with_relative_url(self, relative_url):
url = urljoin(self.url, relative_url)
return Request(self.method, url, self.headers, self.body,
return Request(self.method, url, self.version, self.headers, self.body,
self.skip_auth, self.raise_on_error)

@property
Expand All @@ -77,6 +79,10 @@ def body(self):
def skip_auth(self):
return self.__skip_auth

@property
def version(self):
return self.__version


class Response:
"""
Expand Down Expand Up @@ -134,18 +140,9 @@ def dump_body(self, body):
else:
return json.dumps(body, separators=(',', ':'))

async def reauth(self):
try:
await self.auth.authorize()
except AblyAuthException as e:
if e.code == 40101:
e.message = ("The provided token is not renewable and there is"
" no means to generate a new token")
raise e

def get_rest_hosts(self):
hosts = self.options.get_rest_hosts()
host = self.__host
host = self.__host or self.options.fallback_realtime_host
if host is None:
return hosts

Expand All @@ -160,16 +157,16 @@ def get_rest_hosts(self):
return hosts

@reauth_if_expired
async def make_request(self, method, path, headers=None, body=None,
async def make_request(self, method, path, version=None, headers=None, body=None,
skip_auth=False, timeout=None, raise_on_error=True):

if body is not None and type(body) not in (bytes, str):
body = self.dump_body(body)

if body:
all_headers = HttpUtils.default_post_headers(self.options.use_binary_protocol)
all_headers = HttpUtils.default_post_headers(self.options.use_binary_protocol, version=version)
else:
all_headers = HttpUtils.default_get_headers(self.options.use_binary_protocol)
all_headers = HttpUtils.default_get_headers(self.options.use_binary_protocol, version=version)

params = HttpUtils.get_query_params(self.options)

Expand Down
20 changes: 13 additions & 7 deletions ably/http/httputils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,17 @@ class HttpUtils:
}

@staticmethod
def default_get_headers(binary=False):
headers = {
"X-Ably-Version": ably.api_version,
"Ably-Agent": 'ably-python/%s python/%s' % (ably.lib_version, platform.python_version())
}
def default_get_headers(binary=False, version=None):
headers = HttpUtils.default_headers(version=version)
if binary:
headers["Accept"] = HttpUtils.mime_types['binary']
else:
headers["Accept"] = HttpUtils.mime_types['json']
return headers

@staticmethod
def default_post_headers(binary=False):
headers = HttpUtils.default_get_headers(binary=binary)
def default_post_headers(binary=False, version=None):
headers = HttpUtils.default_get_headers(binary=binary, version=version)
headers["Content-Type"] = headers["Accept"]
return headers

Expand All @@ -39,6 +36,15 @@ def get_host_header(host):
'Host': host,
}

@staticmethod
def default_headers(version=None):
if version is None:
version = ably.api_version
return {
"X-Ably-Version": version,
"Ably-Agent": 'ably-python/%s python/%s' % (ably.lib_version, platform.python_version())
}

@staticmethod
def get_query_params(options):
params = {}
Expand Down
10 changes: 5 additions & 5 deletions ably/http/paginatedresult.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,21 +77,21 @@ async def __get_rel(self, rel_req):
return await self.paginated_query_with_request(self.__http, rel_req, self.__response_processor)

@classmethod
async def paginated_query(cls, http, method='GET', url='/', body=None,
async def paginated_query(cls, http, method='GET', url='/', version=None, body=None,
headers=None, response_processor=None,
raise_on_error=True):
headers = headers or {}
req = Request(method, url, body=body, headers=headers, skip_auth=False,
req = Request(method, url, version=version, body=body, headers=headers, skip_auth=False,
raise_on_error=raise_on_error)
return await cls.paginated_query_with_request(http, req, response_processor)

@classmethod
async def paginated_query_with_request(cls, http, request, response_processor,
raise_on_error=True):
response = await http.make_request(
request.method, request.url, headers=request.headers,
body=request.body, skip_auth=request.skip_auth,
raise_on_error=request.raise_on_error)
request.method, request.url, version=request.version,
headers=request.headers, body=request.body,
skip_auth=request.skip_auth, raise_on_error=request.raise_on_error)

items = response_processor(response)

Expand Down
Empty file added ably/realtime/__init__.py
Empty file.
Loading

0 comments on commit 2e40e1b

Please sign in to comment.