Skip to content

Commit 9652eff

Browse files
author
GitHub Actions
committed
Auto commit from main repo: manual-sync
1 parent d412ab9 commit 9652eff

14 files changed

Lines changed: 301 additions & 301 deletions

File tree

README.md

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ For full documentation, please visit our [dedicated](https://armis-python-sdk.re
1616

1717
## Usage
1818

19-
All interaction with the SDK happens through the `ArmisSdk` class. You'll need three things:
19+
All interaction with the SDK happens through the `ArmisSdk` class. You'll need five things:
2020

21-
1. **Tenant name**: The name of the tenant you want to interact with.
22-
2. **Secret key**: The secret key associated with the tenant, obtained from the tenant itself.
23-
3. **Client id**: A unique identifier for your application. Currently, this can be any string.
21+
1. **Audience**: The url of the tenant you want to interact with, including trailing slash (e.g. `https://acme.armis.com/`).
22+
2. **Client ID**: The email address of the user account within the tenant that was used to generate the Client Secret.
23+
3. **Client Secret**: The confidential credential generated by your customer within Armis, paired with the Client ID.
24+
4. **Vendor ID**: An identifier unique to your developer account or integration, obtained when you register on our developer portal.
25+
5. **Scopes**: The specific permissions required by your access token to interact with the desired API endpoints.
2426

25-
You can either provide these values using the environment variables `ARMIS_TENANT`, `ARMIS_SECRET_KEY`, and `ARMIS_CLIENT_ID`:
27+
You can either provide these values using the environment variables `ARMIS_AUDIENCE`, `ARMIS_CLIENT_ID`, `ARMIS_CLIENT_SECRET`, `ARMIS_VENDOR_ID`, and `ARMIS_CLIENT_ID`:
2628
```python
2729
from armis_sdk import ArmisSdk
2830

@@ -32,16 +34,30 @@ armis_sdk = ArmisSdk()
3234
or by passing them explicitly:
3335
```python
3436
from armis_sdk import ArmisSdk
35-
36-
armis_sdk = ArmisSdk(tenant="<tenant>", secret_key="<secret_key>", client_id="<client_id>")
37+
from armis_sdk import ClientCredentials
38+
39+
credentials = ClientCredentials(
40+
audience="<audience>",
41+
client_id="<client_id>",
42+
client_secret="<client_secret>",
43+
vendor_id="<vendor_id>",
44+
scopes=["scope1", "scope2"],
45+
)
46+
armis_sdk = ArmisSdk(credentials=credentials)
3747
```
3848

3949
> [!TIP]
40-
> If you're building an application that interacts with multiple tenants, you can populate only the `ARMIS_CLIENT_ID` environment variable and pass the `tenant` and `secret_key` explicitly:
50+
> If you're building an application that interacts with multiple tenants, you can populate only the `ARMIS_VENDOR_ID` and `ARMIS_SCOPES` environment variable and pass the `audience`, `client_id` and `client_secret` explicitly:
4151
> ```python
4252
> from armis_sdk import ArmisSdk
53+
> from armis_sdk import ClientCredentials
4354
>
44-
> armis_sdk = ArmisSdk(tenant="<tenant>", secret_key="<secret_key>")
55+
> credentials = ClientCredentials(
56+
> audience="<audience>",
57+
> client_id="<client_id>",
58+
> client_secret="<client_secret>",
59+
> )
60+
> armis_sdk = ArmisSdk(credentials=credentials)
4561
> ```
4662
4763
## Entity clients
@@ -63,7 +79,7 @@ from armis_sdk.entities.site import Site
6379
armis_sdk = ArmisSdk()
6480
6581
async def main():
66-
site = Site(id="1", location="new location")
82+
site = Site(id=1, location="new location")
6783
await armis_sdk.sites.update(site)
6884
6985
asyncio.run(main())

armis_sdk/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from armis_sdk.core.armis_sdk import ArmisSdk
2+
from armis_sdk.core.client_credentials import ClientCredentials

armis_sdk/clients/sites_client.py

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
import universalasync
55

6-
from armis_sdk.clients.network_equipment_client import NetworkEquipmentClient
7-
from armis_sdk.clients.site_integrations_client import SiteIntegrationsClient
86
from armis_sdk.core import response_utils
97
from armis_sdk.core.armis_error import ArmisError
108
from armis_sdk.core.base_entity_client import BaseEntityClient
@@ -18,17 +16,8 @@ class SitesClient(BaseEntityClient):
1816
A client for interacting with sites.
1917
2018
The primary entity for this client is [Site][armis_sdk.entities.site.Site].
21-
22-
Attributes:
23-
network_equipment_client (NetworkEquipmentClient): An instance of [NetworkEquipmentClient][armis_sdk.clients.network_equipment_client.NetworkEquipmentClient]
24-
site_integrations_client (SiteIntegrationsClient): An instance of [SiteIntegrationsClient][armis_sdk.clients.site_integrations_client.SiteIntegrationsClient]
2519
"""
2620

27-
def __init__(self, *args, **kwargs):
28-
super().__init__(*args, **kwargs)
29-
self.network_equipment_client = NetworkEquipmentClient(self._armis_client)
30-
self.site_integrations_client = SiteIntegrationsClient(self._armis_client)
31-
3221
async def create(self, site: Site) -> Site:
3322
"""Create a `Site`.
3423
@@ -69,21 +58,13 @@ async def main():
6958
raise ArmisError("Can't create a site without a name.")
7059

7160
payload = site.model_dump(
72-
by_alias=True,
73-
exclude={"children", "network_equipment_device_ids"},
61+
exclude={"children"},
7462
exclude_none=True,
7563
)
7664
async with self._armis_client.client() as client:
77-
response = await client.post("/api/v1/sites/", json=payload)
65+
response = await client.post("/v3/settings/sites", json=payload)
7866
data = response_utils.get_data_dict(response)
79-
created_site = site.model_copy(update={"id": int(data["id"])}, deep=True)
80-
81-
if site.network_equipment_device_ids:
82-
await self.network_equipment_client.add(
83-
created_site, site.network_equipment_device_ids
84-
)
85-
86-
return created_site
67+
return Site.model_validate(data)
8768

8869
async def delete(self, site: Site):
8970
"""Delete a `Site`.
@@ -112,10 +93,10 @@ async def main():
11293
raise ArmisError("Can't delete a site without an id.")
11394

11495
async with self._armis_client.client() as client:
115-
response = await client.delete(f"/api/v1/sites/{site.id}/")
96+
response = await client.delete(f"/v3/settings/sites/{site.id}")
11697
response_utils.raise_for_status(response)
11798

118-
async def get(self, site_id: str) -> Site:
99+
async def get(self, site_id: int) -> Site:
119100
"""Get a `Site` by its ID.
120101
121102
Args:
@@ -144,7 +125,7 @@ async def main():
144125
```
145126
"""
146127
async with self._armis_client.client() as client:
147-
response = await client.get(f"/api/v1/sites/{site_id}/")
128+
response = await client.get(f"/v3/settings/sites/{site_id}")
148129
data = response_utils.get_data_dict(response)
149130
return Site.model_validate(data)
150131

@@ -218,10 +199,10 @@ async def main():
218199
Site(id=2)
219200
```
220201
"""
221-
async for item in self._list("/api/v1/sites/", "sites", Site):
202+
async for item in self._list("/v3/settings/sites", Site):
222203
yield item
223204

224-
async def update(self, site: Site):
205+
async def update(self, site: Site) -> Site:
225206
"""Update a site's properties.
226207
227208
Args:
@@ -253,23 +234,14 @@ async def main():
253234
)
254235

255236
data = site.model_dump(
256-
by_alias=True,
257-
exclude={
258-
"children",
259-
"id",
260-
"integration_ids",
261-
"network_equipment_device_ids",
262-
},
237+
exclude={"children", "id"},
263238
exclude_none=True,
264239
)
265240

266-
if data:
267-
async with self._armis_client.client() as client:
268-
response = await client.patch(f"/api/v1/sites/{site.id}/", json=data)
269-
response_utils.raise_for_status(response)
270-
271-
if site.network_equipment_device_ids is not None:
272-
await self.network_equipment_client.update(site)
241+
if not data:
242+
return site
273243

274-
if site.integration_ids is not None:
275-
await self.site_integrations_client.update(site)
244+
async with self._armis_client.client() as client:
245+
response = await client.patch(f"/v3/settings/sites/{site.id}", json=data)
246+
data = response_utils.get_data_dict(response)
247+
return Site.model_validate(data)

armis_sdk/core/armis_auth.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from armis_sdk.core import response_utils
88
from armis_sdk.core.armis_error import ArmisError
9+
from armis_sdk.core.client_credentials import ClientCredentials
910

1011
AUTHORIZATION = "Authorization"
1112

@@ -17,16 +18,16 @@ class ArmisAuth(httpx.Auth):
1718
1819
1. Before performing any request check if there's a valid access token.
1920
2. If there is, use it with the `Authorization` header.
20-
3. If there isn't, make a POST request to `/api/v1/access_token/`
21+
3. If there isn't, make a POST request to `/v3/oauth/token`
2122
to generate a new access token.
2223
4. Save the new access token and also use it with the `Authorization` header.
2324
"""
2425

2526
requires_response_body = True
2627

27-
def __init__(self, base_url: str, secret_key: str):
28+
def __init__(self, base_url: str, credentials: ClientCredentials):
2829
self._base_url = base_url
29-
self._secret_key = secret_key
30+
self._credentials = credentials
3031
self._access_token: Optional[str] = None
3132
self._expires_at: Optional[datetime.datetime] = None
3233

@@ -36,7 +37,7 @@ def auth_flow(
3637
if (
3738
self._access_token is None
3839
or self._expires_at is None
39-
or self._expires_at < datetime.datetime.now(datetime.timezone.utc)
40+
or self._expires_at < datetime.datetime.now()
4041
):
4142
access_token_response = yield self._build_access_token_request()
4243
self._update_access_token(access_token_response)
@@ -46,24 +47,33 @@ def auth_flow(
4647
"Something went wrong, there is no access token available."
4748
)
4849

49-
request.headers[AUTHORIZATION] = self._access_token
50+
request.headers[AUTHORIZATION] = f"Bearer {self._access_token}"
5051
response = yield request
5152

5253
if response.status_code == httpx.codes.UNAUTHORIZED:
5354
access_token_response = yield self._build_access_token_request()
5455
self._update_access_token(access_token_response)
5556

56-
request.headers[AUTHORIZATION] = self._access_token
57+
request.headers[AUTHORIZATION] = f"Bearer {self._access_token}"
5758
yield request
5859

5960
def _build_access_token_request(self):
6061
return httpx.Request(
6162
"POST",
62-
f"{self._base_url}/api/v1/access_token/",
63-
json={"secret_key": self._secret_key},
63+
f"{self._base_url}/v3/oauth/token",
64+
json={
65+
"grant_type": "client_credentials",
66+
"vendor_id": self._credentials.vendor_id,
67+
"audience": self._credentials.audience,
68+
"client_id": self._credentials.client_id,
69+
"client_secret": self._credentials.client_secret,
70+
"scopes": self._credentials.scopes,
71+
},
6472
)
6573

6674
def _update_access_token(self, response: httpx.Response):
6775
data = response_utils.get_data_dict(response)
6876
self._access_token = data["access_token"]
69-
self._expires_at = datetime.datetime.fromisoformat(data["expiration_utc"])
77+
self._expires_at = datetime.datetime.now() + datetime.timedelta(
78+
seconds=data["expires_in"]
79+
)

0 commit comments

Comments
 (0)