amesa-api 0.1.0__tar.gz

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.
Files changed (34) hide show
  1. amesa_api-0.1.0/PKG-INFO +45 -0
  2. amesa_api-0.1.0/README.md +27 -0
  3. amesa_api-0.1.0/amesa_api/__init__.py +10 -0
  4. amesa_api-0.1.0/amesa_api/common.py +116 -0
  5. amesa_api-0.1.0/amesa_api/controller/__init__.py +5 -0
  6. amesa_api-0.1.0/amesa_api/controller/job_status_enum.py +14 -0
  7. amesa_api-0.1.0/amesa_api/controller/main.py +60 -0
  8. amesa_api-0.1.0/amesa_api/controller/service.py +87 -0
  9. amesa_api-0.1.0/amesa_api/nocode/__init__.py +5 -0
  10. amesa_api-0.1.0/amesa_api/nocode/main.py +583 -0
  11. amesa_api-0.1.0/amesa_api/util/__init__.py +3 -0
  12. amesa_api-0.1.0/amesa_api/util/logger.py +64 -0
  13. amesa_api-0.1.0/amesa_api/util/singleton.py +55 -0
  14. amesa_api-0.1.0/amesa_api/util/token.py +32 -0
  15. amesa_api-0.1.0/amesa_api.egg-info/PKG-INFO +45 -0
  16. amesa_api-0.1.0/amesa_api.egg-info/SOURCES.txt +32 -0
  17. amesa_api-0.1.0/amesa_api.egg-info/dependency_links.txt +1 -0
  18. amesa_api-0.1.0/amesa_api.egg-info/requires.txt +7 -0
  19. amesa_api-0.1.0/amesa_api.egg-info/top_level.txt +1 -0
  20. amesa_api-0.1.0/dist/build_cython/amesa_api/__init__.c +4915 -0
  21. amesa_api-0.1.0/dist/build_cython/amesa_api/common.c +18995 -0
  22. amesa_api-0.1.0/dist/build_cython/amesa_api/controller/__init__.c +4889 -0
  23. amesa_api-0.1.0/dist/build_cython/amesa_api/controller/job_status_enum.c +5272 -0
  24. amesa_api-0.1.0/dist/build_cython/amesa_api/controller/main.c +14696 -0
  25. amesa_api-0.1.0/dist/build_cython/amesa_api/controller/service.c +14464 -0
  26. amesa_api-0.1.0/dist/build_cython/amesa_api/nocode/__init__.c +4889 -0
  27. amesa_api-0.1.0/dist/build_cython/amesa_api/nocode/main.c +36866 -0
  28. amesa_api-0.1.0/dist/build_cython/amesa_api/util/__init__.c +4591 -0
  29. amesa_api-0.1.0/dist/build_cython/amesa_api/util/logger.c +9504 -0
  30. amesa_api-0.1.0/dist/build_cython/amesa_api/util/singleton.c +9444 -0
  31. amesa_api-0.1.0/dist/build_cython/amesa_api/util/token.c +7840 -0
  32. amesa_api-0.1.0/pyproject.toml +112 -0
  33. amesa_api-0.1.0/setup.cfg +4 -0
  34. amesa_api-0.1.0/setup.py +168 -0
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.4
2
+ Name: amesa-api
3
+ Version: 0.1.0
4
+ Summary: the Amesa API
5
+ Author-email: Hunter Park <hunter@amesa.com>
6
+ Requires-Python: >=3.10, <3.13
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: cython
9
+ Requires-Dist: ruff
10
+ Requires-Dist: simple-term-menu
11
+ Requires-Dist: aiohttp
12
+ Requires-Dist: httpretty==1.0.5
13
+ Requires-Dist: pytest-asyncio
14
+ Requires-Dist: pre-commit
15
+ Dynamic: description
16
+ Dynamic: description-content-type
17
+ Dynamic: requires-python
18
+
19
+ # Amesa
20
+
21
+ Amesa helps you build Autonomous Agents! Through an easy SDK you get access to outscaled simulator training tools.
22
+
23
+ ## Licenses / Seats
24
+
25
+ Amesa is an enterprise platform and requires an API key to use.
26
+
27
+ We offer 3 types of licenses:
28
+
29
+ - **Enterprise:** for enterprise projects and agent creations
30
+ - **Personal:** for personal usage and testing (typically offered to System Integrators)
31
+ - **Trial:** validate the Amesa platform (requires sales contact person)
32
+
33
+ One key is needed per user (seat-based licensing)
34
+
35
+ You can request an API Key through the following methods:
36
+
37
+ - [Discord](https://discord.gg/EQ3BgJt9NC)
38
+ - [Mail](mailto:sales@amesa.com?subject=REQUEST%20API%20KEY%20-%20COMPANY_NAME%20-%20NAME&body=Hi%2C%0D%0A%0D%0AI%20would%20like%20to%20request%20an%20API%20key%20for%20my%20company%20to%20get%20started%20with%20Amesa.%0D%0A%0D%0A*%20Company%20Name%3A%20COMPANY_NAME%0D%0A*%20Seats%3A%20NO_OF_SEATS%0D%0A*%20License%20Type%3A%20Enterprise%20%7C%20Personal%20%7C%20Trial%20%28keep%20what%20is%20required%29%0D%0A%0D%0AKind%20Regards%2C%0D%0ANAME%0D%0AFUNCTION)
39
+
40
+ ## Getting Started
41
+
42
+ 1. Download the Amesa SDK: `pip install amesa`
43
+ 2. Request an API Key
44
+ 3. Get a simulator or use one of the [prebuilt ones](https://hub.docker.com/u/composabl)
45
+ 4. Create an Agent or see our [examples](https://github.com/Amesa/examples.amesa.com)
@@ -0,0 +1,27 @@
1
+ # Amesa
2
+
3
+ Amesa helps you build Autonomous Agents! Through an easy SDK you get access to outscaled simulator training tools.
4
+
5
+ ## Licenses / Seats
6
+
7
+ Amesa is an enterprise platform and requires an API key to use.
8
+
9
+ We offer 3 types of licenses:
10
+
11
+ - **Enterprise:** for enterprise projects and agent creations
12
+ - **Personal:** for personal usage and testing (typically offered to System Integrators)
13
+ - **Trial:** validate the Amesa platform (requires sales contact person)
14
+
15
+ One key is needed per user (seat-based licensing)
16
+
17
+ You can request an API Key through the following methods:
18
+
19
+ - [Discord](https://discord.gg/EQ3BgJt9NC)
20
+ - [Mail](mailto:sales@amesa.com?subject=REQUEST%20API%20KEY%20-%20COMPANY_NAME%20-%20NAME&body=Hi%2C%0D%0A%0D%0AI%20would%20like%20to%20request%20an%20API%20key%20for%20my%20company%20to%20get%20started%20with%20Amesa.%0D%0A%0D%0A*%20Company%20Name%3A%20COMPANY_NAME%0D%0A*%20Seats%3A%20NO_OF_SEATS%0D%0A*%20License%20Type%3A%20Enterprise%20%7C%20Personal%20%7C%20Trial%20%28keep%20what%20is%20required%29%0D%0A%0D%0AKind%20Regards%2C%0D%0ANAME%0D%0AFUNCTION)
21
+
22
+ ## Getting Started
23
+
24
+ 1. Download the Amesa SDK: `pip install amesa`
25
+ 2. Request an API Key
26
+ 3. Get a simulator or use one of the [prebuilt ones](https://hub.docker.com/u/composabl)
27
+ 4. Create an Agent or see our [examples](https://github.com/Amesa/examples.amesa.com)
@@ -0,0 +1,10 @@
1
+ # Copyright (C) Amesa, Inc - All Rights Reserved
2
+ # Unauthorized copying of this file, via any medium is strictly prohibited
3
+ # Proprietary and confidential
4
+
5
+ # We want to expose these classes to the user at the base of core
6
+ # ex: `from amesa.core import Agent`
7
+
8
+ # Continue with the imports
9
+ from .nocode import APINoCode
10
+ from .common import get_ssl_connector
@@ -0,0 +1,116 @@
1
+ # Copyright (C) Amesa, Inc - All Rights Reserved
2
+ # Unauthorized copying of this file, via any medium is strictly prohibited
3
+ # Proprietary and confidential
4
+ from typing import Union
5
+
6
+ import aiohttp
7
+ import json
8
+ import os
9
+ import ssl
10
+
11
+
12
+ def get_ssl_connector():
13
+ ssl_cert_file = os.getenv("SSL_CERT_FILE")
14
+ ssl_cert_path = os.getenv("SSL_CERT_PATH")
15
+ ssl_ctx = ssl.create_default_context(
16
+ ssl.Purpose.SERVER_AUTH, cafile=ssl_cert_file, capath=ssl_cert_path
17
+ )
18
+ return aiohttp.TCPConnector(ssl_context=ssl_ctx)
19
+
20
+
21
+ class APIBase:
22
+ def __init__(self, base_url):
23
+ self.base_url = base_url
24
+ self.token = None
25
+
26
+ def _get_headers(self):
27
+ headers = {"Content-Type": "application/json", "Accept": "application/json"}
28
+
29
+ if self.token is not None:
30
+ headers["Authorization"] = f"Bearer {self.token}"
31
+
32
+ return headers
33
+
34
+ async def get(self, path: str, headers={}):
35
+ """
36
+ Make a GET request to the API
37
+ """
38
+ headers_base = self._get_headers()
39
+ headers_base.update(headers)
40
+ connector = get_ssl_connector()
41
+ async with aiohttp.ClientSession(connector=connector) as session:
42
+ async with session.get(
43
+ self.base_url + path, headers=headers_base
44
+ ) as response:
45
+ return await self._process_response(response)
46
+
47
+ async def post(self, path: str, data: Union[dict, str], headers={}):
48
+ """
49
+ Make a POST request to the API
50
+ """
51
+ headers_base = self._get_headers()
52
+ headers_base.update(headers)
53
+
54
+ if isinstance(data, dict):
55
+ data = json.dumps(data)
56
+ else:
57
+ del headers_base["Content-Type"]
58
+
59
+ connector = get_ssl_connector()
60
+ async with aiohttp.ClientSession(connector=connector) as session:
61
+ async with session.post(
62
+ self.base_url + path, headers=headers_base, data=data
63
+ ) as response:
64
+ return await self._process_response(response)
65
+
66
+ async def put(self, path: str, data: Union[dict, str], headers={}):
67
+ """
68
+ Make a PUT request to the API
69
+ """
70
+ headers_base = self._get_headers()
71
+ headers_base.update(headers)
72
+
73
+ if isinstance(data, dict):
74
+ data = json.dumps(data)
75
+ else:
76
+ del headers_base["Content-Type"]
77
+
78
+ connector = get_ssl_connector()
79
+ async with aiohttp.ClientSession(connector=connector) as session:
80
+ async with session.put(
81
+ self.base_url + path, headers=headers_base, data=data
82
+ ) as response:
83
+ return await self._process_response(response)
84
+
85
+ async def delete(self, path: str, headers={}):
86
+ """
87
+ Make a DELETE request to the API
88
+ """
89
+ headers_base = self._get_headers()
90
+ headers_base.update(headers)
91
+
92
+ connector = get_ssl_connector()
93
+ async with aiohttp.ClientSession(connector=connector) as session:
94
+ async with session.delete(
95
+ self.base_url + path, headers=headers_base
96
+ ) as response:
97
+ return await self._process_response(response)
98
+
99
+ async def _process_response(self, response):
100
+ """
101
+ Process the response from the API
102
+ """
103
+ if response.status >= 200 and response.status < 202:
104
+ try:
105
+ return await response.json()
106
+ except Exception:
107
+ return await response.text()
108
+ elif response.status >= 202 and response.status < 300:
109
+ return None
110
+ else:
111
+ try:
112
+ res_json = await response.json()
113
+ raise Exception(f"Error: {response.status} - {res_json['message']}")
114
+ except Exception:
115
+ txt = await response.text()
116
+ raise Exception(f"Error: {response.status} - {txt}")
@@ -0,0 +1,5 @@
1
+ # Copyright (C) Amesa, Inc - All Rights Reserved
2
+ # Unauthorized copying of this file, via any medium is strictly prohibited
3
+ # Proprietary and confidential
4
+
5
+ from .main import APIController
@@ -0,0 +1,14 @@
1
+ # Copyright (C) Amesa, Inc - All Rights Reserved
2
+ # Unauthorized copying of this file, via any medium is strictly prohibited
3
+ # Proprietary and confidential
4
+
5
+ from enum import Enum
6
+
7
+
8
+ class JobStatusEnum(str, Enum):
9
+ STARTING = "starting"
10
+ STARTED = "started"
11
+ STOPPING = "stopping"
12
+ STOPPED = "stopped"
13
+ FAILED = "failed"
14
+ DONE = "done"
@@ -0,0 +1,60 @@
1
+ # Copyright (C) Amesa, Inc - All Rights Reserved
2
+ # Unauthorized copying of this file, via any medium is strictly prohibited
3
+ # Proprietary and confidential
4
+
5
+ from amesa_api.common import APIBase
6
+ from amesa_api.controller.job_status_enum import JobStatusEnum
7
+
8
+
9
+ class APIController(APIBase):
10
+ def __init__(self, base_url):
11
+ super().__init__(base_url)
12
+
13
+ self.admin = APIControllerAdmin(self)
14
+ self.sdk = APIControllerSdk(self)
15
+ self.direct_mode = APIControllerDirectMode(self)
16
+
17
+
18
+ class APIControllerDirectMode:
19
+ def __init__(self, api):
20
+ self.api: APIController = api
21
+
22
+ async def submit(self, job_id: str, serialized_trainer: str):
23
+ body = {"id": job_id, "json": serialized_trainer}
24
+
25
+ await self.api.post("/jobs", body)
26
+
27
+
28
+ class APIControllerAdmin:
29
+ def __init__(self, api):
30
+ self.api: APIController = api
31
+
32
+ async def list_jobs(self):
33
+ return await self.api.get("/api/admin/v1/jobs")
34
+
35
+ async def get_job(self, job_id: str):
36
+ return await self.api.get(f"/api/admin/v1/jobs/{job_id}")
37
+
38
+ async def cancel_job(self, job_id: str):
39
+ return await self.api.delete(f"/api/admin/v1/jobs/{job_id}")
40
+
41
+
42
+ class APIControllerSdk:
43
+ def __init__(self, api):
44
+ self.api: APIController = api
45
+
46
+ async def heartbeat(self, job_id: str):
47
+ body = {
48
+ "health": "ok",
49
+ }
50
+
51
+ res = await self.api.post(f"/api/sdk/v1/jobs/{job_id}/heartbeat", body)
52
+
53
+ return res
54
+
55
+ async def status(self, status: JobStatusEnum, job_id: str):
56
+ status = {"status": status}
57
+
58
+ res = await self.api.post(f"/api/sdk/v1/jobs/{job_id}/status", status)
59
+
60
+ return res
@@ -0,0 +1,87 @@
1
+ # Copyright (C) Amesa, Inc - All Rights Reserved
2
+ # Unauthorized copying of this file, via any medium is strictly prohibited
3
+ # Proprietary and confidential
4
+
5
+ import asyncio
6
+ import threading
7
+ import time
8
+ from os import getenv
9
+
10
+ import amesa_api.util.logger as logger_util
11
+ from amesa_api.controller.job_status_enum import JobStatusEnum
12
+ from amesa_api.controller.main import APIController
13
+
14
+ logger = logger_util.get_logger(__name__)
15
+
16
+
17
+ class ControllerService:
18
+ _instance = None
19
+ heartbeat_thread = None
20
+ healthy = True
21
+ job_id: str = None
22
+
23
+ def __new__(cls, *args, **kwargs):
24
+ if cls._instance is None:
25
+ cls._instance = super().__new__(cls)
26
+
27
+ return cls._instance
28
+
29
+ def __init__(self, url=None):
30
+ if url is None:
31
+ controller_url = getenv("AMESA_CONTROLLER_URL", None)
32
+ else:
33
+ controller_url = url
34
+
35
+ if controller_url is None:
36
+ raise Exception("AMESA_CONTROLLER_URL could not be read!")
37
+
38
+ self.client = APIController(controller_url)
39
+
40
+ def set_job_id(self, job_id):
41
+ self.job_id = job_id
42
+
43
+ async def start_heartbeat_thread(self):
44
+ try:
45
+ await self.client.sdk.heartbeat(self.job_id)
46
+ except Exception as e:
47
+ logger.error(f"Failed to connect to controller: {e}")
48
+ return
49
+
50
+ self.heartbeat_thread = run_async_in_daemon_thread(self.send_heartbeat)
51
+
52
+ def mark_unhealthy(self):
53
+ self.healthy = False
54
+
55
+ async def send_heartbeat(self):
56
+ while self.healthy:
57
+ try:
58
+ await self.client.sdk.heartbeat(self.job_id)
59
+ except Exception as e:
60
+ logger.error(f"Failed to send heartbeat to controller: {e}")
61
+
62
+ time.sleep(10)
63
+
64
+ logger.info(
65
+ "ControllerService heartbeat task stopped because the job is marked as unhealthy"
66
+ )
67
+
68
+ async def send_status(self, status: JobStatusEnum):
69
+ await self.client.sdk.status(status, self.job_id)
70
+
71
+
72
+ def run_async_in_daemon_thread(fn, *args, **kwargs):
73
+ """
74
+ Runs an asynchronous function in a separate daemon thread, and waits for it to complete.
75
+ Returns the result of the asynchronous function.
76
+ """
77
+
78
+ def run(loop):
79
+ asyncio.set_event_loop(loop)
80
+
81
+ loop.run_until_complete(fn(*args, **kwargs))
82
+
83
+ loop = asyncio.new_event_loop()
84
+ t = threading.Thread(target=run, args=(loop,), daemon=True)
85
+ t.start()
86
+
87
+ return t
@@ -0,0 +1,5 @@
1
+ # Copyright (C) Amesa, Inc - All Rights Reserved
2
+ # Unauthorized copying of this file, via any medium is strictly prohibited
3
+ # Proprietary and confidential
4
+
5
+ from .main import APINoCode