flespi-sdk 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
flespi_sdk/__init__.py ADDED
@@ -0,0 +1,49 @@
1
+ from flespi_sdk.account import Account
2
+
3
+
4
+ def main() -> None:
5
+ print("Hello from plespi!")
6
+
7
+
8
+ async def login_with_token(token: str) -> Account:
9
+ """
10
+ Login to flespi with token
11
+ :param token: token
12
+ :return: Account object
13
+ """
14
+ if not token:
15
+ raise ValueError("Token is required")
16
+ if not isinstance(token, str):
17
+ raise ValueError("Token must be a string")
18
+
19
+ account = Account()
20
+ await account.set_token(token)
21
+ return account
22
+
23
+
24
+ async def login_with_realm(
25
+ realm_public_id: str, realm_username: str, realm_password: str
26
+ ):
27
+ """
28
+ Login to flespi with realm
29
+ :param realm_public_id: realm public id
30
+ :param realm_username: realm username
31
+ :param realm_password: realm password
32
+ :return: Account object
33
+ """
34
+ if not realm_public_id:
35
+ raise ValueError("Realm public id is required")
36
+ if not isinstance(realm_public_id, str):
37
+ raise ValueError("Realm public id must be a string")
38
+ if not realm_username:
39
+ raise ValueError("Realm username is required")
40
+ if not isinstance(realm_username, str):
41
+ raise ValueError("Realm username must be a string")
42
+ if not realm_password:
43
+ raise ValueError("Realm password is required")
44
+ if not isinstance(realm_password, str):
45
+ raise ValueError("Realm password must be a string")
46
+
47
+ account = Account()
48
+ await account.realm_login(realm_public_id, realm_username, realm_password)
49
+ return account
flespi_sdk/account.py ADDED
@@ -0,0 +1,75 @@
1
+ from aiohttp import ClientSession
2
+
3
+ from flespi_sdk.modules.metadata import Metadata
4
+ from flespi_sdk.modules.mqtt import MQTT
5
+ from flespi_sdk.modules.subaccounts import Subaccounts
6
+
7
+
8
+ class Account:
9
+ def __init__(
10
+ self, session: ClientSession | None = None, id: int | None = None
11
+ ) -> None:
12
+ self.id = id
13
+ self.session = session or ClientSession("https://flespi.io")
14
+
15
+ self._init_modules()
16
+
17
+ async def stop(self) -> None:
18
+ """
19
+ Close the aiohttp session.
20
+ """
21
+ await self.session.close()
22
+
23
+ async def set_token(self, token: str) -> None:
24
+ await self._reset(token)
25
+
26
+ async def realm_login(
27
+ self,
28
+ realm_public_id: str,
29
+ realm_username: str,
30
+ realm_password: str,
31
+ ) -> None:
32
+ """
33
+ Login to a realm and set the Authorization header for subsequent requests.
34
+ :param realm_public_id: Public ID of the realm.
35
+ :param realm_username: Username for the realm.
36
+ :param realm_password: Password for the realm.
37
+ :raises Exception: If the login fails.
38
+ """
39
+ if not realm_public_id or not realm_username or not realm_password:
40
+ raise ValueError("All parameters are required")
41
+
42
+ async with self.session.post(
43
+ f"/realm/{realm_public_id}/login",
44
+ json={"username": realm_username, "password": realm_password},
45
+ ) as response:
46
+ if response.status != 200:
47
+ raise Exception("Login failed")
48
+ response_json = await response.json()
49
+
50
+ token = response_json["result"][0]["token"]
51
+
52
+ await self._reset(token)
53
+
54
+ async def _reset(self, token: str | None = None) -> None:
55
+ """
56
+ Reset the account by resetting token and clearing the CID from all subsystems.
57
+ """
58
+ async with self.session.get(
59
+ "/platform/customer", headers=dict(Authorization=f"FlespiToken {token}")
60
+ ) as response:
61
+ if response.status != 200:
62
+ raise Exception("Failed to get account ID: " + str(response.status))
63
+ response_json = await response.json()
64
+ result = response_json["result"]
65
+ id = result[0]["id"]
66
+ if not id:
67
+ raise Exception("Failed to get account ID: " + response_json)
68
+ self.id = id
69
+ self.session.headers["Authorization"] = f"FlespiToken {token}"
70
+ self._init_modules()
71
+
72
+ def _init_modules(self):
73
+ self.metadata = Metadata(self.session, self.id)
74
+ self.subaccounts = Subaccounts(self.session, self.id)
75
+ self.mqtt = MQTT(self.session, self.id)
flespi_sdk/cli.py ADDED
@@ -0,0 +1,46 @@
1
+ import sys
2
+ import argparse
3
+ from flespi_sdk.account import Account
4
+
5
+
6
+ async def get_account(prog: str = sys.argv[0]) -> Account:
7
+ parser = argparse.ArgumentParser(
8
+ prog=prog,
9
+ description="CLI for Flespi SDK",
10
+ epilog=f"Examples:\n {prog} <token>\n {prog} <realm> <username> <password>",
11
+ formatter_class=argparse.RawTextHelpFormatter,
12
+ )
13
+ parser.add_argument(
14
+ "args",
15
+ nargs="+",
16
+ help=(
17
+ "Arguments for login:\n"
18
+ " - Provide 1 argument: <token>\n"
19
+ " - Provide 3 arguments: <realm> <username> <password>"
20
+ ),
21
+ )
22
+
23
+ args = parser.parse_args()
24
+
25
+ if len(args.args) == 1:
26
+ token = args.args[0]
27
+ account = Account()
28
+ try:
29
+ await account.set_token(token)
30
+ except Exception as e:
31
+ await account.stop()
32
+ raise e
33
+ return account
34
+ elif len(args.args) == 3:
35
+ realm, username, password = args.args
36
+ account = Account()
37
+ try:
38
+ await account.realm_login(realm, username, password)
39
+ except Exception as e:
40
+ await account.stop()
41
+ raise e
42
+ return account
43
+ else:
44
+ parser.error(
45
+ "Invalid number of arguments. Provide 1 argument (token) or 3 arguments (realm, username, password)."
46
+ )
File without changes
@@ -0,0 +1,80 @@
1
+ import aiohttp
2
+
3
+ from flespi_sdk.modules.subsystem import Module
4
+
5
+
6
+ class Metadata(Module):
7
+ def __init__(self, session: aiohttp.ClientSession, cid: int | None = None):
8
+ super().__init__(session, cid)
9
+
10
+ async def get(self) -> dict:
11
+ """
12
+ Get metadata for the current account.
13
+ :return: Metadata as a dictionary.
14
+ """
15
+ params = {"fields": "metadata"}
16
+ async with self.session.get(
17
+ "platform/customer", params=params, headers=self.get_headers()
18
+ ) as response:
19
+ result = await self.get_result(response)
20
+ return result[0]["metadata"]
21
+
22
+ async def set(self, metadata: dict) -> None:
23
+ """ "
24
+ "Set metadata for the current account.
25
+ :param metadata: Metadata as a dictionary.
26
+ """
27
+ async with self.session.put(
28
+ "platform/customer",
29
+ json={"metadata": metadata},
30
+ headers=self.get_headers(),
31
+ ) as response:
32
+ await self.get_result(response)
33
+
34
+ async def get_value(self, key_path: str):
35
+ """
36
+ Get a specific value from the metadata.
37
+ :param key_path: The key path to the value in the metadata.
38
+ :return: The value from the metadata.
39
+ """
40
+ metadata = await self.get()
41
+ keys = key_path.split(".")
42
+ value = metadata
43
+ for key in keys:
44
+ if key in value:
45
+ value = value[key]
46
+ else:
47
+ return None
48
+ return value
49
+
50
+ async def set_value(self, key_path: str, value) -> None:
51
+ """
52
+ Set a specific value in the metadata.
53
+ :param key_path: The key path to the value in the metadata.
54
+ :param value: The value to set.
55
+ """
56
+ metadata = await self.get()
57
+ keys = key_path.split(".")
58
+ d = metadata
59
+ for key in keys[:-1]:
60
+ if key not in d:
61
+ d[key] = {}
62
+ d = d[key]
63
+ d[keys[-1]] = value
64
+ await self.set(metadata)
65
+
66
+ async def delete_value(self, key_path: str) -> None:
67
+ """
68
+ Delete a specific key from the metadata.
69
+ :param key_path: The key path to the value in the metadata.
70
+ """
71
+ metadata = await self.get()
72
+ keys = key_path.split(".")
73
+ value = metadata
74
+ for key in keys[:-1]:
75
+ if key in value:
76
+ value = value[key]
77
+ else:
78
+ return None
79
+ del value[keys[-1]]
80
+ await self.set(metadata)
@@ -0,0 +1,77 @@
1
+ import urllib.parse
2
+
3
+ import aiohttp
4
+ from flespi_sdk.modules.subsystem import Module
5
+
6
+
7
+ class MQTT(Module):
8
+ def __init__(self, session: aiohttp.ClientSession, cid: int | None = None):
9
+ super().__init__(session, cid)
10
+
11
+ async def list(self, topic: str):
12
+ """
13
+ Get messages from the specified MQTT topic.
14
+ :param topic: The MQTT topic to get messages from.
15
+ :return: List of messages from the specified topic.
16
+ """
17
+ params = {"fields": "cid,topic,payload,user_properties"}
18
+ async with self.session.get(
19
+ f"/mqtt/messages/{urllib.parse.quote_plus(topic)}",
20
+ params=params,
21
+ headers=self.get_headers(),
22
+ ) as response:
23
+ result = await self.get_result(response)
24
+ direct_messages = [msg for msg in result if msg["cid"] == self.cid]
25
+ for msg in direct_messages:
26
+ del msg["cid"]
27
+ return direct_messages
28
+
29
+ async def get(self, topic: str):
30
+ """
31
+ Get a specific message from the specified MQTT topic.
32
+ :param topic: The MQTT topic to get the message from.
33
+ :return: The message from the specified topic.
34
+ """
35
+ msgs = await self.list(topic)
36
+ if len(msgs) > 1:
37
+ raise ValueError(
38
+ f"Multiple messages found for topic '{topic}'. Use list() to get all messages."
39
+ )
40
+ elif len(msgs) == 0:
41
+ raise ValueError(f"No messages found for topic '{topic}'.")
42
+ else:
43
+ return msgs[0]
44
+
45
+ async def publish(
46
+ self, topic: str, payload: str | None = None, retained: bool = False
47
+ ):
48
+ """
49
+ Publish a message to the specified MQTT topic.
50
+ :param topic: The MQTT topic to publish the message to.
51
+ :param payload: The message payload.
52
+ :param retained: Whether the message should be retained.
53
+ """
54
+ message = {
55
+ "topic": topic,
56
+ "retained": retained,
57
+ "payload": payload,
58
+ }
59
+ async with self.session.post(
60
+ "/mqtt/messages",
61
+ json=message,
62
+ headers=self.get_headers(),
63
+ ) as response:
64
+ result = await self.get_result(response)
65
+ return result
66
+
67
+ async def delete(self, topic: str):
68
+ """
69
+ Delete a message from the specified MQTT topic.
70
+ :param topic: The MQTT topic to delete the message from.
71
+ """
72
+ async with self.session.delete(
73
+ f"/mqtt/messages/{urllib.parse.quote_plus(topic)}",
74
+ headers=self.get_headers(),
75
+ ) as response:
76
+ result = await self.get_result(response)
77
+ return result
@@ -0,0 +1,31 @@
1
+ import aiohttp
2
+
3
+ from flespi_sdk.modules.subsystem import Module
4
+
5
+
6
+ class Subaccounts(Module):
7
+ def __init__(self, session: aiohttp.ClientSession, cid: int | None = None):
8
+ super().__init__(session, cid)
9
+
10
+ async def get_by_id(self, id: str):
11
+ from flespi_sdk.account import Account
12
+
13
+ async with self.session.get(
14
+ f"/platform/subaccounts/{id},cid={self.cid}", headers=self.get_headers()
15
+ ) as response:
16
+ result = await self.get_result(response)
17
+ subacc = result[0]
18
+ return Account(
19
+ self.session,
20
+ id=subacc["id"],
21
+ )
22
+
23
+ async def list(self, selector: str = "all"):
24
+ from flespi_sdk.account import Account
25
+
26
+ async with self.session.get(
27
+ f"/platform/subaccounts/{selector},cid={self.cid}",
28
+ headers=self.get_headers(),
29
+ ) as response:
30
+ result = await self.get_result(response)
31
+ return [Account(self.session, id=subacc["id"]) for subacc in result]
@@ -0,0 +1,22 @@
1
+ from aiohttp import ClientSession
2
+
3
+
4
+ class Module:
5
+ def __init__(self, session: ClientSession, cid: int | None = None):
6
+ self.cid = cid
7
+ self.session = session
8
+
9
+ def reset(self, cid: int):
10
+ self.cid = cid
11
+
12
+ def get_headers(self):
13
+ if self.cid:
14
+ return {"X-Flespi-CID": str(self.cid)}
15
+ raise ValueError("CID is not set. Please set the CID before using this method.")
16
+
17
+ async def get_result(self, response):
18
+ if response.status != 200:
19
+ raise Exception("Status code: " + str(response.status))
20
+ response_json = await response.json()
21
+ result = response_json["result"]
22
+ return result
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: flespi-sdk
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ Author-email: Sergey Shevchik <sergey.shevchik@gmail.com>
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: aiohttp>=3.11.15
@@ -0,0 +1,11 @@
1
+ flespi_sdk/__init__.py,sha256=INx-TmKYpKi9SC0-UMqtqKwnMnHK88oXyvh3tTFY668,1462
2
+ flespi_sdk/account.py,sha256=mnFLKR204ln09UIRXGQcGf0tURK5wgWg8mlo8vN3o3w,2656
3
+ flespi_sdk/cli.py,sha256=6OcRQAu-LLFTYGGY_S0SNTXgHlazr3NaMDqPQ7QLg_E,1346
4
+ flespi_sdk/modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ flespi_sdk/modules/subsystem.py,sha256=6ZKVL-ZtqbA_n1tbuzStu3EV7WYtZepWtuF7G_yIqEA,675
6
+ flespi_sdk/modules/metadata/__init__.py,sha256=SVj_OogLsLEOwOS2snP3iW03NmENe8sIW1MbeKK5iTo,2501
7
+ flespi_sdk/modules/mqtt/__init__.py,sha256=jiN4RniDru1YJF_EMHSIIjUgDsboeJc730ktu7P7a8E,2695
8
+ flespi_sdk/modules/subaccounts/__init__.py,sha256=OElPCprLOqV1hZ-evBi-sWp_KNTnUImdgLXqVvsO2No,1031
9
+ flespi_sdk-0.1.0.dist-info/METADATA,sha256=VJJ7knUJZZQe2gp5oLLUqLoPdN0YcNQGWH6zTliftEM,203
10
+ flespi_sdk-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
+ flespi_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any