From f5eff8e3883b4b4f568748a58bcb939d62244586 Mon Sep 17 00:00:00 2001 From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 22:39:13 +0000 Subject: [PATCH] SDK regeneration --- .fern/metadata.json | 9 + .github/workflows/ci.yml | 9 +- README.md | 42 ++- poetry.lock | 48 +++- pyproject.toml | 5 +- reference.md | 119 +++++++-- src/anduril/__init__.py | 9 +- src/anduril/client.py | 8 +- src/anduril/core/__init__.py | 5 + src/anduril/core/client_wrapper.py | 18 +- src/anduril/core/custom_pagination.py | 152 +++++++++++ src/anduril/core/pagination.py | 28 +- src/anduril/core/pydantic_utilities.py | 4 +- src/anduril/entities/client.py | 48 +++- src/anduril/entities/raw_client.py | 192 ++++++++------ .../types/stream_entities_response.py | 8 +- src/anduril/errors/bad_request_error.py | 2 +- src/anduril/errors/content_too_large_error.py | 2 +- .../errors/insufficient_storage_error.py | 2 +- src/anduril/errors/internal_server_error.py | 2 +- src/anduril/errors/not_found_error.py | 2 +- src/anduril/errors/request_timeout_error.py | 2 +- src/anduril/errors/too_many_requests_error.py | 2 +- src/anduril/errors/unauthorized_error.py | 2 +- src/anduril/objects/client.py | 30 +-- src/anduril/objects/raw_client.py | 170 ++++++------- src/anduril/tasks/client.py | 136 +++++++++- src/anduril/tasks/raw_client.py | 240 +++++++++++++----- src/anduril/types/__init__.py | 33 +-- src/anduril/types/agent.py | 2 +- src/anduril/types/agent_request.py | 18 +- src/anduril/types/allocation.py | 4 +- src/anduril/types/angle_of_arrival.py | 4 +- src/anduril/types/cancel_request.py | 7 +- src/anduril/types/complete_request.py | 3 +- src/anduril/types/entity.py | 7 +- src/anduril/types/entity_event.py | 4 +- src/anduril/types/entity_event_response.py | 4 - src/anduril/types/entity_manager_pose.py | 37 +++ .../{t_mat_3.py => entity_manager_t_mat3.py} | 2 +- src/anduril/types/entity_stream_event.py | 5 - src/anduril/types/execute_request.py | 10 +- src/anduril/types/field_of_view.py | 8 +- src/anduril/types/indicators.py | 4 +- src/anduril/types/lla.py | 2 +- src/anduril/types/location_uncertainty.py | 14 +- .../{mil_std_2525_c.py => mil_std2525c.py} | 0 src/anduril/types/{mode_5.py => mode5.py} | 10 +- ...py => mode5mode5interrogation_response.py} | 0 src/anduril/types/override.py | 3 +- src/anduril/types/overrides.py | 3 +- src/anduril/types/owner.py | 2 +- src/anduril/types/principal.py | 2 +- src/anduril/types/relations.py | 9 +- src/anduril/types/replication.py | 4 +- src/anduril/types/symbology.py | 4 +- src/anduril/types/system.py | 2 +- src/anduril/types/{t_mat_2.py => t_mat2.py} | 0 src/anduril/types/task.py | 43 ++-- src/anduril/types/task_entity.py | 12 +- src/anduril/types/task_error.py | 8 +- src/anduril/types/task_query_results.py | 15 +- src/anduril/types/task_status.py | 22 +- src/anduril/types/task_version.py | 14 +- src/anduril/types/tracked.py | 2 +- src/anduril/types/transponder_codes.py | 22 +- ...nder_codes_mode4interrogation_response.py} | 0 .../{u_int_32_range.py => u_int32range.py} | 0 68 files changed, 1131 insertions(+), 510 deletions(-) create mode 100644 .fern/metadata.json create mode 100644 src/anduril/core/custom_pagination.py create mode 100644 src/anduril/types/entity_manager_pose.py rename src/anduril/types/{t_mat_3.py => entity_manager_t_mat3.py} (94%) rename src/anduril/types/{mil_std_2525_c.py => mil_std2525c.py} (100%) rename src/anduril/types/{mode_5.py => mode5.py} (72%) rename src/anduril/types/{mode_5_mode_5_interrogation_response.py => mode5mode5interrogation_response.py} (100%) rename src/anduril/types/{t_mat_2.py => t_mat2.py} (100%) rename src/anduril/types/{transponder_codes_mode_4_interrogation_response.py => transponder_codes_mode4interrogation_response.py} (100%) rename src/anduril/types/{u_int_32_range.py => u_int32range.py} (100%) diff --git a/.fern/metadata.json b/.fern/metadata.json new file mode 100644 index 0000000..59b2e79 --- /dev/null +++ b/.fern/metadata.json @@ -0,0 +1,9 @@ +{ + "cliVersion": "3.5.0", + "generatorName": "fernapi/fern-python-sdk", + "generatorVersion": "4.38.4", + "generatorConfig": { + "client_class_name": "Lattice", + "package_name": "anduril" + } +} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9818c03..e4588ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,4 @@ name: ci - on: [push] jobs: compile: @@ -10,7 +9,7 @@ jobs: - name: Set up python uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.9 - name: Bootstrap poetry run: | curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 @@ -26,7 +25,7 @@ jobs: - name: Set up python uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.9 - name: Bootstrap poetry run: | curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 @@ -34,7 +33,7 @@ jobs: run: poetry install - name: Test - run: poetry run pytest -rP . + run: poetry run pytest -rP -n auto . publish: needs: [compile, test] @@ -46,7 +45,7 @@ jobs: - name: Set up python uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.9 - name: Bootstrap poetry run: | curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 diff --git a/README.md b/README.md index 435539e..fce708c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,24 @@ The Lattice SDK Python library provides convenient access to the Lattice SDK APIs from Python. +## Table of Contents + +- [Documentation](#documentation) +- [Requirements](#requirements) +- [Installation](#installation) +- [Support](#support) +- [Reference](#reference) +- [Usage](#usage) +- [Async Client](#async-client) +- [Exception Handling](#exception-handling) +- [Streaming](#streaming) +- [Pagination](#pagination) +- [Advanced](#advanced) + - [Access Raw Response Data](#access-raw-response-data) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Custom Client](#custom-client) + ## Documentation API reference documentation is available [here](https://developer.anduril.com/). @@ -103,21 +121,12 @@ for chunk in response.data: Paginated requests will return a `SyncPager` or `AsyncPager`, which can be used as generators for the underlying object. ```python -import datetime - from anduril import Lattice client = Lattice( token="YOUR_TOKEN", ) -response = client.objects.list_objects( - prefix="prefix", - since_timestamp=datetime.datetime.fromisoformat( - "2024-01-15 09:30:00+00:00", - ), - page_token="pageToken", - all_objects_in_mesh=True, -) +response = client.objects.list_objects() for item in response: yield item # alternatively, you can paginate page-by-page @@ -125,6 +134,15 @@ for page in response.iter_pages(): yield page ``` +```python +# You can also iterate through pages and access the typed response per page +pager = client.objects.list_objects(...) +for page in pager.iter_pages(): + print(page.response) # access the typed response for each page + for item in page: + print(item) +``` + ## Advanced ### Access Raw Response Data @@ -142,11 +160,11 @@ response = client.entities.with_raw_response.long_poll_entity_events(...) print(response.headers) # access the response headers print(response.data) # access the underlying object pager = client.objects.list_objects(...) -print(pager.response.headers) # access the response headers for the first page +print(pager.response) # access the typed response for the first page for item in pager: print(item) # access the underlying object(s) for page in pager.iter_pages(): - print(page.response.headers) # access the response headers for each page + print(page.response) # access the typed response for each page for item in page: print(item) # access the underlying object(s) with client.entities.with_raw_response.stream_entities(...) as response: diff --git a/poetry.lock b/poetry.lock index fef3795..9decbf8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -38,13 +38,13 @@ trio = ["trio (>=0.26.1)"] [[package]] name = "certifi" -version = "2025.10.5" +version = "2025.11.12" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" files = [ - {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, - {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, + {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"}, + {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"}, ] [[package]] @@ -60,13 +60,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.3.0" +version = "1.3.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, - {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, + {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, + {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, ] [package.dependencies] @@ -75,6 +75,20 @@ typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "execnet" +version = "2.1.2" +description = "execnet: rapid multi-Python deployment" +optional = false +python-versions = ">=3.8" +files = [ + {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, + {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, +] + +[package.extras] +testing = ["hatch", "pre-commit", "pytest", "tox"] + [[package]] name = "h11" version = "0.16.0" @@ -418,6 +432,26 @@ pytest = ">=7.0.0,<9" docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] +[[package]] +name = "pytest-xdist" +version = "3.6.1" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, + {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, +] + +[package.dependencies] +execnet = ">=2.1" +pytest = ">=7.0.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -557,4 +591,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "8551b871abee465e23fb0966d51f2c155fd257b55bdcb0c02d095de19f92f358" +content-hash = "bcf31a142c86d9e556553c8c260a93b563ac64a043076dbd48b26111d422c26e" diff --git a/pyproject.toml b/pyproject.toml index b35d398..60f47db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "anduril-lattice-sdk" [tool.poetry] name = "anduril-lattice-sdk" -version = "3.0.0" +version = "4.0.0" description = "HTTP clients for the Anduril Lattice SDK" readme = "README.md" authors = [ @@ -35,7 +35,7 @@ packages = [ { include = "anduril", from = "src"} ] -[project.urls] +[tool.poetry.urls] Documentation = 'https://developer.anduril.com' Homepage = 'https://www.anduril.com/lattice-sdk/' Repository = 'https://github.com/anduril/lattice-sdk-python' @@ -51,6 +51,7 @@ typing_extensions = ">= 4.0.0" mypy = "==1.13.0" pytest = "^7.4.0" pytest-asyncio = "^0.23.5" +pytest-xdist = "^3.6.1" python-dateutil = "^2.9.0" types-python-dateutil = "^2.9.0.20240316" ruff = "==0.11.5" diff --git a/reference.md b/reference.md index 26962ea..343a9e3 100644 --- a/reference.md +++ b/reference.md @@ -56,8 +56,8 @@ client.entities.publish_entity() **entity_id:** `typing.Optional[str]` -A Globally Unique Identifier (GUID) for your entity. If this field is empty, the Entity Manager API - automatically generates an ID when it creates the entity. +A Globally Unique Identifier (GUID) for your entity. This is a required + field. @@ -741,7 +741,25 @@ client.entities.long_poll_entity_events(
-Establishes a persistent connection to stream entity events as they occur. +Establishes a server-sent events (SSE) connection that streams entity data in real-time. +This is a one-way connection from server to client that follows the SSE protocol with text/event-stream content type. + +This endpoint enables clients to maintain a real-time view of the common operational picture (COP) +by first streaming all pre-existing entities that match filter criteria, then continuously delivering +updates as entities are created, modified, or deleted. + +The server first sends events with type PREEXISTING for all live entities matching the filter that existed before the stream was open, +then streams CREATE events for newly created entities, UPDATE events when existing entities change, and DELETED events when entities are removed. The stream remains open +indefinitely unless preExistingOnly is set to true. + +Heartbeat messages can be configured to maintain connection health and detect disconnects by setting the heartbeatIntervalMS +parameter. These heartbeats help keep the connection alive and allow clients to verify the server is still responsive. + +Clients can optimize bandwidth usage by specifying which entity components they need populated using the componentsToInclude parameter. +This allows receiving only relevant data instead of complete entities. + +The connection automatically recovers from temporary disconnections, resuming the stream where it left off. Unlike polling approaches, +this provides real-time updates with minimal latency and reduced server load.
@@ -828,8 +846,14 @@ for chunk in response.data:
-Submit a request to create a task and schedule it for delivery. Tasks, once delivered, will -be asynchronously updated by their destined agent. +Creates a new Task in the system with the specified parameters. + +This method initiates a new task with a unique ID (either provided or auto-generated), +sets the initial task state to STATUS_CREATED, and establishes task ownership. The task +can be assigned to a specific agent through the Relations field. + +Once created, a task enters the lifecycle workflow and can be tracked, updated, and managed +through other Tasks API endpoints.
@@ -892,7 +916,7 @@ GUID. Will reject if supplied Task ID does not match [A-Za-z0-9_-.]{5,36}.
-**specification:** `typing.Optional[GoogleProtobufAny]` — Full set of task parameters. +**specification:** `typing.Optional[GoogleProtobufAny]` — The path for the Protobuf task definition, and the complete task data.
@@ -958,6 +982,27 @@ task. For example, an entity Objective, an entity Keep In Zone, etc.
+#### 📝 Description + +
+
+ +
+
+ +Retrieves a specific Task by its ID, with options to select a particular task version or view. + +This method returns detailed information about a task including its current status, +specification, relations, and other metadata. The response includes the complete Task object +with all associated fields. + +By default, the method returns the latest definition version of the task from the manager's +perspective. +
+
+
+
+ #### 🔌 Usage
@@ -1022,7 +1067,17 @@ client.tasks.get_task(
-Update the status of a task. +Updates the status of a Task as it progresses through its lifecycle. + +This method allows agents or operators to report the current state of a task, +which could include changes to task status, and error information. + +Each status update increments the task's status_version. When updating status, +clients must provide the current version to ensure consistency. The system rejects +updates with mismatched versions to prevent race conditions. + +Terminal states (`STATUS_DONE_OK` and `STATUS_DONE_NOT_OK`) are permanent; once a task +reaches these states, no further updates are allowed.
@@ -1121,7 +1176,21 @@ is known are considered stale and ignored.
-Query for tasks by a specified search criteria. +Searches for Tasks that match specified filtering criteria and returns matching tasks in paginated form. + +This method allows filtering tasks based on multiple criteria including: +- Parent task relationships +- Task status (with inclusive or exclusive filtering) +- Update time ranges +- Task view (manager or agent perspective) +- Task assignee +- Task type (via exact URL matches or prefix matching) + +Results are returned in pages. When more results are available than can be returned in a single +response, a page_token is provided that can be used in subsequent requests to retrieve the next +set of results. + +By default, this returns the latest task version for each matching task from the manager's perspective.
@@ -1168,7 +1237,7 @@ client.tasks.query_tasks() **parent_task_id:** `typing.Optional[str]` If present matches Tasks with this parent Task ID. -Note: this is mutually exclusive with all other query parameters, i.e., either provide parent Task ID, or +Note: this is mutually exclusive with all other query parameters, for example, either provide parent task ID, or any of the remaining parameters, but not both. @@ -1217,8 +1286,25 @@ any of the remaining parameters, but not both.
-This is a long polling API that will block until a new task is ready for delivery. If no new task is -available then the server will hold on to your request for up to 5 minutes, after that 5 minute timeout +Establishes a server streaming connection that delivers tasks to taskable agents for execution. + +This method creates a persistent connection from Tasks API to an agent, allowing the server +to push tasks to the agent as they become available. The agent receives a stream of tasks that +match its selector criteria (entity IDs). + +The stream delivers three types of requests: +- ExecuteRequest: Contains a new task for the agent to execute +- CancelRequest: Indicates a task should be canceled +- CompleteRequest: Indicates a task should be completed + +This is the primary method for taskable agents to receive and process tasks in real-time. +Agents should maintain this connection and process incoming tasks according to their capabilities. + +When an agent receives a task, it should update the task status using the UpdateStatus endpoint +to provide progress information back to Tasks API. + +This is a long polling API that will block until a new task is ready for delivery. If no new task is +available then the server will hold on to your request for up to 5 minutes, after that 5 minute timeout period you will be expected to reinitiate a new request.
@@ -1303,21 +1389,12 @@ Lists objects in your environment. You can define a prefix to list a subset of y
```python -import datetime - from anduril import Lattice client = Lattice( token="YOUR_TOKEN", ) -response = client.objects.list_objects( - prefix="prefix", - since_timestamp=datetime.datetime.fromisoformat( - "2024-01-15 09:30:00+00:00", - ), - page_token="pageToken", - all_objects_in_mesh=True, -) +response = client.objects.list_objects() for item in response: yield item # alternatively, you can paginate page-by-page diff --git a/src/anduril/__init__.py b/src/anduril/__init__.py index bce5feb..7410936 100644 --- a/src/anduril/__init__.py +++ b/src/anduril/__init__.py @@ -53,6 +53,8 @@ EntityEventEventType, EntityEventResponse, EntityIdsSelector, + EntityManagerPose, + EntityManagerTMat3, EntityStreamEvent, EntityStreamHeartbeat, Enu, @@ -163,7 +165,6 @@ Symbology, System, TMat2, - TMat3, TargetPriority, Task, TaskCatalog, @@ -254,6 +255,8 @@ "EntityEventEventType": ".types", "EntityEventResponse": ".types", "EntityIdsSelector": ".types", + "EntityManagerPose": ".types", + "EntityManagerTMat3": ".types", "EntityStreamEvent": ".types", "EntityStreamHeartbeat": ".types", "Enu": ".types", @@ -374,7 +377,6 @@ "Symbology": ".types", "System": ".types", "TMat2": ".types", - "TMat3": ".types", "TargetPriority": ".types", "Task": ".types", "TaskCatalog": ".types", @@ -483,6 +485,8 @@ def __dir__(): "EntityEventEventType", "EntityEventResponse", "EntityIdsSelector", + "EntityManagerPose", + "EntityManagerTMat3", "EntityStreamEvent", "EntityStreamHeartbeat", "Enu", @@ -603,7 +607,6 @@ def __dir__(): "Symbology", "System", "TMat2", - "TMat3", "TargetPriority", "Task", "TaskCatalog", diff --git a/src/anduril/client.py b/src/anduril/client.py index eb6532d..64ef0fd 100644 --- a/src/anduril/client.py +++ b/src/anduril/client.py @@ -32,7 +32,7 @@ class Lattice: - token : typing.Optional[typing.Union[str, typing.Callable[[], str]]] + token : typing.Union[str, typing.Callable[[], str]] headers : typing.Optional[typing.Dict[str, str]] Additional headers to send with every request. @@ -59,7 +59,7 @@ def __init__( *, base_url: typing.Optional[str] = None, environment: LatticeEnvironment = LatticeEnvironment.DEFAULT, - token: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + token: typing.Union[str, typing.Callable[[], str]], headers: typing.Optional[typing.Dict[str, str]] = None, timeout: typing.Optional[float] = None, follow_redirects: typing.Optional[bool] = True, @@ -126,7 +126,7 @@ class AsyncLattice: - token : typing.Optional[typing.Union[str, typing.Callable[[], str]]] + token : typing.Union[str, typing.Callable[[], str]] headers : typing.Optional[typing.Dict[str, str]] Additional headers to send with every request. @@ -153,7 +153,7 @@ def __init__( *, base_url: typing.Optional[str] = None, environment: LatticeEnvironment = LatticeEnvironment.DEFAULT, - token: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + token: typing.Union[str, typing.Callable[[], str]], headers: typing.Optional[typing.Dict[str, str]] = None, timeout: typing.Optional[float] = None, follow_redirects: typing.Optional[bool] = True, diff --git a/src/anduril/core/__init__.py b/src/anduril/core/__init__.py index bfce76d..3b5240a 100644 --- a/src/anduril/core/__init__.py +++ b/src/anduril/core/__init__.py @@ -8,6 +8,7 @@ if typing.TYPE_CHECKING: from .api_error import ApiError from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper + from .custom_pagination import AsyncCustomPager, SyncCustomPager from .datetime_utils import serialize_datetime from .file import File, convert_file_dict_to_httpx_tuples, with_content_type from .http_client import AsyncHttpClient, HttpClient @@ -30,6 +31,7 @@ _dynamic_imports: typing.Dict[str, str] = { "ApiError": ".api_error", "AsyncClientWrapper": ".client_wrapper", + "AsyncCustomPager": ".custom_pagination", "AsyncHttpClient": ".http_client", "AsyncHttpResponse": ".http_response", "AsyncPager": ".pagination", @@ -41,6 +43,7 @@ "IS_PYDANTIC_V2": ".pydantic_utilities", "RequestOptions": ".request_options", "SyncClientWrapper": ".client_wrapper", + "SyncCustomPager": ".custom_pagination", "SyncPager": ".pagination", "UniversalBaseModel": ".pydantic_utilities", "UniversalRootModel": ".pydantic_utilities", @@ -82,6 +85,7 @@ def __dir__(): __all__ = [ "ApiError", "AsyncClientWrapper", + "AsyncCustomPager", "AsyncHttpClient", "AsyncHttpResponse", "AsyncPager", @@ -93,6 +97,7 @@ def __dir__(): "IS_PYDANTIC_V2", "RequestOptions", "SyncClientWrapper", + "SyncCustomPager", "SyncPager", "UniversalBaseModel", "UniversalRootModel", diff --git a/src/anduril/core/client_wrapper.py b/src/anduril/core/client_wrapper.py index 6a6ddd9..d1cefd3 100644 --- a/src/anduril/core/client_wrapper.py +++ b/src/anduril/core/client_wrapper.py @@ -10,7 +10,7 @@ class BaseClientWrapper: def __init__( self, *, - token: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + token: typing.Union[str, typing.Callable[[], str]], headers: typing.Optional[typing.Dict[str, str]] = None, base_url: str, timeout: typing.Optional[float] = None, @@ -22,19 +22,17 @@ def __init__( def get_headers(self) -> typing.Dict[str, str]: headers: typing.Dict[str, str] = { - "User-Agent": "anduril-lattice-sdk/3.0.0", + "User-Agent": "anduril-lattice-sdk/4.0.0", "X-Fern-Language": "Python", "X-Fern-SDK-Name": "anduril-lattice-sdk", - "X-Fern-SDK-Version": "3.0.0", + "X-Fern-SDK-Version": "4.0.0", **(self.get_custom_headers() or {}), } - token = self._get_token() - if token is not None: - headers["Authorization"] = f"Bearer {token}" + headers["Authorization"] = f"Bearer {self._get_token()}" return headers - def _get_token(self) -> typing.Optional[str]: - if isinstance(self._token, str) or self._token is None: + def _get_token(self) -> str: + if isinstance(self._token, str): return self._token else: return self._token() @@ -53,7 +51,7 @@ class SyncClientWrapper(BaseClientWrapper): def __init__( self, *, - token: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + token: typing.Union[str, typing.Callable[[], str]], headers: typing.Optional[typing.Dict[str, str]] = None, base_url: str, timeout: typing.Optional[float] = None, @@ -72,7 +70,7 @@ class AsyncClientWrapper(BaseClientWrapper): def __init__( self, *, - token: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, + token: typing.Union[str, typing.Callable[[], str]], headers: typing.Optional[typing.Dict[str, str]] = None, base_url: str, timeout: typing.Optional[float] = None, diff --git a/src/anduril/core/custom_pagination.py b/src/anduril/core/custom_pagination.py new file mode 100644 index 0000000..5de2c7a --- /dev/null +++ b/src/anduril/core/custom_pagination.py @@ -0,0 +1,152 @@ +# This file was auto-generated by Fern from our API Definition. + +""" +Custom Pagination Support + +This file is designed to be modified by SDK users to implement their own +pagination logic. The generator will import SyncCustomPager and AsyncCustomPager +from this module when custom pagination is used. + +Users should: +1. Implement their custom pager (e.g., PayrocPager, MyCustomPager, etc.) +2. Create adapter classes (SyncCustomPager/AsyncCustomPager) that bridge + between the generated SDK code and their custom pager implementation +""" + +from __future__ import annotations + +from typing import Any, AsyncIterator, Generic, Iterator, TypeVar + +# Import the base utilities you'll need +# Adjust these imports based on your actual structure +try: + from .client_wrapper import AsyncClientWrapper, SyncClientWrapper +except ImportError: + # Fallback for type hints + AsyncClientWrapper = Any # type: ignore + SyncClientWrapper = Any # type: ignore + +TItem = TypeVar("TItem") +TResponse = TypeVar("TResponse") + + +class SyncCustomPager(Generic[TItem, TResponse]): + """ + Adapter for custom synchronous pagination. + + The generator will call this with: + SyncCustomPager(initial_response=response, client_wrapper=client_wrapper) + + Implement this class to extract pagination metadata from your response + and delegate to your custom pager implementation. + + Example implementation: + + class SyncCustomPager(Generic[TItem, TResponse]): + def __init__( + self, + *, + initial_response: TResponse, + client_wrapper: SyncClientWrapper, + ): + # Extract data and pagination metadata from response + data = initial_response.data # Adjust based on your response structure + links = initial_response.links + + # Initialize your custom pager + self._pager = MyCustomPager( + current_page=Page(data), + httpx_client=client_wrapper.httpx_client, + get_headers=client_wrapper.get_headers, + # ... other parameters + ) + + def __iter__(self): + return iter(self._pager) + + # Delegate other methods to your pager... + """ + + def __init__( + self, + *, + initial_response: TResponse, + client_wrapper: SyncClientWrapper, + ): + """ + Initialize the custom pager. + + Args: + initial_response: The parsed API response from the first request + client_wrapper: The client wrapper providing HTTP client and utilities + """ + raise NotImplementedError( + "SyncCustomPager must be implemented. " + "Please implement this class in core/custom_pagination.py to define your pagination logic. " + "See the class docstring for examples." + ) + + def __iter__(self) -> Iterator[TItem]: + """Iterate through all items across all pages.""" + raise NotImplementedError("Must implement __iter__ method") + + +class AsyncCustomPager(Generic[TItem, TResponse]): + """ + Adapter for custom asynchronous pagination. + + The generator will call this with: + AsyncCustomPager(initial_response=response, client_wrapper=client_wrapper) + + Implement this class to extract pagination metadata from your response + and delegate to your custom async pager implementation. + + Example implementation: + + class AsyncCustomPager(Generic[TItem, TResponse]): + def __init__( + self, + *, + initial_response: TResponse, + client_wrapper: AsyncClientWrapper, + ): + # Extract data and pagination metadata from response + data = initial_response.data # Adjust based on your response structure + links = initial_response.links + + # Initialize your custom async pager + self._pager = MyAsyncCustomPager( + current_page=Page(data), + httpx_client=client_wrapper.httpx_client, + get_headers=client_wrapper.get_headers, + # ... other parameters + ) + + async def __aiter__(self): + return self._pager.__aiter__() + + # Delegate other methods to your pager... + """ + + def __init__( + self, + *, + initial_response: TResponse, + client_wrapper: AsyncClientWrapper, + ): + """ + Initialize the custom async pager. + + Args: + initial_response: The parsed API response from the first request + client_wrapper: The client wrapper providing HTTP client and utilities + """ + raise NotImplementedError( + "AsyncCustomPager must be implemented. " + "Please implement this class in core/custom_pagination.py to define your pagination logic. " + "See the class docstring for examples." + ) + + async def __aiter__(self) -> AsyncIterator[TItem]: + """Asynchronously iterate through all items across all pages.""" + raise NotImplementedError("Must implement __aiter__ method") diff --git a/src/anduril/core/pagination.py b/src/anduril/core/pagination.py index 97bcb64..760b089 100644 --- a/src/anduril/core/pagination.py +++ b/src/anduril/core/pagination.py @@ -5,10 +5,10 @@ from dataclasses import dataclass from typing import AsyncIterator, Awaitable, Callable, Generic, Iterator, List, Optional, TypeVar -from .http_response import BaseHttpResponse - # Generic to represent the underlying type of the results within a page T = TypeVar("T") +# Generic to represent the type of the API response +R = TypeVar("R") # SDKs implement a Page ABC per-pagination request, the endpoint then returns a pager that wraps this type @@ -23,11 +23,11 @@ @dataclass(frozen=True) -class SyncPager(Generic[T]): - get_next: Optional[Callable[[], Optional[SyncPager[T]]]] +class SyncPager(Generic[T, R]): + get_next: Optional[Callable[[], Optional[SyncPager[T, R]]]] has_next: bool items: Optional[List[T]] - response: Optional[BaseHttpResponse] + response: R # Here we type ignore the iterator to avoid a mypy error # caused by the type conflict with Pydanitc's __iter__ method @@ -37,8 +37,8 @@ def __iter__(self) -> Iterator[T]: # type: ignore[override] if page.items is not None: yield from page.items - def iter_pages(self) -> Iterator[SyncPager[T]]: - page: Optional[SyncPager[T]] = self + def iter_pages(self) -> Iterator[SyncPager[T, R]]: + page: Optional[SyncPager[T, R]] = self while page is not None: yield page @@ -49,16 +49,16 @@ def iter_pages(self) -> Iterator[SyncPager[T]]: if page is None or page.items is None or len(page.items) == 0: return - def next_page(self) -> Optional[SyncPager[T]]: + def next_page(self) -> Optional[SyncPager[T, R]]: return self.get_next() if self.get_next is not None else None @dataclass(frozen=True) -class AsyncPager(Generic[T]): - get_next: Optional[Callable[[], Awaitable[Optional[AsyncPager[T]]]]] +class AsyncPager(Generic[T, R]): + get_next: Optional[Callable[[], Awaitable[Optional[AsyncPager[T, R]]]]] has_next: bool items: Optional[List[T]] - response: Optional[BaseHttpResponse] + response: R async def __aiter__(self) -> AsyncIterator[T]: async for page in self.iter_pages(): @@ -66,8 +66,8 @@ async def __aiter__(self) -> AsyncIterator[T]: for item in page.items: yield item - async def iter_pages(self) -> AsyncIterator[AsyncPager[T]]: - page: Optional[AsyncPager[T]] = self + async def iter_pages(self) -> AsyncIterator[AsyncPager[T, R]]: + page: Optional[AsyncPager[T, R]] = self while page is not None: yield page @@ -78,5 +78,5 @@ async def iter_pages(self) -> AsyncIterator[AsyncPager[T]]: if page is None or page.items is None or len(page.items) == 0: return - async def next_page(self) -> Optional[AsyncPager[T]]: + async def next_page(self) -> Optional[AsyncPager[T, R]]: return await self.get_next() if self.get_next is not None else None diff --git a/src/anduril/core/pydantic_utilities.py b/src/anduril/core/pydantic_utilities.py index 8906cdf..185e5c4 100644 --- a/src/anduril/core/pydantic_utilities.py +++ b/src/anduril/core/pydantic_utilities.py @@ -220,7 +220,9 @@ def universal_root_validator( ) -> Callable[[AnyCallable], AnyCallable]: def decorator(func: AnyCallable) -> AnyCallable: if IS_PYDANTIC_V2: - return cast(AnyCallable, pydantic.model_validator(mode="before" if pre else "after")(func)) # type: ignore[attr-defined] + # In Pydantic v2, for RootModel we always use "before" mode + # The custom validators transform the input value before the model is created + return cast(AnyCallable, pydantic.model_validator(mode="before")(func)) # type: ignore[attr-defined] return cast(AnyCallable, pydantic.root_validator(pre=pre)(func)) # type: ignore[call-overload] return decorator diff --git a/src/anduril/entities/client.py b/src/anduril/entities/client.py index d905a17..fe45e45 100644 --- a/src/anduril/entities/client.py +++ b/src/anduril/entities/client.py @@ -116,8 +116,8 @@ def publish_entity( Parameters ---------- entity_id : typing.Optional[str] - A Globally Unique Identifier (GUID) for your entity. If this field is empty, the Entity Manager API - automatically generates an ID when it creates the entity. + A Globally Unique Identifier (GUID) for your entity. This is a required + field. description : typing.Optional[str] A human-readable entity description that's helpful for debugging purposes and human @@ -490,7 +490,25 @@ def stream_entities( request_options: typing.Optional[RequestOptions] = None, ) -> typing.Iterator[StreamEntitiesResponse]: """ - Establishes a persistent connection to stream entity events as they occur. + Establishes a server-sent events (SSE) connection that streams entity data in real-time. + This is a one-way connection from server to client that follows the SSE protocol with text/event-stream content type. + + This endpoint enables clients to maintain a real-time view of the common operational picture (COP) + by first streaming all pre-existing entities that match filter criteria, then continuously delivering + updates as entities are created, modified, or deleted. + + The server first sends events with type PREEXISTING for all live entities matching the filter that existed before the stream was open, + then streams CREATE events for newly created entities, UPDATE events when existing entities change, and DELETED events when entities are removed. The stream remains open + indefinitely unless preExistingOnly is set to true. + + Heartbeat messages can be configured to maintain connection health and detect disconnects by setting the heartbeatIntervalMS + parameter. These heartbeats help keep the connection alive and allow clients to verify the server is still responsive. + + Clients can optimize bandwidth usage by specifying which entity components they need populated using the componentsToInclude parameter. + This allows receiving only relevant data instead of complete entities. + + The connection automatically recovers from temporary disconnections, resuming the stream where it left off. Unlike polling approaches, + this provides real-time updates with minimal latency and reduced server load. Parameters ---------- @@ -601,8 +619,8 @@ async def publish_entity( Parameters ---------- entity_id : typing.Optional[str] - A Globally Unique Identifier (GUID) for your entity. If this field is empty, the Entity Manager API - automatically generates an ID when it creates the entity. + A Globally Unique Identifier (GUID) for your entity. This is a required + field. description : typing.Optional[str] A human-readable entity description that's helpful for debugging purposes and human @@ -1017,7 +1035,25 @@ async def stream_entities( request_options: typing.Optional[RequestOptions] = None, ) -> typing.AsyncIterator[StreamEntitiesResponse]: """ - Establishes a persistent connection to stream entity events as they occur. + Establishes a server-sent events (SSE) connection that streams entity data in real-time. + This is a one-way connection from server to client that follows the SSE protocol with text/event-stream content type. + + This endpoint enables clients to maintain a real-time view of the common operational picture (COP) + by first streaming all pre-existing entities that match filter criteria, then continuously delivering + updates as entities are created, modified, or deleted. + + The server first sends events with type PREEXISTING for all live entities matching the filter that existed before the stream was open, + then streams CREATE events for newly created entities, UPDATE events when existing entities change, and DELETED events when entities are removed. The stream remains open + indefinitely unless preExistingOnly is set to true. + + Heartbeat messages can be configured to maintain connection health and detect disconnects by setting the heartbeatIntervalMS + parameter. These heartbeats help keep the connection alive and allow clients to verify the server is still responsive. + + Clients can optimize bandwidth usage by specifying which entity components they need populated using the componentsToInclude parameter. + This allows receiving only relevant data instead of complete entities. + + The connection automatically recovers from temporary disconnections, resuming the stream where it left off. Unlike polling approaches, + this provides real-time updates with minimal latency and reduced server load. Parameters ---------- diff --git a/src/anduril/entities/raw_client.py b/src/anduril/entities/raw_client.py index cfdb30e..0c8f710 100644 --- a/src/anduril/entities/raw_client.py +++ b/src/anduril/entities/raw_client.py @@ -118,8 +118,8 @@ def publish_entity( Parameters ---------- entity_id : typing.Optional[str] - A Globally Unique Identifier (GUID) for your entity. If this field is empty, the Entity Manager API - automatically generates an ID when it creates the entity. + A Globally Unique Identifier (GUID) for your entity. This is a required + field. description : typing.Optional[str] A human-readable entity description that's helpful for debugging purposes and human @@ -374,9 +374,9 @@ def publish_entity( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -385,9 +385,9 @@ def publish_entity( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -433,9 +433,9 @@ def get_entity( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -444,9 +444,9 @@ def get_entity( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -455,9 +455,9 @@ def get_entity( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -537,9 +537,9 @@ def override_entity( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -548,9 +548,9 @@ def override_entity( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -559,9 +559,9 @@ def override_entity( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -612,9 +612,9 @@ def remove_entity_override( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -623,9 +623,9 @@ def remove_entity_override( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -634,9 +634,9 @@ def remove_entity_override( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -707,9 +707,9 @@ def long_poll_entity_events( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -718,9 +718,9 @@ def long_poll_entity_events( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -729,9 +729,9 @@ def long_poll_entity_events( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -740,9 +740,9 @@ def long_poll_entity_events( raise RequestTimeoutError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -751,9 +751,9 @@ def long_poll_entity_events( raise TooManyRequestsError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -773,7 +773,25 @@ def stream_entities( request_options: typing.Optional[RequestOptions] = None, ) -> typing.Iterator[HttpResponse[typing.Iterator[StreamEntitiesResponse]]]: """ - Establishes a persistent connection to stream entity events as they occur. + Establishes a server-sent events (SSE) connection that streams entity data in real-time. + This is a one-way connection from server to client that follows the SSE protocol with text/event-stream content type. + + This endpoint enables clients to maintain a real-time view of the common operational picture (COP) + by first streaming all pre-existing entities that match filter criteria, then continuously delivering + updates as entities are created, modified, or deleted. + + The server first sends events with type PREEXISTING for all live entities matching the filter that existed before the stream was open, + then streams CREATE events for newly created entities, UPDATE events when existing entities change, and DELETED events when entities are removed. The stream remains open + indefinitely unless preExistingOnly is set to true. + + Heartbeat messages can be configured to maintain connection health and detect disconnects by setting the heartbeatIntervalMS + parameter. These heartbeats help keep the connection alive and allow clients to verify the server is still responsive. + + Clients can optimize bandwidth usage by specifying which entity components they need populated using the componentsToInclude parameter. + This allows receiving only relevant data instead of complete entities. + + The connection automatically recovers from temporary disconnections, resuming the stream where it left off. Unlike polling approaches, + this provides real-time updates with minimal latency and reduced server load. Parameters ---------- @@ -844,9 +862,9 @@ def _iter(): raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -855,9 +873,9 @@ def _iter(): raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -931,8 +949,8 @@ async def publish_entity( Parameters ---------- entity_id : typing.Optional[str] - A Globally Unique Identifier (GUID) for your entity. If this field is empty, the Entity Manager API - automatically generates an ID when it creates the entity. + A Globally Unique Identifier (GUID) for your entity. This is a required + field. description : typing.Optional[str] A human-readable entity description that's helpful for debugging purposes and human @@ -1187,9 +1205,9 @@ async def publish_entity( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1198,9 +1216,9 @@ async def publish_entity( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1246,9 +1264,9 @@ async def get_entity( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1257,9 +1275,9 @@ async def get_entity( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1268,9 +1286,9 @@ async def get_entity( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1350,9 +1368,9 @@ async def override_entity( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1361,9 +1379,9 @@ async def override_entity( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1372,9 +1390,9 @@ async def override_entity( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1425,9 +1443,9 @@ async def remove_entity_override( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1436,9 +1454,9 @@ async def remove_entity_override( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1447,9 +1465,9 @@ async def remove_entity_override( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1520,9 +1538,9 @@ async def long_poll_entity_events( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1531,9 +1549,9 @@ async def long_poll_entity_events( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1542,9 +1560,9 @@ async def long_poll_entity_events( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1553,9 +1571,9 @@ async def long_poll_entity_events( raise RequestTimeoutError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1564,9 +1582,9 @@ async def long_poll_entity_events( raise TooManyRequestsError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1586,7 +1604,25 @@ async def stream_entities( request_options: typing.Optional[RequestOptions] = None, ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[StreamEntitiesResponse]]]: """ - Establishes a persistent connection to stream entity events as they occur. + Establishes a server-sent events (SSE) connection that streams entity data in real-time. + This is a one-way connection from server to client that follows the SSE protocol with text/event-stream content type. + + This endpoint enables clients to maintain a real-time view of the common operational picture (COP) + by first streaming all pre-existing entities that match filter criteria, then continuously delivering + updates as entities are created, modified, or deleted. + + The server first sends events with type PREEXISTING for all live entities matching the filter that existed before the stream was open, + then streams CREATE events for newly created entities, UPDATE events when existing entities change, and DELETED events when entities are removed. The stream remains open + indefinitely unless preExistingOnly is set to true. + + Heartbeat messages can be configured to maintain connection health and detect disconnects by setting the heartbeatIntervalMS + parameter. These heartbeats help keep the connection alive and allow clients to verify the server is still responsive. + + Clients can optimize bandwidth usage by specifying which entity components they need populated using the componentsToInclude parameter. + This allows receiving only relevant data instead of complete entities. + + The connection automatically recovers from temporary disconnections, resuming the stream where it left off. Unlike polling approaches, + this provides real-time updates with minimal latency and reduced server load. Parameters ---------- @@ -1657,9 +1693,9 @@ async def _iter(): raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -1668,9 +1704,9 @@ async def _iter(): raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), diff --git a/src/anduril/entities/types/stream_entities_response.py b/src/anduril/entities/types/stream_entities_response.py index 068e8e0..f7ceb7d 100644 --- a/src/anduril/entities/types/stream_entities_response.py +++ b/src/anduril/entities/types/stream_entities_response.py @@ -52,9 +52,9 @@ class Config: extra = pydantic.Extra.allow -from ...types.entity import Entity # noqa: E402, F401, I001 -from ...types.override import Override # noqa: E402, F401, I001 -from ...types.overrides import Overrides # noqa: E402, F401, I001 +from ...types.entity import Entity # noqa: E402, I001 -StreamEntitiesResponse = typing.Union[StreamEntitiesResponse_Heartbeat, StreamEntitiesResponse_Entity] +StreamEntitiesResponse = typing_extensions.Annotated[ + typing.Union[StreamEntitiesResponse_Heartbeat, StreamEntitiesResponse_Entity], pydantic.Field(discriminator="event") +] update_forward_refs(StreamEntitiesResponse_Entity) diff --git a/src/anduril/errors/bad_request_error.py b/src/anduril/errors/bad_request_error.py index baf5be4..ec78e26 100644 --- a/src/anduril/errors/bad_request_error.py +++ b/src/anduril/errors/bad_request_error.py @@ -6,5 +6,5 @@ class BadRequestError(ApiError): - def __init__(self, body: typing.Optional[typing.Any], headers: typing.Optional[typing.Dict[str, str]] = None): + def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None): super().__init__(status_code=400, headers=headers, body=body) diff --git a/src/anduril/errors/content_too_large_error.py b/src/anduril/errors/content_too_large_error.py index 2334086..28d4f48 100644 --- a/src/anduril/errors/content_too_large_error.py +++ b/src/anduril/errors/content_too_large_error.py @@ -6,5 +6,5 @@ class ContentTooLargeError(ApiError): - def __init__(self, body: typing.Optional[typing.Any], headers: typing.Optional[typing.Dict[str, str]] = None): + def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None): super().__init__(status_code=413, headers=headers, body=body) diff --git a/src/anduril/errors/insufficient_storage_error.py b/src/anduril/errors/insufficient_storage_error.py index fd084e4..f0dc628 100644 --- a/src/anduril/errors/insufficient_storage_error.py +++ b/src/anduril/errors/insufficient_storage_error.py @@ -6,5 +6,5 @@ class InsufficientStorageError(ApiError): - def __init__(self, body: typing.Optional[typing.Any], headers: typing.Optional[typing.Dict[str, str]] = None): + def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None): super().__init__(status_code=507, headers=headers, body=body) diff --git a/src/anduril/errors/internal_server_error.py b/src/anduril/errors/internal_server_error.py index 14313ab..fabcc45 100644 --- a/src/anduril/errors/internal_server_error.py +++ b/src/anduril/errors/internal_server_error.py @@ -6,5 +6,5 @@ class InternalServerError(ApiError): - def __init__(self, body: typing.Optional[typing.Any], headers: typing.Optional[typing.Dict[str, str]] = None): + def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None): super().__init__(status_code=500, headers=headers, body=body) diff --git a/src/anduril/errors/not_found_error.py b/src/anduril/errors/not_found_error.py index dcd60e3..75f557d 100644 --- a/src/anduril/errors/not_found_error.py +++ b/src/anduril/errors/not_found_error.py @@ -6,5 +6,5 @@ class NotFoundError(ApiError): - def __init__(self, body: typing.Optional[typing.Any], headers: typing.Optional[typing.Dict[str, str]] = None): + def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None): super().__init__(status_code=404, headers=headers, body=body) diff --git a/src/anduril/errors/request_timeout_error.py b/src/anduril/errors/request_timeout_error.py index df4d2d4..8406576 100644 --- a/src/anduril/errors/request_timeout_error.py +++ b/src/anduril/errors/request_timeout_error.py @@ -6,5 +6,5 @@ class RequestTimeoutError(ApiError): - def __init__(self, body: typing.Optional[typing.Any], headers: typing.Optional[typing.Dict[str, str]] = None): + def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None): super().__init__(status_code=408, headers=headers, body=body) diff --git a/src/anduril/errors/too_many_requests_error.py b/src/anduril/errors/too_many_requests_error.py index 2705399..705d6f1 100644 --- a/src/anduril/errors/too_many_requests_error.py +++ b/src/anduril/errors/too_many_requests_error.py @@ -6,5 +6,5 @@ class TooManyRequestsError(ApiError): - def __init__(self, body: typing.Optional[typing.Any], headers: typing.Optional[typing.Dict[str, str]] = None): + def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None): super().__init__(status_code=429, headers=headers, body=body) diff --git a/src/anduril/errors/unauthorized_error.py b/src/anduril/errors/unauthorized_error.py index c83b25c..7e48bb6 100644 --- a/src/anduril/errors/unauthorized_error.py +++ b/src/anduril/errors/unauthorized_error.py @@ -6,5 +6,5 @@ class UnauthorizedError(ApiError): - def __init__(self, body: typing.Optional[typing.Any], headers: typing.Optional[typing.Dict[str, str]] = None): + def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None): super().__init__(status_code=401, headers=headers, body=body) diff --git a/src/anduril/objects/client.py b/src/anduril/objects/client.py index d5787ad..4e0b198 100644 --- a/src/anduril/objects/client.py +++ b/src/anduril/objects/client.py @@ -6,6 +6,7 @@ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.pagination import AsyncPager, SyncPager from ..core.request_options import RequestOptions +from ..types.list_response import ListResponse from ..types.path_metadata import PathMetadata from .raw_client import AsyncRawObjectsClient, RawObjectsClient from .types.get_object_request_accept_encoding import GetObjectRequestAcceptEncoding @@ -37,7 +38,7 @@ def list_objects( page_token: typing.Optional[str] = None, all_objects_in_mesh: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> SyncPager[PathMetadata]: + ) -> SyncPager[PathMetadata, ListResponse]: """ Lists objects in your environment. You can define a prefix to list a subset of your objects. If you do not set a prefix, Lattice returns all available objects. By default this endpoint will list local objects only. @@ -60,26 +61,17 @@ def list_objects( Returns ------- - SyncPager[PathMetadata] + SyncPager[PathMetadata, ListResponse] Successful operation Examples -------- - import datetime - from anduril import Lattice client = Lattice( token="YOUR_TOKEN", ) - response = client.objects.list_objects( - prefix="prefix", - since_timestamp=datetime.datetime.fromisoformat( - "2024-01-15 09:30:00+00:00", - ), - page_token="pageToken", - all_objects_in_mesh=True, - ) + response = client.objects.list_objects() for item in response: yield item # alternatively, you can paginate page-by-page @@ -254,7 +246,7 @@ async def list_objects( page_token: typing.Optional[str] = None, all_objects_in_mesh: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncPager[PathMetadata]: + ) -> AsyncPager[PathMetadata, ListResponse]: """ Lists objects in your environment. You can define a prefix to list a subset of your objects. If you do not set a prefix, Lattice returns all available objects. By default this endpoint will list local objects only. @@ -277,13 +269,12 @@ async def list_objects( Returns ------- - AsyncPager[PathMetadata] + AsyncPager[PathMetadata, ListResponse] Successful operation Examples -------- import asyncio - import datetime from anduril import AsyncLattice @@ -293,14 +284,7 @@ async def list_objects( async def main() -> None: - response = await client.objects.list_objects( - prefix="prefix", - since_timestamp=datetime.datetime.fromisoformat( - "2024-01-15 09:30:00+00:00", - ), - page_token="pageToken", - all_objects_in_mesh=True, - ) + response = await client.objects.list_objects() async for item in response: yield item diff --git a/src/anduril/objects/raw_client.py b/src/anduril/objects/raw_client.py index a93531b..e8cdc1b 100644 --- a/src/anduril/objects/raw_client.py +++ b/src/anduril/objects/raw_client.py @@ -10,7 +10,7 @@ from ..core.datetime_utils import serialize_datetime from ..core.http_response import AsyncHttpResponse, HttpResponse from ..core.jsonable_encoder import jsonable_encoder -from ..core.pagination import AsyncPager, BaseHttpResponse, SyncPager +from ..core.pagination import AsyncPager, SyncPager from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.bad_request_error import BadRequestError @@ -39,7 +39,7 @@ def list_objects( page_token: typing.Optional[str] = None, all_objects_in_mesh: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> SyncPager[PathMetadata]: + ) -> SyncPager[PathMetadata, ListResponse]: """ Lists objects in your environment. You can define a prefix to list a subset of your objects. If you do not set a prefix, Lattice returns all available objects. By default this endpoint will list local objects only. @@ -62,7 +62,7 @@ def list_objects( Returns ------- - SyncPager[PathMetadata] + SyncPager[PathMetadata, ListResponse] Successful operation """ _response = self._client_wrapper.httpx_client.request( @@ -95,16 +95,14 @@ def list_objects( all_objects_in_mesh=all_objects_in_mesh, request_options=request_options, ) - return SyncPager( - has_next=_has_next, items=_items, get_next=_get_next, response=BaseHttpResponse(response=_response) - ) + return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) if _response.status_code == 400: raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -113,9 +111,9 @@ def list_objects( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -124,9 +122,9 @@ def list_objects( raise InternalServerError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -189,9 +187,9 @@ def _stream() -> HttpResponse[typing.Iterator[bytes]]: raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -200,9 +198,9 @@ def _stream() -> HttpResponse[typing.Iterator[bytes]]: raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -211,9 +209,9 @@ def _stream() -> HttpResponse[typing.Iterator[bytes]]: raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -222,9 +220,9 @@ def _stream() -> HttpResponse[typing.Iterator[bytes]]: raise InternalServerError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -287,9 +285,9 @@ def upload_object( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -298,9 +296,9 @@ def upload_object( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -309,9 +307,9 @@ def upload_object( raise ContentTooLargeError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -320,9 +318,9 @@ def upload_object( raise InternalServerError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -331,9 +329,9 @@ def upload_object( raise InsufficientStorageError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -373,9 +371,9 @@ def delete_object( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -384,9 +382,9 @@ def delete_object( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -395,9 +393,9 @@ def delete_object( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -406,9 +404,9 @@ def delete_object( raise InternalServerError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -448,9 +446,9 @@ def get_object_metadata( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -459,9 +457,9 @@ def get_object_metadata( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -470,9 +468,9 @@ def get_object_metadata( raise InternalServerError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -495,7 +493,7 @@ async def list_objects( page_token: typing.Optional[str] = None, all_objects_in_mesh: typing.Optional[bool] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncPager[PathMetadata]: + ) -> AsyncPager[PathMetadata, ListResponse]: """ Lists objects in your environment. You can define a prefix to list a subset of your objects. If you do not set a prefix, Lattice returns all available objects. By default this endpoint will list local objects only. @@ -518,7 +516,7 @@ async def list_objects( Returns ------- - AsyncPager[PathMetadata] + AsyncPager[PathMetadata, ListResponse] Successful operation """ _response = await self._client_wrapper.httpx_client.request( @@ -554,16 +552,14 @@ async def _get_next(): request_options=request_options, ) - return AsyncPager( - has_next=_has_next, items=_items, get_next=_get_next, response=BaseHttpResponse(response=_response) - ) + return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response) if _response.status_code == 400: raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -572,9 +568,9 @@ async def _get_next(): raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -583,9 +579,9 @@ async def _get_next(): raise InternalServerError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -649,9 +645,9 @@ async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -660,9 +656,9 @@ async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -671,9 +667,9 @@ async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -682,9 +678,9 @@ async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: raise InternalServerError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -747,9 +743,9 @@ async def upload_object( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -758,9 +754,9 @@ async def upload_object( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -769,9 +765,9 @@ async def upload_object( raise ContentTooLargeError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -780,9 +776,9 @@ async def upload_object( raise InternalServerError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -791,9 +787,9 @@ async def upload_object( raise InsufficientStorageError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -833,9 +829,9 @@ async def delete_object( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -844,9 +840,9 @@ async def delete_object( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -855,9 +851,9 @@ async def delete_object( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -866,9 +862,9 @@ async def delete_object( raise InternalServerError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -908,9 +904,9 @@ async def get_object_metadata( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -919,9 +915,9 @@ async def get_object_metadata( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -930,9 +926,9 @@ async def get_object_metadata( raise InternalServerError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), diff --git a/src/anduril/tasks/client.py b/src/anduril/tasks/client.py index 42fdbf6..f005196 100644 --- a/src/anduril/tasks/client.py +++ b/src/anduril/tasks/client.py @@ -50,8 +50,14 @@ def create_task( request_options: typing.Optional[RequestOptions] = None, ) -> Task: """ - Submit a request to create a task and schedule it for delivery. Tasks, once delivered, will - be asynchronously updated by their destined agent. + Creates a new Task in the system with the specified parameters. + + This method initiates a new task with a unique ID (either provided or auto-generated), + sets the initial task state to STATUS_CREATED, and establishes task ownership. The task + can be assigned to a specific agent through the Relations field. + + Once created, a task enters the lifecycle workflow and can be tracked, updated, and managed + through other Tasks API endpoints. Parameters ---------- @@ -66,7 +72,7 @@ def create_task( Longer, free form human readable description of this Task. specification : typing.Optional[GoogleProtobufAny] - Full set of task parameters. + The path for the Protobuf task definition, and the complete task data. author : typing.Optional[Principal] @@ -115,6 +121,15 @@ def create_task( def get_task(self, task_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> Task: """ + Retrieves a specific Task by its ID, with options to select a particular task version or view. + + This method returns detailed information about a task including its current status, + specification, relations, and other metadata. The response includes the complete Task object + with all associated fields. + + By default, the method returns the latest definition version of the task from the manager's + perspective. + Parameters ---------- task_id : str @@ -152,7 +167,17 @@ def update_task_status( request_options: typing.Optional[RequestOptions] = None, ) -> Task: """ - Update the status of a task. + Updates the status of a Task as it progresses through its lifecycle. + + This method allows agents or operators to report the current state of a task, + which could include changes to task status, and error information. + + Each status update increments the task's status_version. When updating status, + clients must provide the current version to ensure consistency. The system rejects + updates with mismatched versions to prevent race conditions. + + Terminal states (`STATUS_DONE_OK` and `STATUS_DONE_NOT_OK`) are permanent; once a task + reaches these states, no further updates are allowed. Parameters ---------- @@ -208,7 +233,21 @@ def query_tasks( request_options: typing.Optional[RequestOptions] = None, ) -> TaskQueryResults: """ - Query for tasks by a specified search criteria. + Searches for Tasks that match specified filtering criteria and returns matching tasks in paginated form. + + This method allows filtering tasks based on multiple criteria including: + - Parent task relationships + - Task status (with inclusive or exclusive filtering) + - Update time ranges + - Task view (manager or agent perspective) + - Task assignee + - Task type (via exact URL matches or prefix matching) + + Results are returned in pages. When more results are available than can be returned in a single + response, a page_token is provided that can be used in subsequent requests to retrieve the next + set of results. + + By default, this returns the latest task version for each matching task from the manager's perspective. Parameters ---------- @@ -217,7 +256,7 @@ def query_tasks( parent_task_id : typing.Optional[str] If present matches Tasks with this parent Task ID. - Note: this is mutually exclusive with all other query parameters, i.e., either provide parent Task ID, or + Note: this is mutually exclusive with all other query parameters, for example, either provide parent task ID, or any of the remaining parameters, but not both. status_filter : typing.Optional[TaskQueryStatusFilter] @@ -258,6 +297,23 @@ def listen_as_agent( request_options: typing.Optional[RequestOptions] = None, ) -> AgentRequest: """ + Establishes a server streaming connection that delivers tasks to taskable agents for execution. + + This method creates a persistent connection from Tasks API to an agent, allowing the server + to push tasks to the agent as they become available. The agent receives a stream of tasks that + match its selector criteria (entity IDs). + + The stream delivers three types of requests: + - ExecuteRequest: Contains a new task for the agent to execute + - CancelRequest: Indicates a task should be canceled + - CompleteRequest: Indicates a task should be completed + + This is the primary method for taskable agents to receive and process tasks in real-time. + Agents should maintain this connection and process incoming tasks according to their capabilities. + + When an agent receives a task, it should update the task status using the UpdateStatus endpoint + to provide progress information back to Tasks API. + This is a long polling API that will block until a new task is ready for delivery. If no new task is available then the server will hold on to your request for up to 5 minutes, after that 5 minute timeout period you will be expected to reinitiate a new request. @@ -317,8 +373,14 @@ async def create_task( request_options: typing.Optional[RequestOptions] = None, ) -> Task: """ - Submit a request to create a task and schedule it for delivery. Tasks, once delivered, will - be asynchronously updated by their destined agent. + Creates a new Task in the system with the specified parameters. + + This method initiates a new task with a unique ID (either provided or auto-generated), + sets the initial task state to STATUS_CREATED, and establishes task ownership. The task + can be assigned to a specific agent through the Relations field. + + Once created, a task enters the lifecycle workflow and can be tracked, updated, and managed + through other Tasks API endpoints. Parameters ---------- @@ -333,7 +395,7 @@ async def create_task( Longer, free form human readable description of this Task. specification : typing.Optional[GoogleProtobufAny] - Full set of task parameters. + The path for the Protobuf task definition, and the complete task data. author : typing.Optional[Principal] @@ -390,6 +452,15 @@ async def main() -> None: async def get_task(self, task_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> Task: """ + Retrieves a specific Task by its ID, with options to select a particular task version or view. + + This method returns detailed information about a task including its current status, + specification, relations, and other metadata. The response includes the complete Task object + with all associated fields. + + By default, the method returns the latest definition version of the task from the manager's + perspective. + Parameters ---------- task_id : str @@ -435,7 +506,17 @@ async def update_task_status( request_options: typing.Optional[RequestOptions] = None, ) -> Task: """ - Update the status of a task. + Updates the status of a Task as it progresses through its lifecycle. + + This method allows agents or operators to report the current state of a task, + which could include changes to task status, and error information. + + Each status update increments the task's status_version. When updating status, + clients must provide the current version to ensure consistency. The system rejects + updates with mismatched versions to prevent race conditions. + + Terminal states (`STATUS_DONE_OK` and `STATUS_DONE_NOT_OK`) are permanent; once a task + reaches these states, no further updates are allowed. Parameters ---------- @@ -499,7 +580,21 @@ async def query_tasks( request_options: typing.Optional[RequestOptions] = None, ) -> TaskQueryResults: """ - Query for tasks by a specified search criteria. + Searches for Tasks that match specified filtering criteria and returns matching tasks in paginated form. + + This method allows filtering tasks based on multiple criteria including: + - Parent task relationships + - Task status (with inclusive or exclusive filtering) + - Update time ranges + - Task view (manager or agent perspective) + - Task assignee + - Task type (via exact URL matches or prefix matching) + + Results are returned in pages. When more results are available than can be returned in a single + response, a page_token is provided that can be used in subsequent requests to retrieve the next + set of results. + + By default, this returns the latest task version for each matching task from the manager's perspective. Parameters ---------- @@ -508,7 +603,7 @@ async def query_tasks( parent_task_id : typing.Optional[str] If present matches Tasks with this parent Task ID. - Note: this is mutually exclusive with all other query parameters, i.e., either provide parent Task ID, or + Note: this is mutually exclusive with all other query parameters, for example, either provide parent task ID, or any of the remaining parameters, but not both. status_filter : typing.Optional[TaskQueryStatusFilter] @@ -557,6 +652,23 @@ async def listen_as_agent( request_options: typing.Optional[RequestOptions] = None, ) -> AgentRequest: """ + Establishes a server streaming connection that delivers tasks to taskable agents for execution. + + This method creates a persistent connection from Tasks API to an agent, allowing the server + to push tasks to the agent as they become available. The agent receives a stream of tasks that + match its selector criteria (entity IDs). + + The stream delivers three types of requests: + - ExecuteRequest: Contains a new task for the agent to execute + - CancelRequest: Indicates a task should be canceled + - CompleteRequest: Indicates a task should be completed + + This is the primary method for taskable agents to receive and process tasks in real-time. + Agents should maintain this connection and process incoming tasks according to their capabilities. + + When an agent receives a task, it should update the task status using the UpdateStatus endpoint + to provide progress information back to Tasks API. + This is a long polling API that will block until a new task is ready for delivery. If no new task is available then the server will hold on to your request for up to 5 minutes, after that 5 minute timeout period you will be expected to reinitiate a new request. diff --git a/src/anduril/tasks/raw_client.py b/src/anduril/tasks/raw_client.py index 754d890..ac22cc1 100644 --- a/src/anduril/tasks/raw_client.py +++ b/src/anduril/tasks/raw_client.py @@ -47,8 +47,14 @@ def create_task( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[Task]: """ - Submit a request to create a task and schedule it for delivery. Tasks, once delivered, will - be asynchronously updated by their destined agent. + Creates a new Task in the system with the specified parameters. + + This method initiates a new task with a unique ID (either provided or auto-generated), + sets the initial task state to STATUS_CREATED, and establishes task ownership. The task + can be assigned to a specific agent through the Relations field. + + Once created, a task enters the lifecycle workflow and can be tracked, updated, and managed + through other Tasks API endpoints. Parameters ---------- @@ -63,7 +69,7 @@ def create_task( Longer, free form human readable description of this Task. specification : typing.Optional[GoogleProtobufAny] - Full set of task parameters. + The path for the Protobuf task definition, and the complete task data. author : typing.Optional[Principal] @@ -129,9 +135,9 @@ def create_task( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -140,9 +146,9 @@ def create_task( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -154,6 +160,15 @@ def create_task( def get_task(self, task_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[Task]: """ + Retrieves a specific Task by its ID, with options to select a particular task version or view. + + This method returns detailed information about a task including its current status, + specification, relations, and other metadata. The response includes the complete Task object + with all associated fields. + + By default, the method returns the latest definition version of the task from the manager's + perspective. + Parameters ---------- task_id : str @@ -186,9 +201,9 @@ def get_task(self, task_id: str, *, request_options: typing.Optional[RequestOpti raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -197,9 +212,9 @@ def get_task(self, task_id: str, *, request_options: typing.Optional[RequestOpti raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -208,9 +223,9 @@ def get_task(self, task_id: str, *, request_options: typing.Optional[RequestOpti raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -230,7 +245,17 @@ def update_task_status( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[Task]: """ - Update the status of a task. + Updates the status of a Task as it progresses through its lifecycle. + + This method allows agents or operators to report the current state of a task, + which could include changes to task status, and error information. + + Each status update increments the task's status_version. When updating status, + clients must provide the current version to ensure consistency. The system rejects + updates with mismatched versions to prevent race conditions. + + Terminal states (`STATUS_DONE_OK` and `STATUS_DONE_NOT_OK`) are permanent; once a task + reaches these states, no further updates are allowed. Parameters ---------- @@ -288,9 +313,9 @@ def update_task_status( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -299,9 +324,9 @@ def update_task_status( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -310,9 +335,9 @@ def update_task_status( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -332,7 +357,21 @@ def query_tasks( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[TaskQueryResults]: """ - Query for tasks by a specified search criteria. + Searches for Tasks that match specified filtering criteria and returns matching tasks in paginated form. + + This method allows filtering tasks based on multiple criteria including: + - Parent task relationships + - Task status (with inclusive or exclusive filtering) + - Update time ranges + - Task view (manager or agent perspective) + - Task assignee + - Task type (via exact URL matches or prefix matching) + + Results are returned in pages. When more results are available than can be returned in a single + response, a page_token is provided that can be used in subsequent requests to retrieve the next + set of results. + + By default, this returns the latest task version for each matching task from the manager's perspective. Parameters ---------- @@ -341,7 +380,7 @@ def query_tasks( parent_task_id : typing.Optional[str] If present matches Tasks with this parent Task ID. - Note: this is mutually exclusive with all other query parameters, i.e., either provide parent Task ID, or + Note: this is mutually exclusive with all other query parameters, for example, either provide parent task ID, or any of the remaining parameters, but not both. status_filter : typing.Optional[TaskQueryStatusFilter] @@ -390,9 +429,9 @@ def query_tasks( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -401,9 +440,9 @@ def query_tasks( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -412,9 +451,9 @@ def query_tasks( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -431,6 +470,23 @@ def listen_as_agent( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[AgentRequest]: """ + Establishes a server streaming connection that delivers tasks to taskable agents for execution. + + This method creates a persistent connection from Tasks API to an agent, allowing the server + to push tasks to the agent as they become available. The agent receives a stream of tasks that + match its selector criteria (entity IDs). + + The stream delivers three types of requests: + - ExecuteRequest: Contains a new task for the agent to execute + - CancelRequest: Indicates a task should be canceled + - CompleteRequest: Indicates a task should be completed + + This is the primary method for taskable agents to receive and process tasks in real-time. + Agents should maintain this connection and process incoming tasks according to their capabilities. + + When an agent receives a task, it should update the task status using the UpdateStatus endpoint + to provide progress information back to Tasks API. + This is a long polling API that will block until a new task is ready for delivery. If no new task is available then the server will hold on to your request for up to 5 minutes, after that 5 minute timeout period you will be expected to reinitiate a new request. @@ -476,9 +532,9 @@ def listen_as_agent( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -487,9 +543,9 @@ def listen_as_agent( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -518,8 +574,14 @@ async def create_task( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[Task]: """ - Submit a request to create a task and schedule it for delivery. Tasks, once delivered, will - be asynchronously updated by their destined agent. + Creates a new Task in the system with the specified parameters. + + This method initiates a new task with a unique ID (either provided or auto-generated), + sets the initial task state to STATUS_CREATED, and establishes task ownership. The task + can be assigned to a specific agent through the Relations field. + + Once created, a task enters the lifecycle workflow and can be tracked, updated, and managed + through other Tasks API endpoints. Parameters ---------- @@ -534,7 +596,7 @@ async def create_task( Longer, free form human readable description of this Task. specification : typing.Optional[GoogleProtobufAny] - Full set of task parameters. + The path for the Protobuf task definition, and the complete task data. author : typing.Optional[Principal] @@ -600,9 +662,9 @@ async def create_task( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -611,9 +673,9 @@ async def create_task( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -627,6 +689,15 @@ async def get_task( self, task_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[Task]: """ + Retrieves a specific Task by its ID, with options to select a particular task version or view. + + This method returns detailed information about a task including its current status, + specification, relations, and other metadata. The response includes the complete Task object + with all associated fields. + + By default, the method returns the latest definition version of the task from the manager's + perspective. + Parameters ---------- task_id : str @@ -659,9 +730,9 @@ async def get_task( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -670,9 +741,9 @@ async def get_task( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -681,9 +752,9 @@ async def get_task( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -703,7 +774,17 @@ async def update_task_status( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[Task]: """ - Update the status of a task. + Updates the status of a Task as it progresses through its lifecycle. + + This method allows agents or operators to report the current state of a task, + which could include changes to task status, and error information. + + Each status update increments the task's status_version. When updating status, + clients must provide the current version to ensure consistency. The system rejects + updates with mismatched versions to prevent race conditions. + + Terminal states (`STATUS_DONE_OK` and `STATUS_DONE_NOT_OK`) are permanent; once a task + reaches these states, no further updates are allowed. Parameters ---------- @@ -761,9 +842,9 @@ async def update_task_status( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -772,9 +853,9 @@ async def update_task_status( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -783,9 +864,9 @@ async def update_task_status( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -805,7 +886,21 @@ async def query_tasks( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[TaskQueryResults]: """ - Query for tasks by a specified search criteria. + Searches for Tasks that match specified filtering criteria and returns matching tasks in paginated form. + + This method allows filtering tasks based on multiple criteria including: + - Parent task relationships + - Task status (with inclusive or exclusive filtering) + - Update time ranges + - Task view (manager or agent perspective) + - Task assignee + - Task type (via exact URL matches or prefix matching) + + Results are returned in pages. When more results are available than can be returned in a single + response, a page_token is provided that can be used in subsequent requests to retrieve the next + set of results. + + By default, this returns the latest task version for each matching task from the manager's perspective. Parameters ---------- @@ -814,7 +909,7 @@ async def query_tasks( parent_task_id : typing.Optional[str] If present matches Tasks with this parent Task ID. - Note: this is mutually exclusive with all other query parameters, i.e., either provide parent Task ID, or + Note: this is mutually exclusive with all other query parameters, for example, either provide parent task ID, or any of the remaining parameters, but not both. status_filter : typing.Optional[TaskQueryStatusFilter] @@ -863,9 +958,9 @@ async def query_tasks( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -874,9 +969,9 @@ async def query_tasks( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -885,9 +980,9 @@ async def query_tasks( raise NotFoundError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -904,6 +999,23 @@ async def listen_as_agent( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[AgentRequest]: """ + Establishes a server streaming connection that delivers tasks to taskable agents for execution. + + This method creates a persistent connection from Tasks API to an agent, allowing the server + to push tasks to the agent as they become available. The agent receives a stream of tasks that + match its selector criteria (entity IDs). + + The stream delivers three types of requests: + - ExecuteRequest: Contains a new task for the agent to execute + - CancelRequest: Indicates a task should be canceled + - CompleteRequest: Indicates a task should be completed + + This is the primary method for taskable agents to receive and process tasks in real-time. + Agents should maintain this connection and process incoming tasks according to their capabilities. + + When an agent receives a task, it should update the task status using the UpdateStatus endpoint + to provide progress information back to Tasks API. + This is a long polling API that will block until a new task is ready for delivery. If no new task is available then the server will hold on to your request for up to 5 minutes, after that 5 minute timeout period you will be expected to reinitiate a new request. @@ -949,9 +1061,9 @@ async def listen_as_agent( raise BadRequestError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), @@ -960,9 +1072,9 @@ async def listen_as_agent( raise UnauthorizedError( headers=dict(_response.headers), body=typing.cast( - typing.Optional[typing.Any], + typing.Any, parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore + type_=typing.Any, # type: ignore object_=_response.json(), ), ), diff --git a/src/anduril/types/__init__.py b/src/anduril/types/__init__.py index d12aa16..62190c6 100644 --- a/src/anduril/types/__init__.py +++ b/src/anduril/types/__init__.py @@ -52,6 +52,8 @@ from .entity_event_event_type import EntityEventEventType from .entity_event_response import EntityEventResponse from .entity_ids_selector import EntityIdsSelector + from .entity_manager_pose import EntityManagerPose + from .entity_manager_t_mat3 import EntityManagerTMat3 from .entity_stream_event import EntityStreamEvent from .entity_stream_heartbeat import EntityStreamHeartbeat from .enu import Enu @@ -97,13 +99,13 @@ from .media_item import MediaItem from .media_item_type import MediaItemType from .merged_from import MergedFrom - from .mil_std_2525_c import MilStd2525C + from .mil_std2525c import MilStd2525C from .mil_view import MilView from .mil_view_disposition import MilViewDisposition from .mil_view_environment import MilViewEnvironment from .mil_view_nationality import MilViewNationality - from .mode_5 import Mode5 - from .mode_5_mode_5_interrogation_response import Mode5Mode5InterrogationResponse + from .mode5 import Mode5 + from .mode5mode5interrogation_response import Mode5Mode5InterrogationResponse from .mode_s import ModeS from .non_primary_membership import NonPrimaryMembership from .ontology import Ontology @@ -161,8 +163,7 @@ from .supplies import Supplies from .symbology import Symbology from .system import System - from .t_mat_2 import TMat2 - from .t_mat_3 import TMat3 + from .t_mat2 import TMat2 from .target_priority import TargetPriority from .task import Task from .task_catalog import TaskCatalog @@ -181,8 +182,8 @@ from .tracked import Tracked from .tracked_by import TrackedBy from .transponder_codes import TransponderCodes - from .transponder_codes_mode_4_interrogation_response import TransponderCodesMode4InterrogationResponse - from .u_int_32_range import UInt32Range + from .transponder_codes_mode4interrogation_response import TransponderCodesMode4InterrogationResponse + from .u_int32range import UInt32Range from .user import User from .visual_details import VisualDetails _dynamic_imports: typing.Dict[str, str] = { @@ -232,6 +233,8 @@ "EntityEventEventType": ".entity_event_event_type", "EntityEventResponse": ".entity_event_response", "EntityIdsSelector": ".entity_ids_selector", + "EntityManagerPose": ".entity_manager_pose", + "EntityManagerTMat3": ".entity_manager_t_mat3", "EntityStreamEvent": ".entity_stream_event", "EntityStreamHeartbeat": ".entity_stream_heartbeat", "Enu": ".enu", @@ -277,13 +280,13 @@ "MediaItem": ".media_item", "MediaItemType": ".media_item_type", "MergedFrom": ".merged_from", - "MilStd2525C": ".mil_std_2525_c", + "MilStd2525C": ".mil_std2525c", "MilView": ".mil_view", "MilViewDisposition": ".mil_view_disposition", "MilViewEnvironment": ".mil_view_environment", "MilViewNationality": ".mil_view_nationality", - "Mode5": ".mode_5", - "Mode5Mode5InterrogationResponse": ".mode_5_mode_5_interrogation_response", + "Mode5": ".mode5", + "Mode5Mode5InterrogationResponse": ".mode5mode5interrogation_response", "ModeS": ".mode_s", "NonPrimaryMembership": ".non_primary_membership", "Ontology": ".ontology", @@ -341,8 +344,7 @@ "Supplies": ".supplies", "Symbology": ".symbology", "System": ".system", - "TMat2": ".t_mat_2", - "TMat3": ".t_mat_3", + "TMat2": ".t_mat2", "TargetPriority": ".target_priority", "Task": ".task", "TaskCatalog": ".task_catalog", @@ -361,8 +363,8 @@ "Tracked": ".tracked", "TrackedBy": ".tracked_by", "TransponderCodes": ".transponder_codes", - "TransponderCodesMode4InterrogationResponse": ".transponder_codes_mode_4_interrogation_response", - "UInt32Range": ".u_int_32_range", + "TransponderCodesMode4InterrogationResponse": ".transponder_codes_mode4interrogation_response", + "UInt32Range": ".u_int32range", "User": ".user", "VisualDetails": ".visual_details", } @@ -436,6 +438,8 @@ def __dir__(): "EntityEventEventType", "EntityEventResponse", "EntityIdsSelector", + "EntityManagerPose", + "EntityManagerTMat3", "EntityStreamEvent", "EntityStreamHeartbeat", "Enu", @@ -546,7 +550,6 @@ def __dir__(): "Symbology", "System", "TMat2", - "TMat3", "TargetPriority", "Task", "TaskCatalog", diff --git a/src/anduril/types/agent.py b/src/anduril/types/agent.py index 2b1ac28..633c481 100644 --- a/src/anduril/types/agent.py +++ b/src/anduril/types/agent.py @@ -10,7 +10,7 @@ class Agent(UniversalBaseModel): """ - Represents an Agent in the COP. + Represents an agent capable of processing tasks. """ entity_id: typing_extensions.Annotated[typing.Optional[str], FieldMetadata(alias="entityId")] = pydantic.Field( diff --git a/src/anduril/types/agent_request.py b/src/anduril/types/agent_request.py index 7dd56fa..5e80fd3 100644 --- a/src/anduril/types/agent_request.py +++ b/src/anduril/types/agent_request.py @@ -14,6 +14,19 @@ class AgentRequest(UniversalBaseModel): + """ + Response streamed to an agent containing task actions to perform. + + This message is streamed from Tasks API to agents and contains one of three + possible requests: execute a task, cancel a task, or complete a task. The agent + should process these requests according to its capabilities and report status + updates back to Tasks API using the UpdateStatus endpoint. + + Multiple responses may be sent for different tasks, and the agent should maintain + the connection to receive ongoing task requests. The connection may also be used + for heartbeat messages to ensure the agent is still responsive. + """ + execute_request: typing_extensions.Annotated[ typing.Optional[ExecuteRequest], FieldMetadata(alias="executeRequest") ] = None @@ -34,9 +47,4 @@ class Config: extra = pydantic.Extra.allow -from .entity import Entity # noqa: E402, F401, I001 -from .override import Override # noqa: E402, F401, I001 -from .overrides import Overrides # noqa: E402, F401, I001 -from .principal import Principal # noqa: E402, F401, I001 - update_forward_refs(AgentRequest) diff --git a/src/anduril/types/allocation.py b/src/anduril/types/allocation.py index af2c9b4..647375e 100644 --- a/src/anduril/types/allocation.py +++ b/src/anduril/types/allocation.py @@ -11,14 +11,14 @@ class Allocation(UniversalBaseModel): """ - Allocation contains a list of agents allocated to a Task. + Allocation contains a list of agents allocated to a task. """ active_agents: typing_extensions.Annotated[ typing.Optional[typing.List[Agent]], FieldMetadata(alias="activeAgents") ] = pydantic.Field(default=None) """ - Agents actively being utilized in a Task. + Agents actively being utilized in a task. """ if IS_PYDANTIC_V2: diff --git a/src/anduril/types/angle_of_arrival.py b/src/anduril/types/angle_of_arrival.py index 7920769..eef2c6e 100644 --- a/src/anduril/types/angle_of_arrival.py +++ b/src/anduril/types/angle_of_arrival.py @@ -7,7 +7,7 @@ from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from ..core.serialization import FieldMetadata from .pose import Pose -from .t_mat_2 import TMat2 +from .t_mat2 import TMat2 class AngleOfArrival(UniversalBaseModel): @@ -23,7 +23,7 @@ class AngleOfArrival(UniversalBaseModel): forward-left-up (FLU) frame where the x-axis (1, 0, 0) is pointing towards the target. """ - bearing_elevation_covariance_rad_2: typing_extensions.Annotated[ + bearing_elevation_covariance_rad2: typing_extensions.Annotated[ typing.Optional[TMat2], FieldMetadata(alias="bearingElevationCovarianceRad2") ] = pydantic.Field(default=None) """ diff --git a/src/anduril/types/cancel_request.py b/src/anduril/types/cancel_request.py index d791c00..05e760e 100644 --- a/src/anduril/types/cancel_request.py +++ b/src/anduril/types/cancel_request.py @@ -12,14 +12,15 @@ class CancelRequest(UniversalBaseModel): """ - Request to Cancel a Task. + The request to cancel a task. + Contains the task, and the assignee of the request to cancel the task. """ task_id: typing_extensions.Annotated[typing.Optional[str], FieldMetadata(alias="taskId")] = pydantic.Field( default=None ) """ - ID of the Task to cancel. + The unique task ID of the task to cancel. """ assignee: typing.Optional["Principal"] = pydantic.Field(default=None) @@ -38,6 +39,6 @@ class Config: extra = pydantic.Extra.allow -from .principal import Principal # noqa: E402, F401, I001 +from .principal import Principal # noqa: E402, I001 update_forward_refs(CancelRequest) diff --git a/src/anduril/types/complete_request.py b/src/anduril/types/complete_request.py index 1f36f98..7741822 100644 --- a/src/anduril/types/complete_request.py +++ b/src/anduril/types/complete_request.py @@ -10,7 +10,8 @@ class CompleteRequest(UniversalBaseModel): """ - Request to Complete a Task. + The request to complete a task. + Contains the unique ID of the task to complete. """ task_id: typing_extensions.Annotated[typing.Optional[str], FieldMetadata(alias="taskId")] = pydantic.Field( diff --git a/src/anduril/types/entity.py b/src/anduril/types/entity.py index 22ae604..f664004 100644 --- a/src/anduril/types/entity.py +++ b/src/anduril/types/entity.py @@ -52,8 +52,8 @@ class Entity(UniversalBaseModel): default=None ) """ - A Globally Unique Identifier (GUID) for your entity. If this field is empty, the Entity Manager API - automatically generates an ID when it creates the entity. + A Globally Unique Identifier (GUID) for your entity. This is a required + field. """ description: typing.Optional[str] = pydantic.Field(default=None) @@ -300,7 +300,6 @@ class Config: extra = pydantic.Extra.allow -from .override import Override # noqa: E402, F401, I001 -from .overrides import Overrides # noqa: E402, F401, I001 +from .overrides import Overrides # noqa: E402, I001 update_forward_refs(Entity) diff --git a/src/anduril/types/entity_event.py b/src/anduril/types/entity_event.py index 12a056b..cf8f5e3 100644 --- a/src/anduril/types/entity_event.py +++ b/src/anduril/types/entity_event.py @@ -33,8 +33,6 @@ class Config: extra = pydantic.Extra.allow -from .entity import Entity # noqa: E402, F401, I001 -from .override import Override # noqa: E402, F401, I001 -from .overrides import Overrides # noqa: E402, F401, I001 +from .entity import Entity # noqa: E402, I001 update_forward_refs(EntityEvent) diff --git a/src/anduril/types/entity_event_response.py b/src/anduril/types/entity_event_response.py index cac24a1..bb78dc3 100644 --- a/src/anduril/types/entity_event_response.py +++ b/src/anduril/types/entity_event_response.py @@ -33,8 +33,4 @@ class Config: extra = pydantic.Extra.allow -from .entity import Entity # noqa: E402, F401, I001 -from .override import Override # noqa: E402, F401, I001 -from .overrides import Overrides # noqa: E402, F401, I001 - update_forward_refs(EntityEventResponse) diff --git a/src/anduril/types/entity_manager_pose.py b/src/anduril/types/entity_manager_pose.py new file mode 100644 index 0000000..fef41d6 --- /dev/null +++ b/src/anduril/types/entity_manager_pose.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .position import Position +from .quaternion import Quaternion + + +class EntityManagerPose(UniversalBaseModel): + pos: typing.Optional[Position] = pydantic.Field(default=None) + """ + Geospatial location defined by this Pose. + """ + + orientation: typing.Optional[Quaternion] = pydantic.Field(default=None) + """ + The quaternion to transform a point in the Pose frame to the ENU frame. The Pose frame could be Body, Turret, + etc and is determined by the context in which this Pose is used. + The normal convention for defining orientation is to list the frames of transformation, for example + att_gimbal_to_enu is the quaternion which transforms a point in the gimbal frame to the body frame, but + in this case we truncate to att_enu because the Pose frame isn't defined. A potentially better name for this + field would have been att_pose_to_enu. + + Implementations of this quaternion should left multiply this quaternion to transform a point from the Pose frame + to the enu frame. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/anduril/types/t_mat_3.py b/src/anduril/types/entity_manager_t_mat3.py similarity index 94% rename from src/anduril/types/t_mat_3.py rename to src/anduril/types/entity_manager_t_mat3.py index 5c7bd7b..e6da67c 100644 --- a/src/anduril/types/t_mat_3.py +++ b/src/anduril/types/entity_manager_t_mat3.py @@ -6,7 +6,7 @@ from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -class TMat3(UniversalBaseModel): +class EntityManagerTMat3(UniversalBaseModel): """ Symmetric 3d matrix only representing the upper right triangle. """ diff --git a/src/anduril/types/entity_stream_event.py b/src/anduril/types/entity_stream_event.py index 59ff689..c7aea71 100644 --- a/src/anduril/types/entity_stream_event.py +++ b/src/anduril/types/entity_stream_event.py @@ -18,8 +18,3 @@ class Config: frozen = True smart_union = True extra = pydantic.Extra.allow - - -from .entity import Entity # noqa: E402, F401, I001 -from .override import Override # noqa: E402, F401, I001 -from .overrides import Overrides # noqa: E402, F401, I001 diff --git a/src/anduril/types/execute_request.py b/src/anduril/types/execute_request.py index e59edda..86780e5 100644 --- a/src/anduril/types/execute_request.py +++ b/src/anduril/types/execute_request.py @@ -11,12 +11,13 @@ class ExecuteRequest(UniversalBaseModel): """ - Request to execute a Task. + The request to execute a task. + Contains the unique ID of the task to execute. """ task: typing.Optional[Task] = pydantic.Field(default=None) """ - Task to execute. + The task to execute. """ if IS_PYDANTIC_V2: @@ -29,9 +30,4 @@ class Config: extra = pydantic.Extra.allow -from .entity import Entity # noqa: E402, F401, I001 -from .override import Override # noqa: E402, F401, I001 -from .overrides import Overrides # noqa: E402, F401, I001 -from .principal import Principal # noqa: E402, F401, I001 - update_forward_refs(ExecuteRequest) diff --git a/src/anduril/types/field_of_view.py b/src/anduril/types/field_of_view.py index dec1801..25508e5 100644 --- a/src/anduril/types/field_of_view.py +++ b/src/anduril/types/field_of_view.py @@ -6,8 +6,8 @@ import typing_extensions from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from ..core.serialization import FieldMetadata +from .entity_manager_pose import EntityManagerPose from .field_of_view_mode import FieldOfViewMode -from .pose import Pose from .position import Position from .projected_frustum import ProjectedFrustum @@ -47,9 +47,9 @@ class FieldOfView(UniversalBaseModel): Center ray of the frustum projected onto the ground. """ - center_ray_pose: typing_extensions.Annotated[typing.Optional[Pose], FieldMetadata(alias="centerRayPose")] = ( - pydantic.Field(default=None) - ) + center_ray_pose: typing_extensions.Annotated[ + typing.Optional[EntityManagerPose], FieldMetadata(alias="centerRayPose") + ] = pydantic.Field(default=None) """ The origin and direction of the center ray for this sensor relative to the ENU frame. A ray which is aligned with the positive X axis in the sensor frame will be transformed into the ray along the sensor direction in the ENU diff --git a/src/anduril/types/indicators.py b/src/anduril/types/indicators.py index 014ffab..af9f37f 100644 --- a/src/anduril/types/indicators.py +++ b/src/anduril/types/indicators.py @@ -3,9 +3,7 @@ import typing import pydantic -import typing_extensions from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from ..core.serialization import FieldMetadata class Indicators(UniversalBaseModel): @@ -16,7 +14,7 @@ class Indicators(UniversalBaseModel): simulated: typing.Optional[bool] = None exercise: typing.Optional[bool] = None emergency: typing.Optional[bool] = None - c_2: typing_extensions.Annotated[typing.Optional[bool], FieldMetadata(alias="c2")] = None + c2: typing.Optional[bool] = None egressable: typing.Optional[bool] = pydantic.Field(default=None) """ Indicates the Entity should be egressed to external sources. diff --git a/src/anduril/types/lla.py b/src/anduril/types/lla.py index b6dccaa..99dc821 100644 --- a/src/anduril/types/lla.py +++ b/src/anduril/types/lla.py @@ -13,7 +13,7 @@ class Lla(UniversalBaseModel): lon: typing.Optional[float] = None lat: typing.Optional[float] = None alt: typing.Optional[float] = None - is_2_d: typing_extensions.Annotated[typing.Optional[bool], FieldMetadata(alias="is2d")] = None + is2d: typing.Optional[bool] = None altitude_reference: typing_extensions.Annotated[ typing.Optional[LlaAltitudeReference], FieldMetadata(alias="altitudeReference") ] = pydantic.Field(default=None) diff --git a/src/anduril/types/location_uncertainty.py b/src/anduril/types/location_uncertainty.py index c5d6180..85f2b2e 100644 --- a/src/anduril/types/location_uncertainty.py +++ b/src/anduril/types/location_uncertainty.py @@ -6,8 +6,8 @@ import typing_extensions from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from ..core.serialization import FieldMetadata +from .entity_manager_t_mat3 import EntityManagerTMat3 from .error_ellipse import ErrorEllipse -from .t_mat_3 import TMat3 class LocationUncertainty(UniversalBaseModel): @@ -15,17 +15,17 @@ class LocationUncertainty(UniversalBaseModel): Uncertainty of entity position and velocity, if available. """ - position_enu_cov: typing_extensions.Annotated[typing.Optional[TMat3], FieldMetadata(alias="positionEnuCov")] = ( - pydantic.Field(default=None) - ) + position_enu_cov: typing_extensions.Annotated[ + typing.Optional[EntityManagerTMat3], FieldMetadata(alias="positionEnuCov") + ] = pydantic.Field(default=None) """ Positional covariance represented by the upper triangle of the covariance matrix. It is valid to populate only the diagonal of the matrix if the full covariance matrix is unknown. """ - velocity_enu_cov: typing_extensions.Annotated[typing.Optional[TMat3], FieldMetadata(alias="velocityEnuCov")] = ( - pydantic.Field(default=None) - ) + velocity_enu_cov: typing_extensions.Annotated[ + typing.Optional[EntityManagerTMat3], FieldMetadata(alias="velocityEnuCov") + ] = pydantic.Field(default=None) """ Velocity covariance represented by the upper triangle of the covariance matrix. It is valid to populate only the diagonal of the matrix if the full covariance matrix is unknown. diff --git a/src/anduril/types/mil_std_2525_c.py b/src/anduril/types/mil_std2525c.py similarity index 100% rename from src/anduril/types/mil_std_2525_c.py rename to src/anduril/types/mil_std2525c.py diff --git a/src/anduril/types/mode_5.py b/src/anduril/types/mode5.py similarity index 72% rename from src/anduril/types/mode_5.py rename to src/anduril/types/mode5.py index 1d63c45..4dcbe53 100644 --- a/src/anduril/types/mode_5.py +++ b/src/anduril/types/mode5.py @@ -6,7 +6,7 @@ import typing_extensions from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from ..core.serialization import FieldMetadata -from .mode_5_mode_5_interrogation_response import Mode5Mode5InterrogationResponse +from .mode5mode5interrogation_response import Mode5Mode5InterrogationResponse class Mode5(UniversalBaseModel): @@ -14,21 +14,19 @@ class Mode5(UniversalBaseModel): Describes the Mode 5 transponder interrogation status and codes. """ - mode_5_interrogation_response: typing_extensions.Annotated[ + mode5interrogation_response: typing_extensions.Annotated[ typing.Optional[Mode5Mode5InterrogationResponse], FieldMetadata(alias="mode5InterrogationResponse") ] = pydantic.Field(default=None) """ The validity of the response from the Mode 5 interrogation. """ - mode_5: typing_extensions.Annotated[typing.Optional[int], FieldMetadata(alias="mode5")] = pydantic.Field( - default=None - ) + mode5: typing.Optional[int] = pydantic.Field(default=None) """ The Mode 5 code assigned to military assets. """ - mode_5_platform_id: typing_extensions.Annotated[typing.Optional[int], FieldMetadata(alias="mode5PlatformId")] = ( + mode5platform_id: typing_extensions.Annotated[typing.Optional[int], FieldMetadata(alias="mode5PlatformId")] = ( pydantic.Field(default=None) ) """ diff --git a/src/anduril/types/mode_5_mode_5_interrogation_response.py b/src/anduril/types/mode5mode5interrogation_response.py similarity index 100% rename from src/anduril/types/mode_5_mode_5_interrogation_response.py rename to src/anduril/types/mode5mode5interrogation_response.py diff --git a/src/anduril/types/override.py b/src/anduril/types/override.py index 465da95..7cf10fb 100644 --- a/src/anduril/types/override.py +++ b/src/anduril/types/override.py @@ -71,7 +71,6 @@ class Config: extra = pydantic.Extra.allow -from .entity import Entity # noqa: E402, F401, I001 -from .overrides import Overrides # noqa: E402, F401, I001 +from .entity import Entity # noqa: E402, I001 update_forward_refs(Override) diff --git a/src/anduril/types/overrides.py b/src/anduril/types/overrides.py index 477eeaf..496b404 100644 --- a/src/anduril/types/overrides.py +++ b/src/anduril/types/overrides.py @@ -25,7 +25,6 @@ class Config: extra = pydantic.Extra.allow -from .entity import Entity # noqa: E402, F401, I001 -from .override import Override # noqa: E402, F401, I001 +from .override import Override # noqa: E402, I001 update_forward_refs(Overrides) diff --git a/src/anduril/types/owner.py b/src/anduril/types/owner.py index 6b6b962..32bc8a6 100644 --- a/src/anduril/types/owner.py +++ b/src/anduril/types/owner.py @@ -10,7 +10,7 @@ class Owner(UniversalBaseModel): """ - Owner designates the entity responsible for writes of Task data. + Owner designates the entity responsible for writes of task data. """ entity_id: typing_extensions.Annotated[typing.Optional[str], FieldMetadata(alias="entityId")] = pydantic.Field( diff --git a/src/anduril/types/principal.py b/src/anduril/types/principal.py index a5ce32c..bc71e06 100644 --- a/src/anduril/types/principal.py +++ b/src/anduril/types/principal.py @@ -15,7 +15,7 @@ class Principal(UniversalBaseModel): """ - A Principal is an entity that has authority over this Task. + A Principal is an entity that has authority over this task. """ system: typing.Optional[System] = None diff --git a/src/anduril/types/relations.py b/src/anduril/types/relations.py index 664062a..d0f86b7 100644 --- a/src/anduril/types/relations.py +++ b/src/anduril/types/relations.py @@ -12,19 +12,20 @@ class Relations(UniversalBaseModel): """ - Relations describes the relationships of this Task, such as assignment, or if the Task has any parents. + Describes the relationships associated with this task: the system assigned to + execute the task, and the parent task, if one exists. """ assignee: typing.Optional["Principal"] = pydantic.Field(default=None) """ - Who or what, if anyone, this Task is currently assigned to. + The system, user, or team assigned to the task. """ parent_task_id: typing_extensions.Annotated[typing.Optional[str], FieldMetadata(alias="parentTaskId")] = ( pydantic.Field(default=None) ) """ - If this Task is a "sub-Task", what is its parent, none if empty. + Identifies the parent task if the task is a sub-task. """ if IS_PYDANTIC_V2: @@ -37,6 +38,6 @@ class Config: extra = pydantic.Extra.allow -from .principal import Principal # noqa: E402, F401, I001 +from .principal import Principal # noqa: E402, I001 update_forward_refs(Relations) diff --git a/src/anduril/types/replication.py b/src/anduril/types/replication.py index 2695913..a84072b 100644 --- a/src/anduril/types/replication.py +++ b/src/anduril/types/replication.py @@ -11,14 +11,14 @@ class Replication(UniversalBaseModel): """ - Any metadata associated with the replication of a Task. + Any metadata associated with the replication of a task. """ stale_time: typing_extensions.Annotated[typing.Optional[dt.datetime], FieldMetadata(alias="staleTime")] = ( pydantic.Field(default=None) ) """ - Time by which this Task should be assumed to be stale. + The time by which this task should be assumed to be stale. """ if IS_PYDANTIC_V2: diff --git a/src/anduril/types/symbology.py b/src/anduril/types/symbology.py index 396575d..f1b43d9 100644 --- a/src/anduril/types/symbology.py +++ b/src/anduril/types/symbology.py @@ -6,7 +6,7 @@ import typing_extensions from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from ..core.serialization import FieldMetadata -from .mil_std_2525_c import MilStd2525C +from .mil_std2525c import MilStd2525C class Symbology(UniversalBaseModel): @@ -14,7 +14,7 @@ class Symbology(UniversalBaseModel): Symbology associated with an entity. """ - mil_std_2525_c: typing_extensions.Annotated[typing.Optional[MilStd2525C], FieldMetadata(alias="milStd2525C")] = None + mil_std2525c: typing_extensions.Annotated[typing.Optional[MilStd2525C], FieldMetadata(alias="milStd2525C")] = None if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/src/anduril/types/system.py b/src/anduril/types/system.py index 583dd1c..646d984 100644 --- a/src/anduril/types/system.py +++ b/src/anduril/types/system.py @@ -33,7 +33,7 @@ class System(UniversalBaseModel): """ Whether the System Principal (for example, an Asset) can own scheduling. This means we bypass manager-owned scheduling and defer to the system - Principal to handle scheduling and give us status updates for the Task. + Principal to handle scheduling and give us status updates for the task. Regardless of the value defined by the client, the Task Manager will determine and set this value appropriately. """ diff --git a/src/anduril/types/t_mat_2.py b/src/anduril/types/t_mat2.py similarity index 100% rename from src/anduril/types/t_mat_2.py rename to src/anduril/types/t_mat2.py diff --git a/src/anduril/types/task.py b/src/anduril/types/task.py index ced2b89..c9fbc25 100644 --- a/src/anduril/types/task.py +++ b/src/anduril/types/task.py @@ -20,38 +20,46 @@ class Task(UniversalBaseModel): """ - A Task is something an agent can be asked to do. + A task represents a structured unit of work that can be assigned to an agent for execution. + + Tasks are the fundamental building blocks of work assignment in the Lattice. + Each task has a unique identifier, a specification defining what needs to be done, + status information tracking its progress, and various metadata facilitating its lifecycle management. + + Tasks can be related to each other, through parent-child relationships, assigned to + specific agents, and tracked through a well-defined state machine from creation to completion. + They support rich status reporting, including progress updates, error handling, and results. """ version: typing.Optional[TaskVersion] = pydantic.Field(default=None) """ - Version of this Task. + Version of this task. """ display_name: typing_extensions.Annotated[typing.Optional[str], FieldMetadata(alias="displayName")] = ( pydantic.Field(default=None) ) """ - DEPRECATED: Human readable display name for this Task, should be short (<100 chars). + DEPRECATED: Human readable display name for this task, should be short (<100 chars). """ specification: typing.Optional[GoogleProtobufAny] = pydantic.Field(default=None) """ - Full Task parameterization. + The path for the Protobuf task definition, and the complete task data. """ created_by: typing_extensions.Annotated[typing.Optional["Principal"], FieldMetadata(alias="createdBy")] = ( pydantic.Field(default=None) ) """ - Records who created this Task. This field will not change after the Task has been created. + Records who created this task. This field will not change after the task has been created. """ last_updated_by: typing_extensions.Annotated[typing.Optional["Principal"], FieldMetadata(alias="lastUpdatedBy")] = ( pydantic.Field(default=None) ) """ - Records who updated this Task last. + Records who updated this task last. """ last_update_time: typing_extensions.Annotated[ @@ -63,44 +71,44 @@ class Task(UniversalBaseModel): status: typing.Optional[TaskStatus] = pydantic.Field(default=None) """ - The status of this Task. + The status of this task. """ scheduled_time: typing_extensions.Annotated[typing.Optional[dt.datetime], FieldMetadata(alias="scheduledTime")] = ( pydantic.Field(default=None) ) """ - If the Task has been scheduled to execute, what time it should execute at. + If the task has been scheduled to execute, what time it should execute at. """ relations: typing.Optional[Relations] = pydantic.Field(default=None) """ - Any related Tasks associated with this, typically includes an assignee for this Task and/or a parent. + Any related Tasks associated with this, typically includes an assignee for this task and/or a parent. """ description: typing.Optional[str] = pydantic.Field(default=None) """ - Longer, free form human readable description of this Task + Longer, free form human readable description of this task """ is_executed_elsewhere: typing_extensions.Annotated[ typing.Optional[bool], FieldMetadata(alias="isExecutedElsewhere") ] = pydantic.Field(default=None) """ - If set, execution of this Task is managed elsewhere, not by Task Manager. - In other words, Task manager will not attempt to update the assigned agent with execution instructions. + If set, execution of this task is managed elsewhere, not by Task Manager. + In other words, task manager will not attempt to update the assigned agent with execution instructions. """ create_time: typing_extensions.Annotated[typing.Optional[dt.datetime], FieldMetadata(alias="createTime")] = ( pydantic.Field(default=None) ) """ - Time of Task creation. + Time of task creation. """ replication: typing.Optional[Replication] = pydantic.Field(default=None) """ - If populated, designates this to be a replicated Task. + If populated, designates this to be a replicated task. """ initial_entities: typing_extensions.Annotated[ @@ -115,7 +123,7 @@ class Task(UniversalBaseModel): owner: typing.Optional[Owner] = pydantic.Field(default=None) """ - The networked owner of this Task. It is used to ensure that linear writes occur on the node responsible + The networked owner of this task. It is used to ensure that linear writes occur on the node responsible for replication of task data to other nodes running Task Manager. """ @@ -129,9 +137,6 @@ class Config: extra = pydantic.Extra.allow -from .principal import Principal # noqa: E402, F401, I001 -from .entity import Entity # noqa: E402, F401, I001 -from .override import Override # noqa: E402, F401, I001 -from .overrides import Overrides # noqa: E402, F401, I001 +from .principal import Principal # noqa: E402, I001 update_forward_refs(Task) diff --git a/src/anduril/types/task_entity.py b/src/anduril/types/task_entity.py index 6b2c42a..66d6471 100644 --- a/src/anduril/types/task_entity.py +++ b/src/anduril/types/task_entity.py @@ -10,12 +10,16 @@ class TaskEntity(UniversalBaseModel): """ - Wrapper of an entity passed in Tasking, used to hold an additional information, and as a future extension point. + An entity wrapper used in task definitions, with additional metadata. + + TaskEntity wraps an entity reference with additional contextual information for task execution. + This structure allows entities to be passed to tasks with supplementary metadata that aids + in proper task execution, while also serving as an extension point for future capabilities. """ entity: typing.Optional["Entity"] = pydantic.Field(default=None) """ - The wrapped entity-manager entity. + The wrapped entity. """ snapshot: typing.Optional[bool] = pydantic.Field(default=None) @@ -33,8 +37,6 @@ class Config: extra = pydantic.Extra.allow -from .entity import Entity # noqa: E402, F401, I001 -from .override import Override # noqa: E402, F401, I001 -from .overrides import Overrides # noqa: E402, F401, I001 +from .entity import Entity # noqa: E402, I001 update_forward_refs(TaskEntity) diff --git a/src/anduril/types/task_error.py b/src/anduril/types/task_error.py index 6dd9743..c8608b5 100644 --- a/src/anduril/types/task_error.py +++ b/src/anduril/types/task_error.py @@ -12,12 +12,16 @@ class TaskError(UniversalBaseModel): """ - TaskError contains an error code and message typically associated to a Task. + Error information associated with a task. + + TaskError contains structured error details, including an error code, a human-readable + message, and optional extended error information. This structure is used when a task + encounters problems during its lifecycle. """ code: typing.Optional[TaskErrorCode] = pydantic.Field(default=None) """ - Error code for Task error. + Error code for task error. """ message: typing.Optional[str] = pydantic.Field(default=None) diff --git a/src/anduril/types/task_query_results.py b/src/anduril/types/task_query_results.py index 496ac80..d5c66a9 100644 --- a/src/anduril/types/task_query_results.py +++ b/src/anduril/types/task_query_results.py @@ -12,6 +12,16 @@ class TaskQueryResults(UniversalBaseModel): + """ + Response containing tasks that match the query criteria. + + This message returns a list of Task objects that satisfy the filter conditions + specified in the request. When there are more matching tasks than can be returned + in a single response, a page_token is provided to retrieve the next batch in + a subsequent request. An empty tasks list with no page_token indicates that + there are no more matching tasks. + """ + tasks: typing.Optional[typing.List[Task]] = None next_page_token: typing_extensions.Annotated[typing.Optional[str], FieldMetadata(alias="nextPageToken")] = ( pydantic.Field(default=None) @@ -33,9 +43,4 @@ class Config: extra = pydantic.Extra.allow -from .entity import Entity # noqa: E402, F401, I001 -from .override import Override # noqa: E402, F401, I001 -from .overrides import Overrides # noqa: E402, F401, I001 -from .principal import Principal # noqa: E402, F401, I001 - update_forward_refs(TaskQueryResults) diff --git a/src/anduril/types/task_status.py b/src/anduril/types/task_status.py index cb04a92..7e2339c 100644 --- a/src/anduril/types/task_status.py +++ b/src/anduril/types/task_status.py @@ -15,47 +15,51 @@ class TaskStatus(UniversalBaseModel): """ - TaskStatus is contains information regarding the status of a Task at any given time. Can include related information - such as any progress towards Task completion, or any associated results if Task completed. + Comprehensive status information for a task at a given point in time. + + TaskStatus contains all status-related information for a task, including its current state, + any error conditions, progress details, results, timing information, and resource allocations. + This object evolves throughout a task's lifecycle, providing increasing detail as the task + progresses from creation through execution to completion. """ status: typing.Optional[TaskStatusStatus] = pydantic.Field(default=None) """ - Status of the Task. + Status of the task. """ task_error: typing_extensions.Annotated[typing.Optional[TaskError], FieldMetadata(alias="taskError")] = ( pydantic.Field(default=None) ) """ - Any errors associated with the Task. + Any errors associated with the task. """ progress: typing.Optional[GoogleProtobufAny] = pydantic.Field(default=None) """ - Any incremental progress on the Task, should be from the tasks/v* /progress folder. + Any incremental progress on the task, should be from the tasks/v* /progress folder. """ result: typing.Optional[GoogleProtobufAny] = pydantic.Field(default=None) """ - Any final result of the Task, should be from tasks/v* /result folder. + Any final result of the task, should be from tasks/v* /result folder. """ start_time: typing_extensions.Annotated[typing.Optional[dt.datetime], FieldMetadata(alias="startTime")] = ( pydantic.Field(default=None) ) """ - Time the Task began execution, may not be known even for executing Tasks. + Time the task began execution, may not be known even for executing Tasks. """ estimate: typing.Optional[GoogleProtobufAny] = pydantic.Field(default=None) """ - Any estimate for how the Task will progress, should be from tasks/v* /estimates folder. + Any estimate for how the task will progress, should be from tasks/v* /estimates folder. """ allocation: typing.Optional[Allocation] = pydantic.Field(default=None) """ - Any allocated agents of the Task. + Any allocated agents of the task. """ if IS_PYDANTIC_V2: diff --git a/src/anduril/types/task_version.py b/src/anduril/types/task_version.py index 0ba93ec..a5f60b0 100644 --- a/src/anduril/types/task_version.py +++ b/src/anduril/types/task_version.py @@ -10,28 +10,34 @@ class TaskVersion(UniversalBaseModel): """ - Version of a Task. + Versioning information for a task. + + TaskVersion provides a unique identifier for each task, along with separate version counters + for tracking changes to the task's definition and its status. This versioning system enables + optimistic concurrency control, ensuring that updates from multiple sources don't conflict. """ task_id: typing_extensions.Annotated[typing.Optional[str], FieldMetadata(alias="taskId")] = pydantic.Field( default=None ) """ - The unique ID for this Task. + The unique identifier for this task, used to distinguish it from all other tasks in the system. """ definition_version: typing_extensions.Annotated[typing.Optional[int], FieldMetadata(alias="definitionVersion")] = ( pydantic.Field(default=None) ) """ - Increments on definition (i.e. not TaskStatus) change. 0 is unset, starts at 1 on creation. + Counter that increments on changes to the task definition. + Unset (0) initially, starts at 1 on creation, and increments with each update to task fields. """ status_version: typing_extensions.Annotated[typing.Optional[int], FieldMetadata(alias="statusVersion")] = ( pydantic.Field(default=None) ) """ - Increments on changes to TaskStatus. 0 is unset, starts at 1 on creation. + Counter that increments on changes to TaskStatus. + Unset (0) initially, starts at 1 on creation, and increments with each status update. """ if IS_PYDANTIC_V2: diff --git a/src/anduril/types/tracked.py b/src/anduril/types/tracked.py index 4733764..781c054 100644 --- a/src/anduril/types/tracked.py +++ b/src/anduril/types/tracked.py @@ -8,7 +8,7 @@ from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from ..core.serialization import FieldMetadata from .line_of_bearing import LineOfBearing -from .u_int_32_range import UInt32Range +from .u_int32range import UInt32Range class Tracked(UniversalBaseModel): diff --git a/src/anduril/types/transponder_codes.py b/src/anduril/types/transponder_codes.py index 23c218f..946a8e9 100644 --- a/src/anduril/types/transponder_codes.py +++ b/src/anduril/types/transponder_codes.py @@ -6,9 +6,9 @@ import typing_extensions from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from ..core.serialization import FieldMetadata -from .mode_5 import Mode5 +from .mode5 import Mode5 from .mode_s import ModeS -from .transponder_codes_mode_4_interrogation_response import TransponderCodesMode4InterrogationResponse +from .transponder_codes_mode4interrogation_response import TransponderCodesMode4InterrogationResponse class TransponderCodes(UniversalBaseModel): @@ -16,37 +16,29 @@ class TransponderCodes(UniversalBaseModel): A message describing any transponder codes associated with Mode 1, 2, 3, 4, 5, S interrogations. """ - mode_1: typing_extensions.Annotated[typing.Optional[int], FieldMetadata(alias="mode1")] = pydantic.Field( - default=None - ) + mode1: typing.Optional[int] = pydantic.Field(default=None) """ The mode 1 code assigned to military assets. """ - mode_2: typing_extensions.Annotated[typing.Optional[int], FieldMetadata(alias="mode2")] = pydantic.Field( - default=None - ) + mode2: typing.Optional[int] = pydantic.Field(default=None) """ The Mode 2 code assigned to military assets. """ - mode_3: typing_extensions.Annotated[typing.Optional[int], FieldMetadata(alias="mode3")] = pydantic.Field( - default=None - ) + mode3: typing.Optional[int] = pydantic.Field(default=None) """ The Mode 3 code assigned by ATC to the asset. """ - mode_4_interrogation_response: typing_extensions.Annotated[ + mode4interrogation_response: typing_extensions.Annotated[ typing.Optional[TransponderCodesMode4InterrogationResponse], FieldMetadata(alias="mode4InterrogationResponse") ] = pydantic.Field(default=None) """ The validity of the response from the Mode 4 interrogation. """ - mode_5: typing_extensions.Annotated[typing.Optional[Mode5], FieldMetadata(alias="mode5")] = pydantic.Field( - default=None - ) + mode5: typing.Optional[Mode5] = pydantic.Field(default=None) """ The Mode 5 transponder codes. """ diff --git a/src/anduril/types/transponder_codes_mode_4_interrogation_response.py b/src/anduril/types/transponder_codes_mode4interrogation_response.py similarity index 100% rename from src/anduril/types/transponder_codes_mode_4_interrogation_response.py rename to src/anduril/types/transponder_codes_mode4interrogation_response.py diff --git a/src/anduril/types/u_int_32_range.py b/src/anduril/types/u_int32range.py similarity index 100% rename from src/anduril/types/u_int_32_range.py rename to src/anduril/types/u_int32range.py