Skip to content

Commit fc3fb8a

Browse files
authored
Feature: full API refactoring (#2)
* Internal: user session class Problem: we often pass 2-3 objects to each function that represent information on how to connect to the Aleph network: * the user account * the API server URL * the aiohttp session. This can be simplified to improve the user experience and reduce the complexity of the SDK. Solution: introduce a new state class: `UserSession`. This object is now passed as the first parameter of every public function. It is initialized with the user account and the API server URL. It is in charge of configuring and managing the aiohttp session object. The typical usage of the library now looks like this: ``` account = load_my_account() api_server = "https://aleph.cloud" # example async with UserSession(account=account, api_server=api_server) as session: message, status = await create_post( session=session, ... ) ``` This enables the following improvements: * Less clutter in function signatures: all public SDK functions now have 1 or 2 fewer arguments. * The API server URL is now managed when initializing the aiohttp session inside the user session object. Implementations can simply specify the endpoint URL. Ex: `f"{api_server}/api/v0/messages.json` can now be expressed as `"/api/v0/messages.json"`. Breaking changes: * The signatures of all public functions of the SDK have been modified. The user must now initialize the user session object and pass it as parameter. * UserSession + AuthenticatedUserSession * Feature: full API refactoring The session classes now provide all the SDK functionalities. The `synchronous` and `asynchronous` modules are removed in favor of the `UserSession` and `AuthenticatedUserSession` classes. `UserSession` is intended as a read-only session to access public API endpoints, while `AuthenticatedUserSession` requires a chain account and allows the user to post messages to the Aleph network. Example usage: ``` from aleph_client import AuthenticatedUserSession async def post_message(): async with AuthenticatedUserSession( account=fixture_account, api_server=emitter_node ) as session: post_message, message_status = await session.create_post( post_content={"Hello": "World"}, ) ``` Both classes provide a synchronous context manager and an equivalent sync class for non-async code. Breaking changes: - Everything: the SDK API is entirely modified.
1 parent 1d778a6 commit fc3fb8a

24 files changed

+1830
-1695
lines changed

examples/httpgateway.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,14 @@
22
"""
33
# -*- coding: utf-8 -*-
44

5-
import os
6-
7-
# import requests
8-
import platform
9-
10-
# import socket
11-
import time
125
import asyncio
6+
137
import click
14-
from aleph_client.asynchronous import create_post
8+
from aiohttp import web
159

16-
# from aleph_client.chains.nuls1 import NULSAccount, get_fallback_account
17-
from aleph_client.chains.ethereum import ETHAccount
1810
from aleph_client.chains.common import get_fallback_private_key
19-
20-
from aiohttp import web
11+
from aleph_client.chains.ethereum import ETHAccount
12+
from aleph_client.user_session import AuthenticatedUserSession
2113

2214
app = web.Application()
2315
routes = web.RouteTableDef()
@@ -42,13 +34,14 @@ async def source_post(request):
4234
return web.json_response(
4335
{"status": "error", "message": "unauthorized secret"}
4436
)
45-
message, _status = await create_post(
46-
app["account"],
47-
data,
48-
"event",
49-
channel=app["channel"],
50-
api_server="https://api2.aleph.im",
51-
)
37+
async with AuthenticatedUserSession(
38+
account=app["account"], api_server="https://api2.aleph.im"
39+
) as session:
40+
message, _status = await session.create_post(
41+
post_content=data,
42+
post_type="event",
43+
channel=app["channel"],
44+
)
5245

5346
return web.json_response({"status": "success", "item_hash": message.item_hash})
5447

examples/metrics.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@
55
import os
66
import platform
77
import time
8+
from typing import Tuple
89

910
import psutil
11+
from aleph_message.models import AlephMessage
1012

13+
from aleph_client import AuthenticatedUserSession
1114
from aleph_client.chains.ethereum import get_fallback_account
12-
from aleph_client.synchronous import create_aggregate
15+
from aleph_client.conf import settings
16+
from aleph_client.types import MessageStatus
17+
from aleph_client.user_session import AuthenticatedUserSessionSync
1318

1419

1520
def get_sysinfo():
@@ -49,8 +54,10 @@ def get_cpu_cores():
4954
return [c._asdict() for c in psutil.cpu_times_percent(0, percpu=True)]
5055

5156

52-
def send_metrics(account, metrics):
53-
return create_aggregate(account, "metrics", metrics, channel="SYSINFO")
57+
def send_metrics(
58+
session: AuthenticatedUserSessionSync, metrics
59+
) -> Tuple[AlephMessage, MessageStatus]:
60+
return session.create_aggregate(key="metrics", content=metrics, channel="SYSINFO")
5461

5562

5663
def collect_metrics():
@@ -64,11 +71,14 @@ def collect_metrics():
6471

6572
def main():
6673
account = get_fallback_account()
67-
while True:
68-
metrics = collect_metrics()
69-
message, status = send_metrics(account, metrics)
70-
print("sent", message.item_hash)
71-
time.sleep(10)
74+
with AuthenticatedUserSession(
75+
account=account, api_server=settings.API_HOST
76+
) as session:
77+
while True:
78+
metrics = collect_metrics()
79+
message, status = send_metrics(session, metrics)
80+
print("sent", message.item_hash)
81+
time.sleep(10)
7282

7383

7484
if __name__ == "__main__":

examples/mqtt.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
from aleph_client.chains.common import get_fallback_private_key
1212
from aleph_client.chains.ethereum import ETHAccount
13-
from aleph_client.main import create_aggregate
13+
from aleph_client import AuthenticatedUserSession
14+
from aleph_client.conf import settings
1415

1516

1617
def get_input_data(value):
@@ -26,7 +27,12 @@ def get_input_data(value):
2627

2728

2829
def send_metrics(account, metrics):
29-
return create_aggregate(account, "metrics", metrics, channel="SYSINFO")
30+
with AuthenticatedUserSession(
31+
account=account, api_server=settings.API_HOST
32+
) as session:
33+
return session.create_aggregate(
34+
key="metrics", content=metrics, channel="SYSINFO"
35+
)
3036

3137

3238
def on_disconnect(client, userdata, rc):
@@ -95,10 +101,15 @@ async def gateway(
95101
if not userdata["received"]:
96102
await client.reconnect()
97103

98-
for key, value in state.items():
99-
message, status = create_aggregate(account, key, value, channel="IOT_TEST")
100-
print("sent", message.item_hash)
101-
userdata["received"] = False
104+
async with AuthenticatedUserSession(
105+
account=account, api_server=settings.API_HOST
106+
) as session:
107+
for key, value in state.items():
108+
message, status = await session.create_aggregate(
109+
key=key, content=value, channel="IOT_TEST"
110+
)
111+
print("sent", message.item_hash)
112+
userdata["received"] = False
102113

103114

104115
@click.command()

examples/store.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import asyncio
22

3-
import aiohttp
43
import click
54
from aleph_message.models import StoreMessage
65

7-
from aleph_client.asynchronous import create_store
86
from aleph_client.chains.common import get_fallback_private_key
97
from aleph_client.chains.ethereum import ETHAccount
8+
from aleph_client.conf import settings
109
from aleph_client.types import MessageStatus
10+
from aleph_client.user_session import AuthenticatedUserSession
1111

1212
DEFAULT_SERVER = "https://api2.aleph.im"
1313

@@ -23,7 +23,9 @@ async def print_output_hash(message: StoreMessage, status: MessageStatus):
2323

2424

2525
async def do_upload(account, engine, channel, filename=None, file_hash=None):
26-
async with aiohttp.ClientSession() as session:
26+
async with AuthenticatedUserSession(
27+
account=account, api_server=settings.API_HOST
28+
) as session:
2729
print(filename, account.get_address())
2830
if filename:
2931
try:
@@ -33,24 +35,20 @@ async def do_upload(account, engine, channel, filename=None, file_hash=None):
3335
if len(content) > 4 * 1024 * 1024 and engine == "STORAGE":
3436
print("File too big for native STORAGE engine")
3537
return
36-
message, status = await create_store(
37-
account,
38+
message, status = await session.create_store(
3839
file_content=content,
3940
channel=channel,
4041
storage_engine=engine.lower(),
41-
session=session,
4242
)
4343
except IOError:
4444
print("File not accessible")
4545
raise
4646

4747
elif file_hash:
48-
message, status = await create_store(
49-
account,
48+
message, status = await session.create_store(
5049
file_hash=file_hash,
5150
channel=channel,
5251
storage_engine=engine.lower(),
53-
session=session,
5452
)
5553

5654
await print_output_hash(message, status)

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ testing =
6060
pytest
6161
pytest-cov
6262
pytest-asyncio
63+
pytest-mock
6364
mypy
6465
secp256k1
6566
pynacl
@@ -69,6 +70,7 @@ testing =
6970
httpx
7071
requests
7172
aleph-pytezos==0.1.0
73+
types-certifi
7274
types-setuptools
7375
black
7476
mqtt =

src/aleph_client/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from pkg_resources import get_distribution, DistributionNotFound
2+
from .user_session import AuthenticatedUserSession, UserSession
23

34
try:
45
# Change here if project is renamed and does not equal the package name
@@ -8,3 +9,6 @@
89
__version__ = "unknown"
910
finally:
1011
del get_distribution, DistributionNotFound
12+
13+
14+
__all__ = ["AuthenticatedUserSession", "UserSession"]

0 commit comments

Comments
 (0)