From 8e33a705f102b2e78038a637242c89b36d3b8502 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 23 Mar 2026 08:24:43 +0000 Subject: [PATCH 1/2] Auto commit from main repo: manual-sync --- armis_sdk/__init__.py | 4 +- armis_sdk/clients/assets_client.py | 31 ++++++------- armis_sdk/clients/collectors_client.py | 15 +++---- armis_sdk/clients/data_export_client.py | 13 +++--- .../device_custom_properties_client.py | 2 +- armis_sdk/clients/sites_client.py | 5 +-- armis_sdk/core/armis_auth.py | 5 +-- armis_sdk/core/armis_client.py | 11 +++-- armis_sdk/core/armis_error.py | 9 ++-- armis_sdk/core/armis_sdk.py | 4 +- armis_sdk/core/base_entity_client.py | 8 ++-- armis_sdk/core/client_credentials.py | 11 +++-- armis_sdk/core/response_utils.py | 5 +-- armis_sdk/entities/asq_rule.py | 6 +-- armis_sdk/entities/asset.py | 6 +-- armis_sdk/entities/data_export/application.py | 3 +- .../data_export/base_exported_entity.py | 3 +- armis_sdk/entities/data_export/data_export.py | 3 +- armis_sdk/entities/data_export/risk_factor.py | 13 +++--- .../entities/data_export/vulnerability.py | 9 ++-- armis_sdk/entities/device.py | 45 +++++++++---------- armis_sdk/entities/device_custom_property.py | 11 +++-- armis_sdk/entities/network_interface.py | 26 +++++------ armis_sdk/entities/site.py | 24 +++++----- pyproject.toml | 2 +- tests/armis_sdk/clients/assets_client_test.py | 10 ++--- .../entities/data_export/risk_factor_test.py | 12 ++--- tests/plugins/auto_setup_plugin.py | 2 +- 28 files changed, 133 insertions(+), 165 deletions(-) diff --git a/armis_sdk/__init__.py b/armis_sdk/__init__.py index 67329af..6cc01ba 100644 --- a/armis_sdk/__init__.py +++ b/armis_sdk/__init__.py @@ -1,2 +1,2 @@ -from armis_sdk.core.armis_sdk import ArmisSdk -from armis_sdk.core.client_credentials import ClientCredentials +from armis_sdk.core.armis_sdk import ArmisSdk # noqa: F401 +from armis_sdk.core.client_credentials import ClientCredentials # noqa: F401 diff --git a/armis_sdk/clients/assets_client.py b/armis_sdk/clients/assets_client.py index 21eb22d..52ca131 100644 --- a/armis_sdk/clients/assets_client.py +++ b/armis_sdk/clients/assets_client.py @@ -1,8 +1,5 @@ import datetime -from typing import AsyncIterator -from typing import Optional -from typing import Type -from typing import Union +from collections.abc import AsyncIterator import universalasync @@ -31,10 +28,10 @@ class AssetsClient(BaseEntityClient): # pylint: disable=too-few-public-methods async def list_by_asset_id( self, - asset_class: Type[AssetT], - asset_ids: Union[list[int], list[str]], + asset_class: type[AssetT], + asset_ids: list[int] | list[str], asset_id_source: AssetIdSource = "ASSET_ID", - fields: Optional[list[str]] = None, + fields: list[str] | None = None, ) -> AsyncIterator[AssetT]: """List assets by asset ID or other identifiers. @@ -81,9 +78,9 @@ async def main(): async def list_by_last_seen( self, - asset_class: Type[AssetT], - last_seen: Union[datetime.datetime, datetime.timedelta], - fields: Optional[list[str]] = None, + asset_class: type[AssetT], + last_seen: datetime.datetime | datetime.timedelta, + fields: list[str] | None = None, ) -> AsyncIterator[AssetT]: """List assets by last seen timestamp. @@ -120,7 +117,7 @@ async def main(): asyncio.run(main()) ``` """ - filter_: dict[str, Union[str, int]] = {"filter_criteria": "LAST_SEEN"} + filter_: dict[str, str | int] = {"filter_criteria": "LAST_SEEN"} if isinstance(last_seen, datetime.datetime): filter_["last_seen_ge"] = last_seen.isoformat() @@ -133,7 +130,7 @@ async def main(): yield item async def list_fields( - self, asset_class: Type[AssetT] + self, asset_class: type[AssetT] ) -> AsyncIterator[AssetFieldDescription]: """List all available fields for a given asset class. @@ -244,7 +241,7 @@ async def main(): def _create_bulk_update_request( cls, asset: Asset, - asset_id: Union[str, int], + asset_id: str | int, field: str, ): request = {"asset_id": asset_id, "key": field} @@ -266,7 +263,7 @@ def _get_asset_id( asset: Asset, index: int, asset_id_source: AssetIdSource, - ) -> Union[str, int]: + ) -> str | int: if isinstance(asset, Device): return cls._get_device_asset_id(asset, index, asset_id_source) @@ -324,8 +321,8 @@ def _is_integration_field(cls, field: str) -> bool: async def _list_assets( self, - asset_class: Type[AssetT], - fields: Optional[list[str]], + asset_class: type[AssetT], + fields: list[str] | None, filter_: dict, ) -> AsyncIterator[AssetT]: fields = fields or sorted(asset_class.all_fields()) @@ -353,7 +350,7 @@ def _validate_asset_class(cls, assets: list[AssetT]): @classmethod def _validate_fields( cls, - asset_class: Type[AssetT], + asset_class: type[AssetT], fields: list[str], allow_model_members=True, ): diff --git a/armis_sdk/clients/collectors_client.py b/armis_sdk/clients/collectors_client.py index 612a39a..0502d17 100644 --- a/armis_sdk/clients/collectors_client.py +++ b/armis_sdk/clients/collectors_client.py @@ -1,8 +1,7 @@ +from collections.abc import AsyncIterator +from collections.abc import Generator import contextlib from typing import IO -from typing import AsyncIterator -from typing import Generator -from typing import Union import httpx import universalasync @@ -25,7 +24,7 @@ class CollectorsClient(BaseEntityClient): async def download_image( self, - destination: Union[str, IO[bytes]], + destination: str | IO[bytes], image_type: CollectorImageType = "OVA", ) -> AsyncIterator[DownloadProgress]: """Download a collector image to a specified destination path / file. @@ -48,12 +47,12 @@ async def main(): collectors_client = CollectorsClient() # Download to a path - async for progress in armis_sdk.collectors.download_image("/tmp/collector.ova"): + async for progress in collectors_client.download_image("/tmp/collector.ova"): print(progress.percent) # Download to a file with open("/tmp/collector.ova", "wb") as file: - async for progress in armis_sdk.collectors.download_image(file): + async for progress in collectors_client.download_image(file): print(progress.percent) asyncio.run(main()) @@ -67,7 +66,7 @@ async def main(): etc. """ collector_image = await self.get_image(image_type=image_type) - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient() as client: # noqa: SIM117 async with client.stream("GET", collector_image.url) as response: response.raise_for_status() total_size = int(response.headers.get("Content-Length", "0")) @@ -114,7 +113,7 @@ async def main(): @classmethod @contextlib.contextmanager def open_file( - cls, destination: Union[str, IO[bytes]] + cls, destination: str | IO[bytes] ) -> Generator[IO[bytes], None, None]: if isinstance(destination, str): with open(destination, "wb") as file: diff --git a/armis_sdk/clients/data_export_client.py b/armis_sdk/clients/data_export_client.py index 66d3142..0022477 100644 --- a/armis_sdk/clients/data_export_client.py +++ b/armis_sdk/clients/data_export_client.py @@ -1,7 +1,6 @@ import asyncio +from collections.abc import AsyncIterator from typing import Any -from typing import AsyncIterator -from typing import Type import pandas import universalasync @@ -17,7 +16,7 @@ @universalasync.wrap class DataExportClient(BaseEntityClient): - async def disable(self, entity: Type[BaseExportedEntity]): + async def disable(self, entity: type[BaseExportedEntity]): """Disable data export of the entity. Args: @@ -40,7 +39,7 @@ async def main(): """ await self.toggle(entity, False) - async def enable(self, entity: Type[BaseExportedEntity]): + async def enable(self, entity: type[BaseExportedEntity]): """Enable data export of the entity. Args: @@ -63,7 +62,7 @@ async def main(): """ await self.toggle(entity, True) - async def iterate(self, entity: Type[T], **kwargs: Any) -> AsyncIterator[T]: + async def iterate(self, entity: type[T], **kwargs: Any) -> AsyncIterator[T]: # pylint: disable=line-too-long """Iterate over the exported data. @@ -136,7 +135,7 @@ async def main(): for _, row in data_frame.iterrows(): yield entity.series_to_model(row) - async def get(self, entity: Type[BaseExportedEntity]) -> DataExport: + async def get(self, entity: type[BaseExportedEntity]) -> DataExport: """Get the `DataExport` of the entity Args: @@ -169,7 +168,7 @@ async def main(): data = response_utils.get_data_dict(response) return DataExport.model_validate(data) - async def toggle(self, entity: Type[BaseExportedEntity], enabled: bool): + async def toggle(self, entity: type[BaseExportedEntity], enabled: bool): """Enable / disable export of an entity. Args: diff --git a/armis_sdk/clients/device_custom_properties_client.py b/armis_sdk/clients/device_custom_properties_client.py index 3d435ec..477a607 100644 --- a/armis_sdk/clients/device_custom_properties_client.py +++ b/armis_sdk/clients/device_custom_properties_client.py @@ -1,4 +1,4 @@ -from typing import AsyncIterator +from collections.abc import AsyncIterator import universalasync diff --git a/armis_sdk/clients/sites_client.py b/armis_sdk/clients/sites_client.py index def82a1..d8e6bf4 100644 --- a/armis_sdk/clients/sites_client.py +++ b/armis_sdk/clients/sites_client.py @@ -1,5 +1,4 @@ -from typing import AsyncIterator -from typing import List +from collections.abc import AsyncIterator import universalasync @@ -129,7 +128,7 @@ async def main(): data = response_utils.get_data_dict(response) return Site.model_validate(data) - async def hierarchy(self) -> List[Site]: + async def hierarchy(self) -> list[Site]: """Create a hierarchy of the tenant's sites, taking into account the parent-child relationships. Returns: diff --git a/armis_sdk/core/armis_auth.py b/armis_sdk/core/armis_auth.py index 3596f3f..4e19d4a 100644 --- a/armis_sdk/core/armis_auth.py +++ b/armis_sdk/core/armis_auth.py @@ -1,6 +1,5 @@ import datetime import typing -from typing import Optional import httpx @@ -28,8 +27,8 @@ class ArmisAuth(httpx.Auth): def __init__(self, base_url: str, credentials: ClientCredentials): self._base_url = base_url self._credentials = credentials - self._access_token: Optional[str] = None - self._expires_at: Optional[datetime.datetime] = None + self._access_token: str | None = None + self._expires_at: datetime.datetime | None = None def auth_flow( self, request: httpx.Request diff --git a/armis_sdk/core/armis_client.py b/armis_sdk/core/armis_client.py index 1e909c2..117a6b6 100644 --- a/armis_sdk/core/armis_client.py +++ b/armis_sdk/core/armis_client.py @@ -1,8 +1,7 @@ import importlib.metadata import os import platform -from typing import AsyncIterator -from typing import Optional +from collections.abc import AsyncIterator from typing import TypeVar import httpx @@ -48,7 +47,7 @@ class ArmisClient: # pylint: disable=too-few-public-methods 4. Proxy configuration via HTTPS_PROXY and HTTP_PROXY environment variables. """ - def __init__(self, credentials: Optional[ClientCredentials] = None): + def __init__(self, credentials: ClientCredentials | None = None): credentials = self._get_credentials(credentials) self._auth = ArmisAuth(API_BASE_URL, credentials) self._user_agent = " ".join(USER_AGENT_PARTS) @@ -61,7 +60,7 @@ def __init__(self, credentials: Optional[ClientCredentials] = None): except ValueError: self._default_backoff = 0 - def client(self, retries: Optional[int] = None, backoff: Optional[float] = None): + def client(self, retries: int | None = None, backoff: float | None = None): retries = retries if retries is not None else self._default_retries backoff = backoff if backoff is not None else self._default_backoff retry = Retry(total=retries, backoff_factor=backoff) @@ -82,7 +81,7 @@ def client(self, retries: Optional[int] = None, backoff: Optional[float] = None) trust_env=True, ) - async def list(self, url: str, body: Optional[dict] = None) -> AsyncIterator[dict]: + async def list(self, url: str, body: dict | None = None) -> AsyncIterator[dict]: """List all items from a paginated endpoint. Args: @@ -131,7 +130,7 @@ async def main(): @classmethod def _get_credentials( - cls, credentials: Optional[ClientCredentials] + cls, credentials: ClientCredentials | None ) -> ClientCredentials: credentials = credentials or ClientCredentials() credentials.vendor_id = credentials.vendor_id or os.getenv(ARMIS_VENDOR_ID) diff --git a/armis_sdk/core/armis_error.py b/armis_sdk/core/armis_error.py index 6c12b9d..9467ec4 100644 --- a/armis_sdk/core/armis_error.py +++ b/armis_sdk/core/armis_error.py @@ -4,16 +4,13 @@ """ import json -from typing import List -from typing import Optional -from typing import Union from httpx import HTTPStatusError from pydantic import BaseModel class DetailItem(BaseModel): - loc: list[Union[str, int]] + loc: list[str | int] msg: str type: str @@ -26,7 +23,7 @@ def __str__(self): class ErrorBody(BaseModel): - detail: Union[str, List[DetailItem]] + detail: str | list[DetailItem] class ArmisError(Exception): @@ -63,7 +60,7 @@ class ResponseError(ArmisError): def __init__( self, error_body: ErrorBody, - response_errors: Optional[List[HTTPStatusError]] = None, + response_errors: list[HTTPStatusError] | None = None, ): super().__init__(self._get_message(error_body)) self.response_errors = response_errors diff --git a/armis_sdk/core/armis_sdk.py b/armis_sdk/core/armis_sdk.py index e4653d4..0ae5240 100644 --- a/armis_sdk/core/armis_sdk.py +++ b/armis_sdk/core/armis_sdk.py @@ -1,5 +1,3 @@ -from typing import Optional - from armis_sdk.clients.assets_client import AssetsClient from armis_sdk.clients.collectors_client import CollectorsClient from armis_sdk.clients.data_export_client import DataExportClient @@ -41,7 +39,7 @@ async def main(): ``` """ - def __init__(self, credentials: Optional[ClientCredentials] = None): + def __init__(self, credentials: ClientCredentials | None = None): self.client: ArmisClient = ArmisClient(credentials=credentials) self.assets: AssetsClient = AssetsClient(self.client) self.collectors: CollectorsClient = CollectorsClient(self.client) diff --git a/armis_sdk/core/base_entity_client.py b/armis_sdk/core/base_entity_client.py index 0ae2327..4dbb02d 100644 --- a/armis_sdk/core/base_entity_client.py +++ b/armis_sdk/core/base_entity_client.py @@ -1,6 +1,4 @@ -from typing import AsyncIterator -from typing import Optional -from typing import Type +from collections.abc import AsyncIterator import universalasync @@ -10,12 +8,12 @@ class BaseEntityClient: # pylint: disable=too-few-public-methods - def __init__(self, armis_client: Optional[ArmisClient] = None) -> None: + def __init__(self, armis_client: ArmisClient | None = None) -> None: self._armis_client = armis_client or ArmisClient() @universalasync.async_to_sync_wraps async def _list( - self, url: str, model: Type[BaseEntityT] + self, url: str, model: type[BaseEntityT] ) -> AsyncIterator[BaseEntityT]: async for item in self._armis_client.list(url): yield model.model_validate(item) diff --git a/armis_sdk/core/client_credentials.py b/armis_sdk/core/client_credentials.py index e187d6e..23c7a4c 100644 --- a/armis_sdk/core/client_credentials.py +++ b/armis_sdk/core/client_credentials.py @@ -1,11 +1,10 @@ import dataclasses -from typing import Optional @dataclasses.dataclass class ClientCredentials: - audience: Optional[str] = None - client_id: Optional[str] = None - client_secret: Optional[str] = None - vendor_id: Optional[str] = None - scopes: Optional[list[str]] = None + audience: str | None = None + client_id: str | None = None + client_secret: str | None = None + vendor_id: str | None = None + scopes: list[str] | None = None diff --git a/armis_sdk/core/response_utils.py b/armis_sdk/core/response_utils.py index 3732dbf..1c8e568 100644 --- a/armis_sdk/core/response_utils.py +++ b/armis_sdk/core/response_utils.py @@ -1,6 +1,5 @@ import json from json import JSONDecodeError -from typing import Type from typing import TypeVar import httpx @@ -18,7 +17,7 @@ def get_data( response: httpx.Response, - data_type: Type[DataTypeT], + data_type: type[DataTypeT], ) -> DataTypeT: raise_for_status(response) data = parse_response(response, dict) @@ -36,7 +35,7 @@ def get_data_dict(response: httpx.Response): def parse_response( response: httpx.Response, - data_type: Type[DataTypeT], + data_type: type[DataTypeT], ) -> DataTypeT: try: response_data = response.json() diff --git a/armis_sdk/entities/asq_rule.py b/armis_sdk/entities/asq_rule.py index d28ad4a..b0a26c8 100644 --- a/armis_sdk/entities/asq_rule.py +++ b/armis_sdk/entities/asq_rule.py @@ -1,5 +1,3 @@ -from typing import List -from typing import Optional from typing import Union from pydantic import Field @@ -41,10 +39,10 @@ class AsqRule(BaseEntity): ``` """ - and_: Optional[List[Union[str, "AsqRule"]]] = Field(alias="and", default=None) + and_: list[Union[str, "AsqRule"]] | None = Field(alias="and", default=None) """Rules that all must match.""" - or_: Optional[List[Union[str, "AsqRule"]]] = Field(alias="or", default=None) + or_: list[Union[str, "AsqRule"]] | None = Field(alias="or", default=None) """Rules that at least one of them must match.""" @classmethod diff --git a/armis_sdk/entities/asset.py b/armis_sdk/entities/asset.py index 6442bc7..1a8e885 100644 --- a/armis_sdk/entities/asset.py +++ b/armis_sdk/entities/asset.py @@ -1,9 +1,7 @@ import collections from typing import Any from typing import ClassVar -from typing import DefaultDict from typing import Literal -from typing import Type from typing import TypeVar from pydantic import Field @@ -26,8 +24,8 @@ class Asset(BaseEntity): """Integration properties of the asset. Values can by anything.""" @classmethod - def from_search_result(cls: Type[AssetT], data: dict) -> AssetT: - fields: DefaultDict[str, Any] = collections.defaultdict(dict) + def from_search_result(cls: type[AssetT], data: dict) -> AssetT: + fields: collections.defaultdict[str, Any] = collections.defaultdict(dict) for key, value in data["fields"].items(): if len(parts := key.split(".", 1)) > 1: part1, part2 = parts diff --git a/armis_sdk/entities/data_export/application.py b/armis_sdk/entities/data_export/application.py index f9016b7..5f6076e 100644 --- a/armis_sdk/entities/data_export/application.py +++ b/armis_sdk/entities/data_export/application.py @@ -1,6 +1,5 @@ import datetime from typing import ClassVar -from typing import Optional import pandas @@ -38,7 +37,7 @@ class Application(BaseExportedEntity): **Example**: `30.0.1599.40` """ - cpe: Optional[str] + cpe: str | None """ The CPE (Common Platform Enumeration) of the application diff --git a/armis_sdk/entities/data_export/base_exported_entity.py b/armis_sdk/entities/data_export/base_exported_entity.py index 13ed734..4f0eb09 100644 --- a/armis_sdk/entities/data_export/base_exported_entity.py +++ b/armis_sdk/entities/data_export/base_exported_entity.py @@ -1,5 +1,4 @@ import abc -from typing import Type from typing import TypeVar import pandas @@ -11,7 +10,7 @@ class BaseExportedEntity(BaseModel, abc.ABC): @classmethod @abc.abstractmethod - def series_to_model(cls: Type[T], series: pandas.Series) -> T: ... + def series_to_model(cls: type[T], series: pandas.Series) -> T: ... @property @abc.abstractmethod diff --git a/armis_sdk/entities/data_export/data_export.py b/armis_sdk/entities/data_export/data_export.py index e46476d..abaed74 100644 --- a/armis_sdk/entities/data_export/data_export.py +++ b/armis_sdk/entities/data_export/data_export.py @@ -1,6 +1,5 @@ import datetime from typing import Literal -from typing import Optional from pydantic import BaseModel from pydantic import Field @@ -24,5 +23,5 @@ class DataExport(BaseModel): urls: list[str] """URLs to the files that contain the exported data.""" - urls_creation_time: Optional[datetime.datetime] = Field(strict=False) + urls_creation_time: datetime.datetime | None = Field(strict=False) """The creation time of the URLs.""" diff --git a/armis_sdk/entities/data_export/risk_factor.py b/armis_sdk/entities/data_export/risk_factor.py index b3e07b9..ce68d59 100644 --- a/armis_sdk/entities/data_export/risk_factor.py +++ b/armis_sdk/entities/data_export/risk_factor.py @@ -1,7 +1,6 @@ import datetime import json from typing import ClassVar -from typing import Optional import pandas from pydantic import BaseModel @@ -110,13 +109,13 @@ class RiskFactor(BaseExportedEntity): **Example**: `OPEN` """ - status_update_time: Optional[datetime.datetime] + status_update_time: datetime.datetime | None """When was the status last changed""" - status_updated_by_user_id: Optional[int] + status_updated_by_user_id: int | None """Which used id last changed the status""" - status_update_reason: Optional[str] + status_update_reason: str | None """ The reason for the status change @@ -133,11 +132,11 @@ def series_to_model(cls, series: pandas.Series) -> "RiskFactor": score=series.loc["score"], status=series.loc["status"], group=series.loc["group"], - remediation_type=series.loc["remidiation"], - remediation_description=series.loc["remidiation_description"], + remediation_type=series.loc["remediation"], + remediation_description=series.loc["remediation_description"], remediation_recommended_actions=[ RiskFactorRecommendedAction(**item) - for item in json.loads(series.loc["remoidiation_recommended_actions"]) + for item in json.loads(series.loc["remediation_recommended_actions"]) ], first_seen=series.loc["first_seen"].to_pydatetime(), last_seen=series.loc["last_seen"].to_pydatetime(), diff --git a/armis_sdk/entities/data_export/vulnerability.py b/armis_sdk/entities/data_export/vulnerability.py index 2327c23..71c2c15 100644 --- a/armis_sdk/entities/data_export/vulnerability.py +++ b/armis_sdk/entities/data_export/vulnerability.py @@ -1,6 +1,5 @@ import datetime from typing import ClassVar -from typing import Optional import pandas @@ -24,7 +23,7 @@ class Vulnerability(BaseExportedEntity): **Example**: `CVE-2025-53799` """ - advisory_id: Optional[str] + advisory_id: str | None """ The id of the advisory @@ -38,7 +37,7 @@ class Vulnerability(BaseExportedEntity): **Example**: `["VERSION_UPDATE"]` """ - avm_rating: Optional[str] + avm_rating: str | None """The Armis AVM (Asset Vulnerability Management) rating of the vulnerability""" match_source: list[str] @@ -55,10 +54,10 @@ class Vulnerability(BaseExportedEntity): **Example**: `Open` """ - status_change_time: Optional[datetime.datetime] + status_change_time: datetime.datetime | None """When was the status last changed""" - status_change_reason: Optional[str] + status_change_reason: str | None """The reason for the status change""" @classmethod diff --git a/armis_sdk/entities/device.py b/armis_sdk/entities/device.py index 9fba2ad..731d9a1 100644 --- a/armis_sdk/entities/device.py +++ b/armis_sdk/entities/device.py @@ -1,7 +1,6 @@ import datetime from typing import ClassVar from typing import Literal -from typing import Optional from pydantic import Field @@ -15,104 +14,104 @@ class Device(Asset): # pylint: disable=line-too-long asset_type: ClassVar[Literal["DEVICE"]] = "DEVICE" - boundaries: Optional[list[Boundary]] = None + boundaries: list[Boundary] | None = None """The list of boundaries the device belongs to.""" - brand: Optional[str] = None + brand: str | None = None """ The device brand. Example: `Apple` """ - category: Optional[str] = None + category: str | None = None """ The device category. Example: `Handheld` """ - device_id: Optional[int] = None + device_id: int | None = None """The unique identifier given to the device by thr Armis engine.""" - display: Optional[str] = None + display: str | None = None """ The display text of the device. Example: `My iPhone` """ - first_seen: Optional[datetime.datetime] = Field(strict=False, default=None) + first_seen: datetime.datetime | None = Field(strict=False, default=None) """When was the device first seen.""" - ipv4_addresses: Optional[list[str]] = None + ipv4_addresses: list[str] | None = None """The list of IPv4 addresses of the device""" - ipv6_addresses: Optional[list[str]] = None + ipv6_addresses: list[str] | None = None """The list of IPv6 addresses of the device""" - last_seen: Optional[datetime.datetime] = Field(strict=False, default=None) + last_seen: datetime.datetime | None = Field(strict=False, default=None) """When was the device last seen.""" - mac_addresses: Optional[list[str]] = None + mac_addresses: list[str] | None = None """The list of MAC addresses of the device""" - model: Optional[str] = None + model: str | None = None """ The model of the device. Example: `iPhone 17` """ - names: Optional[list[str]] = None + names: list[str] | None = None """ List of names of the device Example: `["My iPhone 17", "Jane's iPhone"]` """ - network_interfaces: Optional[list[NetworkInterface]] = None + network_interfaces: list[NetworkInterface] | None = None """List of network interfaces detected on the device.""" - os_name: Optional[str] = None + os_name: str | None = None """ The OS name running on the device. Example: `iOS` """ - os_version: Optional[str] = None + os_version: str | None = None """ The OS version running on the device. Example: `17` """ - purdue_level: Optional[float] = None + purdue_level: float | None = None """ The purdue level of the devices. See [Wikipedia](https://en.wikipedia.org/wiki/Purdue_Enterprise_Reference_Architecture) article for more details. Example: `4` """ - risk_level: Optional[int] = Field(ge=0, le=1000, default=None) + risk_level: int | None = Field(ge=0, le=1000, default=None) """The risk level given to the device by the Armis engine, between `0` and `100`.""" - serial_numbers: Optional[list[str]] = None + serial_numbers: list[str] | None = None """The list of serial numbers of the device""" - site: Optional[Site] = None + site: Site | None = None """The site in which this device was last seen.""" - tags: Optional[list[str]] = None + tags: list[str] | None = None """The tags given to the devices.""" - type: Optional[str] = None + type: str | None = None """ The type of the device. Example: `Mobile Phones` """ - visibility: Optional[Literal["Full", "Limited"]] = None + visibility: Literal["Full", "Limited"] | None = None """Whether the device is fully visibly or limited.""" diff --git a/armis_sdk/entities/device_custom_property.py b/armis_sdk/entities/device_custom_property.py index dec7f24..20f8391 100644 --- a/armis_sdk/entities/device_custom_property.py +++ b/armis_sdk/entities/device_custom_property.py @@ -1,6 +1,5 @@ import datetime from typing import Literal -from typing import Optional from pydantic import Field @@ -8,7 +7,7 @@ class DeviceCustomProperty(BaseEntity): - id: Optional[int] = None + id: int | None = None """The id of the property.""" name: str = Field(max_length=40, pattern=r"^[\w_]*$") @@ -18,7 +17,7 @@ class DeviceCustomProperty(BaseEntity): Example: `Size` """ - description: Optional[str] = Field(max_length=250, default=None) + description: str | None = Field(max_length=250, default=None) """ The description of the property. @@ -39,15 +38,15 @@ class DeviceCustomProperty(BaseEntity): Example: `enum` """ - allowed_values: Optional[list[str]] = None + allowed_values: list[str] | None = None """ The allowed values of the property when the 'type' is 'enum'. Example: `["s", "m", "l"]` """ - created_by: Optional[str] = Field(max_length=50, default=None) + created_by: str | None = Field(max_length=50, default=None) """Who / what created the property.""" - creation_time: Optional[datetime.datetime] = Field(strict=False, default=None) + creation_time: datetime.datetime | None = Field(strict=False, default=None) """The creation time of the property.""" diff --git a/armis_sdk/entities/network_interface.py b/armis_sdk/entities/network_interface.py index 0f3086b..47f732e 100644 --- a/armis_sdk/entities/network_interface.py +++ b/armis_sdk/entities/network_interface.py @@ -1,5 +1,3 @@ -from typing import Optional - from armis_sdk.core.base_entity import BaseEntity @@ -9,41 +7,41 @@ class NetworkInterface(BaseEntity): A `NetworkInterface` represents a physical network card of a [Device][armis_sdk.entities.device.Device]. """ - alias: Optional[str] + alias: str | None """The alias of the interface.""" - brand: Optional[str] + brand: str | None """The brand of the interface.""" - broadcast_ssid: Optional[str] + broadcast_ssid: str | None """The last SSID broadcasted by the interface.""" channels: list[int] """The channels that the interface uses to transmit.""" - description: Optional[str] + description: str | None """The description of the interface""" - hidden_broadcast_ssid: Optional[bool] + hidden_broadcast_ssid: bool | None """Is the broadcasted SSID hidden.""" - ipv4_address: Optional[str] + ipv4_address: str | None """The last IPv4 address associated with the interface.""" - ipv6_address: Optional[str] + ipv6_address: str | None """The last IPv6 address associated with the interface.""" - last_connected_ssid: Optional[str] + last_connected_ssid: str | None """The SSID the interface last connected to.""" - mac_address: Optional[str] + mac_address: str | None """The MAC address of the interface.""" - name: Optional[str] + name: str | None """The name of the interface.""" - type: Optional[str] + type: str | None """The type of the interface.""" - vlan: Optional[int] + vlan: int | None """The VLAN of the interface.""" diff --git a/armis_sdk/entities/site.py b/armis_sdk/entities/site.py index 2ab5825..489d0fb 100644 --- a/armis_sdk/entities/site.py +++ b/armis_sdk/entities/site.py @@ -1,8 +1,6 @@ import json from typing import Annotated from typing import Any -from typing import List -from typing import Optional from pydantic import BeforeValidator from pydantic import Field @@ -24,13 +22,13 @@ class Site(BaseEntity): The `Site` entity represents a physical location at the customer's environment. """ - id: Optional[int] = Field(strict=False, default=None) + id: int | None = Field(strict=False, default=None) """The id of the site.""" - name: Optional[str] = None + name: str | None = None """The name of the site.""" - lat: Optional[float] = Field(frozen=True, default=None) + lat: float | None = Field(frozen=True, default=None) """ The latitude coordinate of the physical location of the site on earth. @@ -41,7 +39,7 @@ class Site(BaseEntity): """ - lng: Optional[float] = Field(frozen=True, default=None) + lng: float | None = Field(frozen=True, default=None) """ The longitude coordinate of the physical location of the site on earth. @@ -51,7 +49,7 @@ class Site(BaseEntity): Example: `-122.4007818` """ - location: Optional[str] = None + location: str | None = None """ The name of the location of the site, such as an address. @@ -61,26 +59,26 @@ class Site(BaseEntity): [`lng`][armis_sdk.entities.site.Site.lng] are automatically derived from it. """ - parent_id: Optional[int] = Field(strict=False, default=None) + parent_id: int | None = Field(strict=False, default=None) """The id of the parent site.""" - tier: Optional[str] = None + tier: str | None = None """The tier of the site.""" - asq_rule: Optional[AsqRule] = Field(default=None) + asq_rule: AsqRule | None = Field(default=None) """The ASQ rule of the site.""" network_equipment_device_ids: Annotated[ - Optional[List[int]], BeforeValidator(ensure_list_of_ints) + list[int] | None, BeforeValidator(ensure_list_of_ints) ] = None """The ids of network equipment devices associated with the site.""" integration_ids: Annotated[ - Optional[List[int]], BeforeValidator(ensure_list_of_ints) + list[int] | None, BeforeValidator(ensure_list_of_ints) ] = None """The ids of the integration associated with the site.""" - children: List["Site"] = Field(default_factory=list) + children: list["Site"] = Field(default_factory=list) """The sub-sites that are directly under this site (their `parent_id` will match this site's `id`).""" diff --git a/pyproject.toml b/pyproject.toml index 3c7ba2c..3bff884 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "armis_sdk" -version = "1.1.0" +version = "1.1.1" description = "The Armis SDK is a package that encapsulates common use-cases for interacting with the Armis platform." authors = [ { name = "Shai Lachmanovich", email = "shai@armis.com" }, diff --git a/tests/armis_sdk/clients/assets_client_test.py b/tests/armis_sdk/clients/assets_client_test.py index a631325..5f290af 100644 --- a/tests/armis_sdk/clients/assets_client_test.py +++ b/tests/armis_sdk/clients/assets_client_test.py @@ -2,6 +2,7 @@ import pytest import pytest_httpx +from tests.armis_sdk.clients import assets_test_data from armis_sdk.clients.assets_client import AssetsClient from armis_sdk.core.armis_error import ArmisError @@ -9,7 +10,6 @@ from armis_sdk.entities.asset import Asset from armis_sdk.entities.asset_field_description import AssetFieldDescription from armis_sdk.entities.device import Device -from tests.armis_sdk.clients import assets_test_data pytest_plugins = ["tests.plugins.auto_setup_plugin"] @@ -374,10 +374,10 @@ async def test_update_with_failed_requests(httpx_mock: pytest_httpx.HTTPXMock): with pytest.raises( BulkUpdateError, match=( - "Failed to update item at index 2. " - 'Request: {"asset_id": 2, "key": "custom.MyField1", ' - '"operation": "SET", "value": "value3"}, ' - 'Response: {"status": 400, "error": "Bad Request"}' + r"Failed to update item at index 2\. " + r'Request: \{"asset_id": 2, "key": "custom\.MyField1", ' + r'"operation": "SET", "value": "value3"\}, ' + r'Response: \{"status": 400, "error": "Bad Request"\}' ), ): await assets_client.update(assets, fields) diff --git a/tests/armis_sdk/entities/data_export/risk_factor_test.py b/tests/armis_sdk/entities/data_export/risk_factor_test.py index 24a8e64..b35102b 100644 --- a/tests/armis_sdk/entities/data_export/risk_factor_test.py +++ b/tests/armis_sdk/entities/data_export/risk_factor_test.py @@ -17,9 +17,9 @@ "score", "status", "group", - "remidiation", - "remidiation_description", - "remoidiation_recommended_actions", + "remediation", + "remediation_description", + "remediation_recommended_actions", "first_seen", "last_seen", "status_update_time", @@ -39,9 +39,9 @@ def test_series_to_model(): "score": 2, "status": "OPEN", "group": "group1", - "remidiation": "remediation1", - "remidiation_description": "remediation_description1", - "remoidiation_recommended_actions": json.dumps( + "remediation": "remediation1", + "remediation_description": "remediation_description1", + "remediation_recommended_actions": json.dumps( [ { "id": 1, diff --git a/tests/plugins/auto_setup_plugin.py b/tests/plugins/auto_setup_plugin.py index f2aa9e3..c0bc741 100644 --- a/tests/plugins/auto_setup_plugin.py +++ b/tests/plugins/auto_setup_plugin.py @@ -5,4 +5,4 @@ @pytest.fixture(autouse=True) def auto_setup(setup_env_variables, authorized): # pylint: disable=unused-argument - yield + return From a7b97c8a644e3e5f4f518f79d8d4ad6a3bf6d3be Mon Sep 17 00:00:00 2001 From: Shai Lachmanovich Date: Mon, 23 Mar 2026 09:27:51 +0100 Subject: [PATCH 2/2] isort --- armis_sdk/clients/collectors_client.py | 2 +- tests/armis_sdk/clients/assets_client_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/armis_sdk/clients/collectors_client.py b/armis_sdk/clients/collectors_client.py index 0502d17..0fe5b41 100644 --- a/armis_sdk/clients/collectors_client.py +++ b/armis_sdk/clients/collectors_client.py @@ -1,6 +1,6 @@ +import contextlib from collections.abc import AsyncIterator from collections.abc import Generator -import contextlib from typing import IO import httpx diff --git a/tests/armis_sdk/clients/assets_client_test.py b/tests/armis_sdk/clients/assets_client_test.py index 5f290af..3a4ab3e 100644 --- a/tests/armis_sdk/clients/assets_client_test.py +++ b/tests/armis_sdk/clients/assets_client_test.py @@ -2,7 +2,6 @@ import pytest import pytest_httpx -from tests.armis_sdk.clients import assets_test_data from armis_sdk.clients.assets_client import AssetsClient from armis_sdk.core.armis_error import ArmisError @@ -10,6 +9,7 @@ from armis_sdk.entities.asset import Asset from armis_sdk.entities.asset_field_description import AssetFieldDescription from armis_sdk.entities.device import Device +from tests.armis_sdk.clients import assets_test_data pytest_plugins = ["tests.plugins.auto_setup_plugin"]