diff --git a/.github/workflows/stlc-sync.yml b/.github/workflows/stlc-sync.yml index e45a6fb1..ee3c2f48 100644 --- a/.github/workflows/stlc-sync.yml +++ b/.github/workflows/stlc-sync.yml @@ -76,6 +76,8 @@ jobs: - name: Sync production to staging (fast-forward) if: steps.diff.outputs.behind == 'true' + env: + PUSH_TOKEN: ${{ secrets.PRODUCTION_REPO_TOKEN }} run: | # Refuse unless staging is an ancestor of production: otherwise the # trunks have forked and a fast-forward would be unsafe. @@ -83,7 +85,17 @@ jobs: echo "::error title=Back-sync blocked::staging main is not an ancestor of production/main." exit 1 fi - git push origin production/main:refs/heads/main + # Push as the automation identity (PRODUCTION_REPO_TOKEN), not GITHUB_TOKEN: + # staging main requires a pull request for humans and only the automation's + # repository role bypasses that rule. A token push (unlike GITHUB_TOKEN) also + # triggers seal-dispatch, so the config repo re-seals right away instead of + # at the next scheduled sync. Blank the persisted GITHUB_TOKEN header first. + if [ -n "${PUSH_TOKEN:-}" ]; then + git -c "http.https://github.com/.extraheader=" push \ + "https://x-access-token:${PUSH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" production/main:refs/heads/main + else + git push origin production/main:refs/heads/main + fi echo "Fast-forwarded staging/main to production/main." - name: Alert on failure diff --git a/.stats.yml b/.stats.yml index e8ba8c9f..b4620cc9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 32 +configured_endpoints: 58 diff --git a/api.md b/api.md index 08062d22..9048346f 100644 --- a/api.md +++ b/api.md @@ -186,12 +186,13 @@ Methods: Types: ```python -from openlayer.types.storage import PresignedURLCreateResponse +from openlayer.types.storage import PresignedURLCreateResponse, PresignedURLRetrieveResponse ``` Methods: - client.storage.presigned_url.create(\*\*params) -> PresignedURLCreateResponse +- client.storage.presigned_url.retrieve(\*\*params) -> PresignedURLRetrieveResponse # Tests @@ -205,3 +206,149 @@ Methods: - client.tests.evaluate(test_id, \*\*params) -> TestEvaluateResponse - client.tests.list_results(test_id, \*\*params) -> TestListResultsResponse + +# BackgroundTasks + +Types: + +```python +from openlayer.types import BackgroundTaskRetrieveResponse +``` + +Methods: + +- client.background_tasks.retrieve(task_id) -> BackgroundTaskRetrieveResponse + +# Governance + +## Frameworks + +Types: + +```python +from openlayer.types.governance import ( + FrameworkCreateResponse, + FrameworkRetrieveResponse, + FrameworkUpdateResponse, + FrameworkListResponse, + FrameworkExportResponse, + FrameworkListProjectRuleStatsResponse, + FrameworkListProjectsResponse, + FrameworkListRulesResponse, +) +``` + +Methods: + +- client.governance.frameworks.create(workspace_id, \*\*params) -> FrameworkCreateResponse +- client.governance.frameworks.retrieve(framework_id) -> FrameworkRetrieveResponse +- client.governance.frameworks.update(framework_id, \*\*params) -> FrameworkUpdateResponse +- client.governance.frameworks.list(workspace_id, \*\*params) -> FrameworkListResponse +- client.governance.frameworks.export(framework_id, \*\*params) -> FrameworkExportResponse +- client.governance.frameworks.list_project_rule_stats(framework_id, \*\*params) -> FrameworkListProjectRuleStatsResponse +- client.governance.frameworks.list_projects(framework_id, \*\*params) -> FrameworkListProjectsResponse +- client.governance.frameworks.list_rules(framework_id, \*\*params) -> FrameworkListRulesResponse + +### Documents + +Types: + +```python +from openlayer.types.governance.frameworks import DocumentRetrieveResponse, DocumentListResponse +``` + +Methods: + +- client.governance.frameworks.documents.retrieve(document_id, \*, framework_id) -> DocumentRetrieveResponse +- client.governance.frameworks.documents.list(framework_id, \*\*params) -> DocumentListResponse + +### Sections + +Types: + +```python +from openlayer.types.governance.frameworks import SectionListRulesResponse +``` + +Methods: + +- client.governance.frameworks.sections.list_rules(section_id, \*, framework_id, \*\*params) -> SectionListRulesResponse + +### Subsections + +Types: + +```python +from openlayer.types.governance.frameworks import SubsectionListRulesResponse +``` + +Methods: + +- client.governance.frameworks.subsections.list_rules(subsection_id, \*, framework_id, \*\*params) -> SubsectionListRulesResponse + +## Rules + +Types: + +```python +from openlayer.types.governance import ( + RuleCreateResponse, + RuleRetrieveResponse, + RuleUpdateResponse, + RuleListResponse, +) +``` + +Methods: + +- client.governance.rules.create(workspace_id, \*\*params) -> RuleCreateResponse +- client.governance.rules.retrieve(rule_id) -> RuleRetrieveResponse +- client.governance.rules.update(rule_id, \*\*params) -> RuleUpdateResponse +- client.governance.rules.list(workspace_id, \*\*params) -> RuleListResponse +- client.governance.rules.delete(rule_id) -> None + +## RuleResults + +Types: + +```python +from openlayer.types.governance import ( + RuleResultRetrieveResponse, + RuleResultUpdateResponse, + RuleResultListResponse, + RuleResultCreateEvidenceResponse, + RuleResultListEvidenceResponse, +) +``` + +Methods: + +- client.governance.rule_results.retrieve(rule_result_id) -> RuleResultRetrieveResponse +- client.governance.rule_results.update(rule_result_id, \*\*params) -> RuleResultUpdateResponse +- client.governance.rule_results.list(workspace_id, \*\*params) -> RuleResultListResponse +- client.governance.rule_results.create_evidence(rule_result_id, \*\*params) -> RuleResultCreateEvidenceResponse +- client.governance.rule_results.list_evidence(rule_result_id, \*\*params) -> RuleResultListEvidenceResponse + +## RuleStats + +Types: + +```python +from openlayer.types.governance import RuleStatRetrieveResponse +``` + +Methods: + +- client.governance.rule_stats.retrieve(workspace_id, \*\*params) -> RuleStatRetrieveResponse + +## RuleTags + +Types: + +```python +from openlayer.types.governance import RuleTagListResponse +``` + +Methods: + +- client.governance.rule_tags.list(workspace_id, \*\*params) -> RuleTagListResponse diff --git a/scripts/mock b/scripts/mock index 099a4c9c..45416b90 100755 --- a/scripts/mock +++ b/scripts/mock @@ -16,7 +16,7 @@ elif [ -n "$STAINLESS_OPENAPI_SPEC_URL" ]; then URL="$STAINLESS_OPENAPI_SPEC_URL" else # Embedded OpenAPI spec (base64-encoded, gzipped) - EMBEDDED_SPEC="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" + EMBEDDED_SPEC="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" SPEC_TMPFILE="$(mktemp)" trap 'rm -f "$SPEC_TMPFILE"' EXIT echo "$EMBEDDED_SPEC" | base64 --decode | gunzip > "$SPEC_TMPFILE" diff --git a/src/openlayer/_client.py b/src/openlayer/_client.py index 5d5ad74b..4a226707 100644 --- a/src/openlayer/_client.py +++ b/src/openlayer/_client.py @@ -36,11 +36,22 @@ ) if TYPE_CHECKING: - from .resources import tests, commits, storage, projects, workspaces, inference_pipelines + from .resources import ( + tests, + commits, + storage, + projects, + governance, + workspaces, + background_tasks, + inference_pipelines, + ) from .resources.tests import TestsResource, AsyncTestsResource from .resources.commits.commits import CommitsResource, AsyncCommitsResource from .resources.storage.storage import StorageResource, AsyncStorageResource + from .resources.background_tasks import BackgroundTasksResource, AsyncBackgroundTasksResource from .resources.projects.projects import ProjectsResource, AsyncProjectsResource + from .resources.governance.governance import GovernanceResource, AsyncGovernanceResource from .resources.workspaces.workspaces import WorkspacesResource, AsyncWorkspacesResource from .resources.inference_pipelines.inference_pipelines import ( InferencePipelinesResource, @@ -155,6 +166,18 @@ def tests(self) -> TestsResource: return TestsResource(self) + @cached_property + def background_tasks(self) -> BackgroundTasksResource: + from .resources.background_tasks import BackgroundTasksResource + + return BackgroundTasksResource(self) + + @cached_property + def governance(self) -> GovernanceResource: + from .resources.governance import GovernanceResource + + return GovernanceResource(self) + @cached_property def with_raw_response(self) -> OpenlayerWithRawResponse: return OpenlayerWithRawResponse(self) @@ -375,6 +398,18 @@ def tests(self) -> AsyncTestsResource: return AsyncTestsResource(self) + @cached_property + def background_tasks(self) -> AsyncBackgroundTasksResource: + from .resources.background_tasks import AsyncBackgroundTasksResource + + return AsyncBackgroundTasksResource(self) + + @cached_property + def governance(self) -> AsyncGovernanceResource: + from .resources.governance import AsyncGovernanceResource + + return AsyncGovernanceResource(self) + @cached_property def with_raw_response(self) -> AsyncOpenlayerWithRawResponse: return AsyncOpenlayerWithRawResponse(self) @@ -541,6 +576,18 @@ def tests(self) -> tests.TestsResourceWithRawResponse: return TestsResourceWithRawResponse(self._client.tests) + @cached_property + def background_tasks(self) -> background_tasks.BackgroundTasksResourceWithRawResponse: + from .resources.background_tasks import BackgroundTasksResourceWithRawResponse + + return BackgroundTasksResourceWithRawResponse(self._client.background_tasks) + + @cached_property + def governance(self) -> governance.GovernanceResourceWithRawResponse: + from .resources.governance import GovernanceResourceWithRawResponse + + return GovernanceResourceWithRawResponse(self._client.governance) + class AsyncOpenlayerWithRawResponse: _client: AsyncOpenlayer @@ -584,6 +631,18 @@ def tests(self) -> tests.AsyncTestsResourceWithRawResponse: return AsyncTestsResourceWithRawResponse(self._client.tests) + @cached_property + def background_tasks(self) -> background_tasks.AsyncBackgroundTasksResourceWithRawResponse: + from .resources.background_tasks import AsyncBackgroundTasksResourceWithRawResponse + + return AsyncBackgroundTasksResourceWithRawResponse(self._client.background_tasks) + + @cached_property + def governance(self) -> governance.AsyncGovernanceResourceWithRawResponse: + from .resources.governance import AsyncGovernanceResourceWithRawResponse + + return AsyncGovernanceResourceWithRawResponse(self._client.governance) + class OpenlayerWithStreamedResponse: _client: Openlayer @@ -627,6 +686,18 @@ def tests(self) -> tests.TestsResourceWithStreamingResponse: return TestsResourceWithStreamingResponse(self._client.tests) + @cached_property + def background_tasks(self) -> background_tasks.BackgroundTasksResourceWithStreamingResponse: + from .resources.background_tasks import BackgroundTasksResourceWithStreamingResponse + + return BackgroundTasksResourceWithStreamingResponse(self._client.background_tasks) + + @cached_property + def governance(self) -> governance.GovernanceResourceWithStreamingResponse: + from .resources.governance import GovernanceResourceWithStreamingResponse + + return GovernanceResourceWithStreamingResponse(self._client.governance) + class AsyncOpenlayerWithStreamedResponse: _client: AsyncOpenlayer @@ -670,6 +741,18 @@ def tests(self) -> tests.AsyncTestsResourceWithStreamingResponse: return AsyncTestsResourceWithStreamingResponse(self._client.tests) + @cached_property + def background_tasks(self) -> background_tasks.AsyncBackgroundTasksResourceWithStreamingResponse: + from .resources.background_tasks import AsyncBackgroundTasksResourceWithStreamingResponse + + return AsyncBackgroundTasksResourceWithStreamingResponse(self._client.background_tasks) + + @cached_property + def governance(self) -> governance.AsyncGovernanceResourceWithStreamingResponse: + from .resources.governance import AsyncGovernanceResourceWithStreamingResponse + + return AsyncGovernanceResourceWithStreamingResponse(self._client.governance) + Client = Openlayer diff --git a/src/openlayer/resources/__init__.py b/src/openlayer/resources/__init__.py index 291546cd..c6772d66 100644 --- a/src/openlayer/resources/__init__.py +++ b/src/openlayer/resources/__init__.py @@ -32,6 +32,14 @@ ProjectsResourceWithStreamingResponse, AsyncProjectsResourceWithStreamingResponse, ) +from .governance import ( + GovernanceResource, + AsyncGovernanceResource, + GovernanceResourceWithRawResponse, + AsyncGovernanceResourceWithRawResponse, + GovernanceResourceWithStreamingResponse, + AsyncGovernanceResourceWithStreamingResponse, +) from .workspaces import ( WorkspacesResource, AsyncWorkspacesResource, @@ -40,6 +48,14 @@ WorkspacesResourceWithStreamingResponse, AsyncWorkspacesResourceWithStreamingResponse, ) +from .background_tasks import ( + BackgroundTasksResource, + AsyncBackgroundTasksResource, + BackgroundTasksResourceWithRawResponse, + AsyncBackgroundTasksResourceWithRawResponse, + BackgroundTasksResourceWithStreamingResponse, + AsyncBackgroundTasksResourceWithStreamingResponse, +) from .inference_pipelines import ( InferencePipelinesResource, AsyncInferencePipelinesResource, @@ -86,4 +102,16 @@ "AsyncTestsResourceWithRawResponse", "TestsResourceWithStreamingResponse", "AsyncTestsResourceWithStreamingResponse", + "BackgroundTasksResource", + "AsyncBackgroundTasksResource", + "BackgroundTasksResourceWithRawResponse", + "AsyncBackgroundTasksResourceWithRawResponse", + "BackgroundTasksResourceWithStreamingResponse", + "AsyncBackgroundTasksResourceWithStreamingResponse", + "GovernanceResource", + "AsyncGovernanceResource", + "GovernanceResourceWithRawResponse", + "AsyncGovernanceResourceWithRawResponse", + "GovernanceResourceWithStreamingResponse", + "AsyncGovernanceResourceWithStreamingResponse", ] diff --git a/src/openlayer/resources/background_tasks.py b/src/openlayer/resources/background_tasks.py new file mode 100644 index 00000000..b3ae6088 --- /dev/null +++ b/src/openlayer/resources/background_tasks.py @@ -0,0 +1,172 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.background_task_retrieve_response import BackgroundTaskRetrieveResponse + +__all__ = ["BackgroundTasksResource", "AsyncBackgroundTasksResource"] + + +class BackgroundTasksResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> BackgroundTasksResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return BackgroundTasksResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BackgroundTasksResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return BackgroundTasksResourceWithStreamingResponse(self) + + def retrieve( + self, + task_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BackgroundTaskRetrieveResponse: + """ + Retrieve a background task's status, progress and results. + + Endpoints that cannot answer within one request queue a task and hand back its + id -- for example `POST /frameworks/{frameworkId}/export`. Poll this endpoint + until `complete` is `true`, then read what the task produced from `outputs`. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not task_id: + raise ValueError(f"Expected a non-empty value for `task_id` but received {task_id!r}") + return self._get( + path_template("/background-tasks/{task_id}", task_id=task_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BackgroundTaskRetrieveResponse, + ) + + +class AsyncBackgroundTasksResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncBackgroundTasksResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return AsyncBackgroundTasksResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBackgroundTasksResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return AsyncBackgroundTasksResourceWithStreamingResponse(self) + + async def retrieve( + self, + task_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BackgroundTaskRetrieveResponse: + """ + Retrieve a background task's status, progress and results. + + Endpoints that cannot answer within one request queue a task and hand back its + id -- for example `POST /frameworks/{frameworkId}/export`. Poll this endpoint + until `complete` is `true`, then read what the task produced from `outputs`. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not task_id: + raise ValueError(f"Expected a non-empty value for `task_id` but received {task_id!r}") + return await self._get( + path_template("/background-tasks/{task_id}", task_id=task_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BackgroundTaskRetrieveResponse, + ) + + +class BackgroundTasksResourceWithRawResponse: + def __init__(self, background_tasks: BackgroundTasksResource) -> None: + self._background_tasks = background_tasks + + self.retrieve = to_raw_response_wrapper( + background_tasks.retrieve, + ) + + +class AsyncBackgroundTasksResourceWithRawResponse: + def __init__(self, background_tasks: AsyncBackgroundTasksResource) -> None: + self._background_tasks = background_tasks + + self.retrieve = async_to_raw_response_wrapper( + background_tasks.retrieve, + ) + + +class BackgroundTasksResourceWithStreamingResponse: + def __init__(self, background_tasks: BackgroundTasksResource) -> None: + self._background_tasks = background_tasks + + self.retrieve = to_streamed_response_wrapper( + background_tasks.retrieve, + ) + + +class AsyncBackgroundTasksResourceWithStreamingResponse: + def __init__(self, background_tasks: AsyncBackgroundTasksResource) -> None: + self._background_tasks = background_tasks + + self.retrieve = async_to_streamed_response_wrapper( + background_tasks.retrieve, + ) diff --git a/src/openlayer/resources/governance/__init__.py b/src/openlayer/resources/governance/__init__.py new file mode 100644 index 00000000..cce59fe8 --- /dev/null +++ b/src/openlayer/resources/governance/__init__.py @@ -0,0 +1,89 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .rules import ( + RulesResource, + AsyncRulesResource, + RulesResourceWithRawResponse, + AsyncRulesResourceWithRawResponse, + RulesResourceWithStreamingResponse, + AsyncRulesResourceWithStreamingResponse, +) +from .rule_tags import ( + RuleTagsResource, + AsyncRuleTagsResource, + RuleTagsResourceWithRawResponse, + AsyncRuleTagsResourceWithRawResponse, + RuleTagsResourceWithStreamingResponse, + AsyncRuleTagsResourceWithStreamingResponse, +) +from .frameworks import ( + FrameworksResource, + AsyncFrameworksResource, + FrameworksResourceWithRawResponse, + AsyncFrameworksResourceWithRawResponse, + FrameworksResourceWithStreamingResponse, + AsyncFrameworksResourceWithStreamingResponse, +) +from .governance import ( + GovernanceResource, + AsyncGovernanceResource, + GovernanceResourceWithRawResponse, + AsyncGovernanceResourceWithRawResponse, + GovernanceResourceWithStreamingResponse, + AsyncGovernanceResourceWithStreamingResponse, +) +from .rule_stats import ( + RuleStatsResource, + AsyncRuleStatsResource, + RuleStatsResourceWithRawResponse, + AsyncRuleStatsResourceWithRawResponse, + RuleStatsResourceWithStreamingResponse, + AsyncRuleStatsResourceWithStreamingResponse, +) +from .rule_results import ( + RuleResultsResource, + AsyncRuleResultsResource, + RuleResultsResourceWithRawResponse, + AsyncRuleResultsResourceWithRawResponse, + RuleResultsResourceWithStreamingResponse, + AsyncRuleResultsResourceWithStreamingResponse, +) + +__all__ = [ + "FrameworksResource", + "AsyncFrameworksResource", + "FrameworksResourceWithRawResponse", + "AsyncFrameworksResourceWithRawResponse", + "FrameworksResourceWithStreamingResponse", + "AsyncFrameworksResourceWithStreamingResponse", + "RulesResource", + "AsyncRulesResource", + "RulesResourceWithRawResponse", + "AsyncRulesResourceWithRawResponse", + "RulesResourceWithStreamingResponse", + "AsyncRulesResourceWithStreamingResponse", + "RuleResultsResource", + "AsyncRuleResultsResource", + "RuleResultsResourceWithRawResponse", + "AsyncRuleResultsResourceWithRawResponse", + "RuleResultsResourceWithStreamingResponse", + "AsyncRuleResultsResourceWithStreamingResponse", + "RuleStatsResource", + "AsyncRuleStatsResource", + "RuleStatsResourceWithRawResponse", + "AsyncRuleStatsResourceWithRawResponse", + "RuleStatsResourceWithStreamingResponse", + "AsyncRuleStatsResourceWithStreamingResponse", + "RuleTagsResource", + "AsyncRuleTagsResource", + "RuleTagsResourceWithRawResponse", + "AsyncRuleTagsResourceWithRawResponse", + "RuleTagsResourceWithStreamingResponse", + "AsyncRuleTagsResourceWithStreamingResponse", + "GovernanceResource", + "AsyncGovernanceResource", + "GovernanceResourceWithRawResponse", + "AsyncGovernanceResourceWithRawResponse", + "GovernanceResourceWithStreamingResponse", + "AsyncGovernanceResourceWithStreamingResponse", +] diff --git a/src/openlayer/resources/governance/frameworks/__init__.py b/src/openlayer/resources/governance/frameworks/__init__.py new file mode 100644 index 00000000..bf062b6f --- /dev/null +++ b/src/openlayer/resources/governance/frameworks/__init__.py @@ -0,0 +1,61 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .sections import ( + SectionsResource, + AsyncSectionsResource, + SectionsResourceWithRawResponse, + AsyncSectionsResourceWithRawResponse, + SectionsResourceWithStreamingResponse, + AsyncSectionsResourceWithStreamingResponse, +) +from .documents import ( + DocumentsResource, + AsyncDocumentsResource, + DocumentsResourceWithRawResponse, + AsyncDocumentsResourceWithRawResponse, + DocumentsResourceWithStreamingResponse, + AsyncDocumentsResourceWithStreamingResponse, +) +from .frameworks import ( + FrameworksResource, + AsyncFrameworksResource, + FrameworksResourceWithRawResponse, + AsyncFrameworksResourceWithRawResponse, + FrameworksResourceWithStreamingResponse, + AsyncFrameworksResourceWithStreamingResponse, +) +from .subsections import ( + SubsectionsResource, + AsyncSubsectionsResource, + SubsectionsResourceWithRawResponse, + AsyncSubsectionsResourceWithRawResponse, + SubsectionsResourceWithStreamingResponse, + AsyncSubsectionsResourceWithStreamingResponse, +) + +__all__ = [ + "DocumentsResource", + "AsyncDocumentsResource", + "DocumentsResourceWithRawResponse", + "AsyncDocumentsResourceWithRawResponse", + "DocumentsResourceWithStreamingResponse", + "AsyncDocumentsResourceWithStreamingResponse", + "SectionsResource", + "AsyncSectionsResource", + "SectionsResourceWithRawResponse", + "AsyncSectionsResourceWithRawResponse", + "SectionsResourceWithStreamingResponse", + "AsyncSectionsResourceWithStreamingResponse", + "SubsectionsResource", + "AsyncSubsectionsResource", + "SubsectionsResourceWithRawResponse", + "AsyncSubsectionsResourceWithRawResponse", + "SubsectionsResourceWithStreamingResponse", + "AsyncSubsectionsResourceWithStreamingResponse", + "FrameworksResource", + "AsyncFrameworksResource", + "FrameworksResourceWithRawResponse", + "AsyncFrameworksResourceWithRawResponse", + "FrameworksResourceWithStreamingResponse", + "AsyncFrameworksResourceWithStreamingResponse", +] diff --git a/src/openlayer/resources/governance/frameworks/documents.py b/src/openlayer/resources/governance/frameworks/documents.py new file mode 100644 index 00000000..b863d104 --- /dev/null +++ b/src/openlayer/resources/governance/frameworks/documents.py @@ -0,0 +1,302 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ...._base_client import make_request_options +from ....types.governance.frameworks import document_list_params +from ....types.governance.frameworks.document_list_response import DocumentListResponse +from ....types.governance.frameworks.document_retrieve_response import DocumentRetrieveResponse + +__all__ = ["DocumentsResource", "AsyncDocumentsResource"] + + +class DocumentsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> DocumentsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return DocumentsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> DocumentsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return DocumentsResourceWithStreamingResponse(self) + + def retrieve( + self, + document_id: str, + *, + framework_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DocumentRetrieveResponse: + """ + Retrieve a framework document, including its sections, subsections, and the + rules mapped to each. + + Each section and subsection carries a `ruleCount`, so you can tell which + requirements have rules mapped to them before drilling in. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + if not document_id: + raise ValueError(f"Expected a non-empty value for `document_id` but received {document_id!r}") + return self._get( + path_template( + "/frameworks/{framework_id}/documents/{document_id}", framework_id=framework_id, document_id=document_id + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DocumentRetrieveResponse, + ) + + def list( + self, + framework_id: str, + *, + page: int | Omit = omit, + per_page: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DocumentListResponse: + """ + List the documents attached to a framework. + + A document holds the text of the standard the framework is based on, split into + sections and subsections. Retrieve a single document to get that structure, + along with the rules mapped to each part of it. + + Args: + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + return self._get( + path_template("/frameworks/{framework_id}/documents", framework_id=framework_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "page": page, + "per_page": per_page, + }, + document_list_params.DocumentListParams, + ), + ), + cast_to=DocumentListResponse, + ) + + +class AsyncDocumentsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncDocumentsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return AsyncDocumentsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncDocumentsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return AsyncDocumentsResourceWithStreamingResponse(self) + + async def retrieve( + self, + document_id: str, + *, + framework_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DocumentRetrieveResponse: + """ + Retrieve a framework document, including its sections, subsections, and the + rules mapped to each. + + Each section and subsection carries a `ruleCount`, so you can tell which + requirements have rules mapped to them before drilling in. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + if not document_id: + raise ValueError(f"Expected a non-empty value for `document_id` but received {document_id!r}") + return await self._get( + path_template( + "/frameworks/{framework_id}/documents/{document_id}", framework_id=framework_id, document_id=document_id + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DocumentRetrieveResponse, + ) + + async def list( + self, + framework_id: str, + *, + page: int | Omit = omit, + per_page: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DocumentListResponse: + """ + List the documents attached to a framework. + + A document holds the text of the standard the framework is based on, split into + sections and subsections. Retrieve a single document to get that structure, + along with the rules mapped to each part of it. + + Args: + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + return await self._get( + path_template("/frameworks/{framework_id}/documents", framework_id=framework_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "page": page, + "per_page": per_page, + }, + document_list_params.DocumentListParams, + ), + ), + cast_to=DocumentListResponse, + ) + + +class DocumentsResourceWithRawResponse: + def __init__(self, documents: DocumentsResource) -> None: + self._documents = documents + + self.retrieve = to_raw_response_wrapper( + documents.retrieve, + ) + self.list = to_raw_response_wrapper( + documents.list, + ) + + +class AsyncDocumentsResourceWithRawResponse: + def __init__(self, documents: AsyncDocumentsResource) -> None: + self._documents = documents + + self.retrieve = async_to_raw_response_wrapper( + documents.retrieve, + ) + self.list = async_to_raw_response_wrapper( + documents.list, + ) + + +class DocumentsResourceWithStreamingResponse: + def __init__(self, documents: DocumentsResource) -> None: + self._documents = documents + + self.retrieve = to_streamed_response_wrapper( + documents.retrieve, + ) + self.list = to_streamed_response_wrapper( + documents.list, + ) + + +class AsyncDocumentsResourceWithStreamingResponse: + def __init__(self, documents: AsyncDocumentsResource) -> None: + self._documents = documents + + self.retrieve = async_to_streamed_response_wrapper( + documents.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + documents.list, + ) diff --git a/src/openlayer/resources/governance/frameworks/frameworks.py b/src/openlayer/resources/governance/frameworks/frameworks.py new file mode 100644 index 00000000..834eccbe --- /dev/null +++ b/src/openlayer/resources/governance/frameworks/frameworks.py @@ -0,0 +1,1327 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Optional +from typing_extensions import Literal + +import httpx + +from .sections import ( + SectionsResource, + AsyncSectionsResource, + SectionsResourceWithRawResponse, + AsyncSectionsResourceWithRawResponse, + SectionsResourceWithStreamingResponse, + AsyncSectionsResourceWithStreamingResponse, +) +from ...._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform +from .documents import ( + DocumentsResource, + AsyncDocumentsResource, + DocumentsResourceWithRawResponse, + AsyncDocumentsResourceWithRawResponse, + DocumentsResourceWithStreamingResponse, + AsyncDocumentsResourceWithStreamingResponse, +) +from ...._compat import cached_property +from .subsections import ( + SubsectionsResource, + AsyncSubsectionsResource, + SubsectionsResourceWithRawResponse, + AsyncSubsectionsResourceWithRawResponse, + SubsectionsResourceWithStreamingResponse, + AsyncSubsectionsResourceWithStreamingResponse, +) +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ...._base_client import make_request_options +from ....types.governance import ( + framework_list_params, + framework_create_params, + framework_export_params, + framework_update_params, + framework_list_rules_params, + framework_list_projects_params, + framework_list_project_rule_stats_params, +) +from ....types.governance.framework_list_response import FrameworkListResponse +from ....types.governance.framework_create_response import FrameworkCreateResponse +from ....types.governance.framework_export_response import FrameworkExportResponse +from ....types.governance.framework_update_response import FrameworkUpdateResponse +from ....types.governance.framework_retrieve_response import FrameworkRetrieveResponse +from ....types.governance.framework_list_rules_response import FrameworkListRulesResponse +from ....types.governance.framework_list_projects_response import FrameworkListProjectsResponse +from ....types.governance.framework_list_project_rule_stats_response import FrameworkListProjectRuleStatsResponse + +__all__ = ["FrameworksResource", "AsyncFrameworksResource"] + + +class FrameworksResource(SyncAPIResource): + @cached_property + def documents(self) -> DocumentsResource: + return DocumentsResource(self._client) + + @cached_property + def sections(self) -> SectionsResource: + return SectionsResource(self._client) + + @cached_property + def subsections(self) -> SubsectionsResource: + return SubsectionsResource(self._client) + + @cached_property + def with_raw_response(self) -> FrameworksResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return FrameworksResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> FrameworksResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return FrameworksResourceWithStreamingResponse(self) + + def create( + self, + workspace_id: str, + *, + name: str, + description: Optional[str] | Omit = omit, + enabled: bool | Omit = omit, + project_selector: Optional[framework_create_params.ProjectSelector] | Omit = omit, + tags: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkCreateResponse: + """ + Create a custom governance framework in a workspace. + + Use this to track compliance against an internal policy, or against a standard + Openlayer does not ship as a built-in framework. A new framework starts with no + rules -- add them from the Openlayer app, or map an existing rule to it. + + A framework is created disabled unless you pass `enabled: true`. While it is + disabled its rules are not evaluated and do not count towards compliance. + + Args: + name: The framework name. + + description: A short description of the framework. + + enabled: Whether the framework is active. Rules of a disabled framework are not evaluated + and do not count towards compliance. + + project_selector: Determines which projects the framework applies to. An empty or `null` `match` + array applies the framework to every project in the workspace. + + tags: Free-form labels on the framework. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not workspace_id: + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._post( + path_template("/workspaces/{workspace_id}/frameworks", workspace_id=workspace_id), + body=maybe_transform( + { + "name": name, + "description": description, + "enabled": enabled, + "project_selector": project_selector, + "tags": tags, + }, + framework_create_params.FrameworkCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FrameworkCreateResponse, + ) + + def retrieve( + self, + framework_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkRetrieveResponse: + """ + Retrieve a governance framework by its id. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + return self._get( + path_template("/frameworks/{framework_id}", framework_id=framework_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FrameworkRetrieveResponse, + ) + + def update( + self, + framework_id: str, + *, + avatar: Optional[framework_update_params.Avatar] | Omit = omit, + description: Optional[str] | Omit = omit, + enabled: bool | Omit = omit, + extended_description: Optional[Dict[str, object]] | Omit = omit, + href: Optional[str] | Omit = omit, + name: str | Omit = omit, + project_selector: Optional[framework_update_params.ProjectSelector] | Omit = omit, + tags: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkUpdateResponse: + """ + Update a governance framework. + + The most common use is activating or deactivating a framework for the workspace + by setting `enabled`. Rules of a disabled framework are not evaluated and do not + count towards compliance. + + Frameworks that ship with Openlayer report `immutable: true`. For those, only + `enabled`, `tags`, and `projectSelector` can be changed -- their name and + definition are managed by Openlayer. + + Only the fields you send are changed. + + Args: + avatar: The icon shown for the framework. + + description: A short description of the framework. + + enabled: Whether the framework is active. Rules of a disabled framework are not evaluated + and do not count towards compliance. + + extended_description: A longer, rich-text description, as a TipTap JSON document. + + href: A link to the external standard or regulation the framework is based on. + + name: The framework name. + + project_selector: Determines which projects the framework applies to. An empty or `null` `match` + array applies the framework to every project in the workspace. + + tags: Free-form labels on the framework. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + return self._put( + path_template("/frameworks/{framework_id}", framework_id=framework_id), + body=maybe_transform( + { + "avatar": avatar, + "description": description, + "enabled": enabled, + "extended_description": extended_description, + "href": href, + "name": name, + "project_selector": project_selector, + "tags": tags, + }, + framework_update_params.FrameworkUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FrameworkUpdateResponse, + ) + + def list( + self, + workspace_id: str, + *, + asc: bool | Omit = omit, + completion_operator: Literal["is", ">", ">=", "<", "<=", "!="] | Omit = omit, + completion_value: int | Omit = omit, + enabled: bool | Omit = omit, + include_rule_stats: bool | Omit = omit, + page: int | Omit = omit, + per_page: int | Omit = omit, + project_id: str | Omit = omit, + search_query: str | Omit = omit, + sort_column: Literal[ + "name", "enabled", "dateCreated", "dateUpdated", "overallCompletion", "projectCompletionBuckets" + ] + | Omit = omit, + tags: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkListResponse: + """ + List the governance frameworks in a workspace. + + A framework is a set of rules -- drawn from a regulation, a standard, or your + own internal policy -- that Openlayer tracks compliance against. Use this + endpoint to find the framework you want to report on, then read its rules and + rule results. + + Args: + asc: Whether to sort in ascending order. + + completion_operator: How to compare each framework's completion percentage with `completionValue`. + Must be sent together with `completionValue`. + + completion_value: The completion percentage to compare against, from 0 to 100. + + enabled: Only include frameworks that are enabled (or disabled). + + include_rule_stats: Whether to include a `ruleStats` object on each framework, with its rule result + status counts and its per-project completion buckets. Computed over the returned + page only. + + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + project_id: Only include items that apply to this project. + + search_query: Filter by a free-text search over names and descriptions. + + sort_column: The column to sort on. + + tags: Only include frameworks carrying all of these tags. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not workspace_id: + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._get( + path_template("/workspaces/{workspace_id}/frameworks", workspace_id=workspace_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "asc": asc, + "completion_operator": completion_operator, + "completion_value": completion_value, + "enabled": enabled, + "include_rule_stats": include_rule_stats, + "page": page, + "per_page": per_page, + "project_id": project_id, + "search_query": search_query, + "sort_column": sort_column, + "tags": tags, + }, + framework_list_params.FrameworkListParams, + ), + ), + cast_to=FrameworkListResponse, + ) + + def export( + self, + framework_id: str, + *, + project_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkExportResponse: + """ + Export a framework's evidence and progress as an audit-ready zip archive. + + The archive holds every evidence file uploaded against the framework's + evidence-based rules, a markdown report of the framework's progress and the + status of all its rules (broken down by documentation section when the framework + has documents), and CSV manifests of rules and evidence with SHA-256 checksums. + + Send `projectId` to export one project's compliance with the framework. Omit it + for the workspace-wide view across every project in the framework, including + workspace-scoped rules. + + The export runs as a background task, so this returns `202` immediately. To + collect the archive: + + 1. Poll `GET /background-tasks/{taskId}` with the returned `taskResultId` until + `complete` is `true`. + 2. Read `outputs.storageUri` off that task. + 3. Exchange it for a download link at + `GET /storage/presigned-url?storageUri=`. + + Rate limited to 2 requests per minute per framework. Asking for an export while + an identical one is still queued returns that task rather than starting a second + one. + + Args: + project_id: Scope the export to this project. It must belong to the framework. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + return self._post( + path_template("/frameworks/{framework_id}/export", framework_id=framework_id), + body=maybe_transform({"project_id": project_id}, framework_export_params.FrameworkExportParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FrameworkExportResponse, + ) + + def list_project_rule_stats( + self, + framework_id: str, + *, + asc: bool | Omit = omit, + page: int | Omit = omit, + per_page: int | Omit = omit, + sort_column: Literal[ + "projectName", + "total", + "overallCompletion", + "totalPassing", + "totalFailing", + "totalSkipped", + "totalRunning", + "totalError", + "totalPending", + "totalDueSoon", + ] + | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkListProjectRuleStatsResponse: + """ + Get a compliance roll-up for a framework, one row per project it applies to. + + Each row counts the project's rule results by status, so you can report on where + a framework is complete and where it is not without fetching every individual + rule result. + + Args: + asc: Whether to sort in ascending order. + + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + sort_column: The column to sort on. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + return self._get( + path_template("/frameworks/{framework_id}/project-rule-stats", framework_id=framework_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "asc": asc, + "page": page, + "per_page": per_page, + "sort_column": sort_column, + }, + framework_list_project_rule_stats_params.FrameworkListProjectRuleStatsParams, + ), + ), + cast_to=FrameworkListProjectRuleStatsResponse, + ) + + def list_projects( + self, + framework_id: str, + *, + page: int | Omit = omit, + per_page: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkListProjectsResponse: + """ + List the projects a framework applies to. + + Which projects a framework covers is determined by its `projectSelector`. A + framework with an empty selector applies to every project in the workspace. + + Args: + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + return self._get( + path_template("/frameworks/{framework_id}/projects", framework_id=framework_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "page": page, + "per_page": per_page, + }, + framework_list_projects_params.FrameworkListProjectsParams, + ), + ), + cast_to=FrameworkListProjectsResponse, + ) + + def list_rules( + self, + framework_id: str, + *, + page: int | Omit = omit, + per_page: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkListRulesResponse: + """ + List the rules that belong to a framework. + + To read the compliance status of these rules, use + [List rule results](/api-reference/rest/governance/list-rule-results) with the + `frameworkId` filter, or fetch the results of an individual rule. + + Args: + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + return self._get( + path_template("/frameworks/{framework_id}/rules", framework_id=framework_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "page": page, + "per_page": per_page, + }, + framework_list_rules_params.FrameworkListRulesParams, + ), + ), + cast_to=FrameworkListRulesResponse, + ) + + +class AsyncFrameworksResource(AsyncAPIResource): + @cached_property + def documents(self) -> AsyncDocumentsResource: + return AsyncDocumentsResource(self._client) + + @cached_property + def sections(self) -> AsyncSectionsResource: + return AsyncSectionsResource(self._client) + + @cached_property + def subsections(self) -> AsyncSubsectionsResource: + return AsyncSubsectionsResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncFrameworksResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return AsyncFrameworksResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncFrameworksResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return AsyncFrameworksResourceWithStreamingResponse(self) + + async def create( + self, + workspace_id: str, + *, + name: str, + description: Optional[str] | Omit = omit, + enabled: bool | Omit = omit, + project_selector: Optional[framework_create_params.ProjectSelector] | Omit = omit, + tags: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkCreateResponse: + """ + Create a custom governance framework in a workspace. + + Use this to track compliance against an internal policy, or against a standard + Openlayer does not ship as a built-in framework. A new framework starts with no + rules -- add them from the Openlayer app, or map an existing rule to it. + + A framework is created disabled unless you pass `enabled: true`. While it is + disabled its rules are not evaluated and do not count towards compliance. + + Args: + name: The framework name. + + description: A short description of the framework. + + enabled: Whether the framework is active. Rules of a disabled framework are not evaluated + and do not count towards compliance. + + project_selector: Determines which projects the framework applies to. An empty or `null` `match` + array applies the framework to every project in the workspace. + + tags: Free-form labels on the framework. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not workspace_id: + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._post( + path_template("/workspaces/{workspace_id}/frameworks", workspace_id=workspace_id), + body=await async_maybe_transform( + { + "name": name, + "description": description, + "enabled": enabled, + "project_selector": project_selector, + "tags": tags, + }, + framework_create_params.FrameworkCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FrameworkCreateResponse, + ) + + async def retrieve( + self, + framework_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkRetrieveResponse: + """ + Retrieve a governance framework by its id. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + return await self._get( + path_template("/frameworks/{framework_id}", framework_id=framework_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FrameworkRetrieveResponse, + ) + + async def update( + self, + framework_id: str, + *, + avatar: Optional[framework_update_params.Avatar] | Omit = omit, + description: Optional[str] | Omit = omit, + enabled: bool | Omit = omit, + extended_description: Optional[Dict[str, object]] | Omit = omit, + href: Optional[str] | Omit = omit, + name: str | Omit = omit, + project_selector: Optional[framework_update_params.ProjectSelector] | Omit = omit, + tags: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkUpdateResponse: + """ + Update a governance framework. + + The most common use is activating or deactivating a framework for the workspace + by setting `enabled`. Rules of a disabled framework are not evaluated and do not + count towards compliance. + + Frameworks that ship with Openlayer report `immutable: true`. For those, only + `enabled`, `tags`, and `projectSelector` can be changed -- their name and + definition are managed by Openlayer. + + Only the fields you send are changed. + + Args: + avatar: The icon shown for the framework. + + description: A short description of the framework. + + enabled: Whether the framework is active. Rules of a disabled framework are not evaluated + and do not count towards compliance. + + extended_description: A longer, rich-text description, as a TipTap JSON document. + + href: A link to the external standard or regulation the framework is based on. + + name: The framework name. + + project_selector: Determines which projects the framework applies to. An empty or `null` `match` + array applies the framework to every project in the workspace. + + tags: Free-form labels on the framework. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + return await self._put( + path_template("/frameworks/{framework_id}", framework_id=framework_id), + body=await async_maybe_transform( + { + "avatar": avatar, + "description": description, + "enabled": enabled, + "extended_description": extended_description, + "href": href, + "name": name, + "project_selector": project_selector, + "tags": tags, + }, + framework_update_params.FrameworkUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FrameworkUpdateResponse, + ) + + async def list( + self, + workspace_id: str, + *, + asc: bool | Omit = omit, + completion_operator: Literal["is", ">", ">=", "<", "<=", "!="] | Omit = omit, + completion_value: int | Omit = omit, + enabled: bool | Omit = omit, + include_rule_stats: bool | Omit = omit, + page: int | Omit = omit, + per_page: int | Omit = omit, + project_id: str | Omit = omit, + search_query: str | Omit = omit, + sort_column: Literal[ + "name", "enabled", "dateCreated", "dateUpdated", "overallCompletion", "projectCompletionBuckets" + ] + | Omit = omit, + tags: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkListResponse: + """ + List the governance frameworks in a workspace. + + A framework is a set of rules -- drawn from a regulation, a standard, or your + own internal policy -- that Openlayer tracks compliance against. Use this + endpoint to find the framework you want to report on, then read its rules and + rule results. + + Args: + asc: Whether to sort in ascending order. + + completion_operator: How to compare each framework's completion percentage with `completionValue`. + Must be sent together with `completionValue`. + + completion_value: The completion percentage to compare against, from 0 to 100. + + enabled: Only include frameworks that are enabled (or disabled). + + include_rule_stats: Whether to include a `ruleStats` object on each framework, with its rule result + status counts and its per-project completion buckets. Computed over the returned + page only. + + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + project_id: Only include items that apply to this project. + + search_query: Filter by a free-text search over names and descriptions. + + sort_column: The column to sort on. + + tags: Only include frameworks carrying all of these tags. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not workspace_id: + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._get( + path_template("/workspaces/{workspace_id}/frameworks", workspace_id=workspace_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "asc": asc, + "completion_operator": completion_operator, + "completion_value": completion_value, + "enabled": enabled, + "include_rule_stats": include_rule_stats, + "page": page, + "per_page": per_page, + "project_id": project_id, + "search_query": search_query, + "sort_column": sort_column, + "tags": tags, + }, + framework_list_params.FrameworkListParams, + ), + ), + cast_to=FrameworkListResponse, + ) + + async def export( + self, + framework_id: str, + *, + project_id: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkExportResponse: + """ + Export a framework's evidence and progress as an audit-ready zip archive. + + The archive holds every evidence file uploaded against the framework's + evidence-based rules, a markdown report of the framework's progress and the + status of all its rules (broken down by documentation section when the framework + has documents), and CSV manifests of rules and evidence with SHA-256 checksums. + + Send `projectId` to export one project's compliance with the framework. Omit it + for the workspace-wide view across every project in the framework, including + workspace-scoped rules. + + The export runs as a background task, so this returns `202` immediately. To + collect the archive: + + 1. Poll `GET /background-tasks/{taskId}` with the returned `taskResultId` until + `complete` is `true`. + 2. Read `outputs.storageUri` off that task. + 3. Exchange it for a download link at + `GET /storage/presigned-url?storageUri=`. + + Rate limited to 2 requests per minute per framework. Asking for an export while + an identical one is still queued returns that task rather than starting a second + one. + + Args: + project_id: Scope the export to this project. It must belong to the framework. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + return await self._post( + path_template("/frameworks/{framework_id}/export", framework_id=framework_id), + body=await async_maybe_transform({"project_id": project_id}, framework_export_params.FrameworkExportParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FrameworkExportResponse, + ) + + async def list_project_rule_stats( + self, + framework_id: str, + *, + asc: bool | Omit = omit, + page: int | Omit = omit, + per_page: int | Omit = omit, + sort_column: Literal[ + "projectName", + "total", + "overallCompletion", + "totalPassing", + "totalFailing", + "totalSkipped", + "totalRunning", + "totalError", + "totalPending", + "totalDueSoon", + ] + | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkListProjectRuleStatsResponse: + """ + Get a compliance roll-up for a framework, one row per project it applies to. + + Each row counts the project's rule results by status, so you can report on where + a framework is complete and where it is not without fetching every individual + rule result. + + Args: + asc: Whether to sort in ascending order. + + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + sort_column: The column to sort on. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + return await self._get( + path_template("/frameworks/{framework_id}/project-rule-stats", framework_id=framework_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "asc": asc, + "page": page, + "per_page": per_page, + "sort_column": sort_column, + }, + framework_list_project_rule_stats_params.FrameworkListProjectRuleStatsParams, + ), + ), + cast_to=FrameworkListProjectRuleStatsResponse, + ) + + async def list_projects( + self, + framework_id: str, + *, + page: int | Omit = omit, + per_page: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkListProjectsResponse: + """ + List the projects a framework applies to. + + Which projects a framework covers is determined by its `projectSelector`. A + framework with an empty selector applies to every project in the workspace. + + Args: + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + return await self._get( + path_template("/frameworks/{framework_id}/projects", framework_id=framework_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "page": page, + "per_page": per_page, + }, + framework_list_projects_params.FrameworkListProjectsParams, + ), + ), + cast_to=FrameworkListProjectsResponse, + ) + + async def list_rules( + self, + framework_id: str, + *, + page: int | Omit = omit, + per_page: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FrameworkListRulesResponse: + """ + List the rules that belong to a framework. + + To read the compliance status of these rules, use + [List rule results](/api-reference/rest/governance/list-rule-results) with the + `frameworkId` filter, or fetch the results of an individual rule. + + Args: + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + return await self._get( + path_template("/frameworks/{framework_id}/rules", framework_id=framework_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "page": page, + "per_page": per_page, + }, + framework_list_rules_params.FrameworkListRulesParams, + ), + ), + cast_to=FrameworkListRulesResponse, + ) + + +class FrameworksResourceWithRawResponse: + def __init__(self, frameworks: FrameworksResource) -> None: + self._frameworks = frameworks + + self.create = to_raw_response_wrapper( + frameworks.create, + ) + self.retrieve = to_raw_response_wrapper( + frameworks.retrieve, + ) + self.update = to_raw_response_wrapper( + frameworks.update, + ) + self.list = to_raw_response_wrapper( + frameworks.list, + ) + self.export = to_raw_response_wrapper( + frameworks.export, + ) + self.list_project_rule_stats = to_raw_response_wrapper( + frameworks.list_project_rule_stats, + ) + self.list_projects = to_raw_response_wrapper( + frameworks.list_projects, + ) + self.list_rules = to_raw_response_wrapper( + frameworks.list_rules, + ) + + @cached_property + def documents(self) -> DocumentsResourceWithRawResponse: + return DocumentsResourceWithRawResponse(self._frameworks.documents) + + @cached_property + def sections(self) -> SectionsResourceWithRawResponse: + return SectionsResourceWithRawResponse(self._frameworks.sections) + + @cached_property + def subsections(self) -> SubsectionsResourceWithRawResponse: + return SubsectionsResourceWithRawResponse(self._frameworks.subsections) + + +class AsyncFrameworksResourceWithRawResponse: + def __init__(self, frameworks: AsyncFrameworksResource) -> None: + self._frameworks = frameworks + + self.create = async_to_raw_response_wrapper( + frameworks.create, + ) + self.retrieve = async_to_raw_response_wrapper( + frameworks.retrieve, + ) + self.update = async_to_raw_response_wrapper( + frameworks.update, + ) + self.list = async_to_raw_response_wrapper( + frameworks.list, + ) + self.export = async_to_raw_response_wrapper( + frameworks.export, + ) + self.list_project_rule_stats = async_to_raw_response_wrapper( + frameworks.list_project_rule_stats, + ) + self.list_projects = async_to_raw_response_wrapper( + frameworks.list_projects, + ) + self.list_rules = async_to_raw_response_wrapper( + frameworks.list_rules, + ) + + @cached_property + def documents(self) -> AsyncDocumentsResourceWithRawResponse: + return AsyncDocumentsResourceWithRawResponse(self._frameworks.documents) + + @cached_property + def sections(self) -> AsyncSectionsResourceWithRawResponse: + return AsyncSectionsResourceWithRawResponse(self._frameworks.sections) + + @cached_property + def subsections(self) -> AsyncSubsectionsResourceWithRawResponse: + return AsyncSubsectionsResourceWithRawResponse(self._frameworks.subsections) + + +class FrameworksResourceWithStreamingResponse: + def __init__(self, frameworks: FrameworksResource) -> None: + self._frameworks = frameworks + + self.create = to_streamed_response_wrapper( + frameworks.create, + ) + self.retrieve = to_streamed_response_wrapper( + frameworks.retrieve, + ) + self.update = to_streamed_response_wrapper( + frameworks.update, + ) + self.list = to_streamed_response_wrapper( + frameworks.list, + ) + self.export = to_streamed_response_wrapper( + frameworks.export, + ) + self.list_project_rule_stats = to_streamed_response_wrapper( + frameworks.list_project_rule_stats, + ) + self.list_projects = to_streamed_response_wrapper( + frameworks.list_projects, + ) + self.list_rules = to_streamed_response_wrapper( + frameworks.list_rules, + ) + + @cached_property + def documents(self) -> DocumentsResourceWithStreamingResponse: + return DocumentsResourceWithStreamingResponse(self._frameworks.documents) + + @cached_property + def sections(self) -> SectionsResourceWithStreamingResponse: + return SectionsResourceWithStreamingResponse(self._frameworks.sections) + + @cached_property + def subsections(self) -> SubsectionsResourceWithStreamingResponse: + return SubsectionsResourceWithStreamingResponse(self._frameworks.subsections) + + +class AsyncFrameworksResourceWithStreamingResponse: + def __init__(self, frameworks: AsyncFrameworksResource) -> None: + self._frameworks = frameworks + + self.create = async_to_streamed_response_wrapper( + frameworks.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + frameworks.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + frameworks.update, + ) + self.list = async_to_streamed_response_wrapper( + frameworks.list, + ) + self.export = async_to_streamed_response_wrapper( + frameworks.export, + ) + self.list_project_rule_stats = async_to_streamed_response_wrapper( + frameworks.list_project_rule_stats, + ) + self.list_projects = async_to_streamed_response_wrapper( + frameworks.list_projects, + ) + self.list_rules = async_to_streamed_response_wrapper( + frameworks.list_rules, + ) + + @cached_property + def documents(self) -> AsyncDocumentsResourceWithStreamingResponse: + return AsyncDocumentsResourceWithStreamingResponse(self._frameworks.documents) + + @cached_property + def sections(self) -> AsyncSectionsResourceWithStreamingResponse: + return AsyncSectionsResourceWithStreamingResponse(self._frameworks.sections) + + @cached_property + def subsections(self) -> AsyncSubsectionsResourceWithStreamingResponse: + return AsyncSubsectionsResourceWithStreamingResponse(self._frameworks.subsections) diff --git a/src/openlayer/resources/governance/frameworks/sections.py b/src/openlayer/resources/governance/frameworks/sections.py new file mode 100644 index 00000000..7cfcb382 --- /dev/null +++ b/src/openlayer/resources/governance/frameworks/sections.py @@ -0,0 +1,253 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ...._base_client import make_request_options +from ....types.governance.frameworks import section_list_rules_params +from ....types.governance.frameworks.section_list_rules_response import SectionListRulesResponse + +__all__ = ["SectionsResource", "AsyncSectionsResource"] + + +class SectionsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SectionsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return SectionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SectionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return SectionsResourceWithStreamingResponse(self) + + def list_rules( + self, + section_id: str, + *, + framework_id: str, + include_results: bool | Omit = omit, + include_subsection_rules: bool | Omit = omit, + page: int | Omit = omit, + per_page: int | Omit = omit, + project_id: str | Omit = omit, + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SectionListRulesResponse: + """ + List the rules mapped to a section of a framework document. + + Pass `includeSubsectionRules=true` to also return the rules mapped to the + section's subsections, which is how you get every rule covering a requirement + and everything under it. + + Args: + include_results: Whether to include each rule's results inline, in a `results` array. + + include_subsection_rules: Whether to also include the rules mapped to the section's subsections. + + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + project_id: Only include items that apply to this project. + + status: Only include items whose rule result has this compliance status. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + if not section_id: + raise ValueError(f"Expected a non-empty value for `section_id` but received {section_id!r}") + return self._get( + path_template( + "/frameworks/{framework_id}/sections/{section_id}/rules", + framework_id=framework_id, + section_id=section_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "include_results": include_results, + "include_subsection_rules": include_subsection_rules, + "page": page, + "per_page": per_page, + "project_id": project_id, + "status": status, + }, + section_list_rules_params.SectionListRulesParams, + ), + ), + cast_to=SectionListRulesResponse, + ) + + +class AsyncSectionsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSectionsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return AsyncSectionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSectionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return AsyncSectionsResourceWithStreamingResponse(self) + + async def list_rules( + self, + section_id: str, + *, + framework_id: str, + include_results: bool | Omit = omit, + include_subsection_rules: bool | Omit = omit, + page: int | Omit = omit, + per_page: int | Omit = omit, + project_id: str | Omit = omit, + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SectionListRulesResponse: + """ + List the rules mapped to a section of a framework document. + + Pass `includeSubsectionRules=true` to also return the rules mapped to the + section's subsections, which is how you get every rule covering a requirement + and everything under it. + + Args: + include_results: Whether to include each rule's results inline, in a `results` array. + + include_subsection_rules: Whether to also include the rules mapped to the section's subsections. + + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + project_id: Only include items that apply to this project. + + status: Only include items whose rule result has this compliance status. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + if not section_id: + raise ValueError(f"Expected a non-empty value for `section_id` but received {section_id!r}") + return await self._get( + path_template( + "/frameworks/{framework_id}/sections/{section_id}/rules", + framework_id=framework_id, + section_id=section_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "include_results": include_results, + "include_subsection_rules": include_subsection_rules, + "page": page, + "per_page": per_page, + "project_id": project_id, + "status": status, + }, + section_list_rules_params.SectionListRulesParams, + ), + ), + cast_to=SectionListRulesResponse, + ) + + +class SectionsResourceWithRawResponse: + def __init__(self, sections: SectionsResource) -> None: + self._sections = sections + + self.list_rules = to_raw_response_wrapper( + sections.list_rules, + ) + + +class AsyncSectionsResourceWithRawResponse: + def __init__(self, sections: AsyncSectionsResource) -> None: + self._sections = sections + + self.list_rules = async_to_raw_response_wrapper( + sections.list_rules, + ) + + +class SectionsResourceWithStreamingResponse: + def __init__(self, sections: SectionsResource) -> None: + self._sections = sections + + self.list_rules = to_streamed_response_wrapper( + sections.list_rules, + ) + + +class AsyncSectionsResourceWithStreamingResponse: + def __init__(self, sections: AsyncSectionsResource) -> None: + self._sections = sections + + self.list_rules = async_to_streamed_response_wrapper( + sections.list_rules, + ) diff --git a/src/openlayer/resources/governance/frameworks/subsections.py b/src/openlayer/resources/governance/frameworks/subsections.py new file mode 100644 index 00000000..564325ce --- /dev/null +++ b/src/openlayer/resources/governance/frameworks/subsections.py @@ -0,0 +1,245 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ...._base_client import make_request_options +from ....types.governance.frameworks import subsection_list_rules_params +from ....types.governance.frameworks.subsection_list_rules_response import SubsectionListRulesResponse + +__all__ = ["SubsectionsResource", "AsyncSubsectionsResource"] + + +class SubsectionsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SubsectionsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return SubsectionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SubsectionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return SubsectionsResourceWithStreamingResponse(self) + + def list_rules( + self, + subsection_id: str, + *, + framework_id: str, + include_results: bool | Omit = omit, + page: int | Omit = omit, + per_page: int | Omit = omit, + project_id: str | Omit = omit, + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SubsectionListRulesResponse: + """ + List the rules mapped to a subsection of a framework document. + + A subsection is usually the level at which a standard states an individual + requirement, so this is the endpoint to use when you want to show which rules + cover a specific clause. + + Args: + include_results: Whether to include each rule's results inline, in a `results` array. + + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + project_id: Only include items that apply to this project. + + status: Only include items whose rule result has this compliance status. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + if not subsection_id: + raise ValueError(f"Expected a non-empty value for `subsection_id` but received {subsection_id!r}") + return self._get( + path_template( + "/frameworks/{framework_id}/subsections/{subsection_id}/rules", + framework_id=framework_id, + subsection_id=subsection_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "include_results": include_results, + "page": page, + "per_page": per_page, + "project_id": project_id, + "status": status, + }, + subsection_list_rules_params.SubsectionListRulesParams, + ), + ), + cast_to=SubsectionListRulesResponse, + ) + + +class AsyncSubsectionsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSubsectionsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return AsyncSubsectionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSubsectionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return AsyncSubsectionsResourceWithStreamingResponse(self) + + async def list_rules( + self, + subsection_id: str, + *, + framework_id: str, + include_results: bool | Omit = omit, + page: int | Omit = omit, + per_page: int | Omit = omit, + project_id: str | Omit = omit, + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SubsectionListRulesResponse: + """ + List the rules mapped to a subsection of a framework document. + + A subsection is usually the level at which a standard states an individual + requirement, so this is the endpoint to use when you want to show which rules + cover a specific clause. + + Args: + include_results: Whether to include each rule's results inline, in a `results` array. + + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + project_id: Only include items that apply to this project. + + status: Only include items whose rule result has this compliance status. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not framework_id: + raise ValueError(f"Expected a non-empty value for `framework_id` but received {framework_id!r}") + if not subsection_id: + raise ValueError(f"Expected a non-empty value for `subsection_id` but received {subsection_id!r}") + return await self._get( + path_template( + "/frameworks/{framework_id}/subsections/{subsection_id}/rules", + framework_id=framework_id, + subsection_id=subsection_id, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "include_results": include_results, + "page": page, + "per_page": per_page, + "project_id": project_id, + "status": status, + }, + subsection_list_rules_params.SubsectionListRulesParams, + ), + ), + cast_to=SubsectionListRulesResponse, + ) + + +class SubsectionsResourceWithRawResponse: + def __init__(self, subsections: SubsectionsResource) -> None: + self._subsections = subsections + + self.list_rules = to_raw_response_wrapper( + subsections.list_rules, + ) + + +class AsyncSubsectionsResourceWithRawResponse: + def __init__(self, subsections: AsyncSubsectionsResource) -> None: + self._subsections = subsections + + self.list_rules = async_to_raw_response_wrapper( + subsections.list_rules, + ) + + +class SubsectionsResourceWithStreamingResponse: + def __init__(self, subsections: SubsectionsResource) -> None: + self._subsections = subsections + + self.list_rules = to_streamed_response_wrapper( + subsections.list_rules, + ) + + +class AsyncSubsectionsResourceWithStreamingResponse: + def __init__(self, subsections: AsyncSubsectionsResource) -> None: + self._subsections = subsections + + self.list_rules = async_to_streamed_response_wrapper( + subsections.list_rules, + ) diff --git a/src/openlayer/resources/governance/governance.py b/src/openlayer/resources/governance/governance.py new file mode 100644 index 00000000..c5c82818 --- /dev/null +++ b/src/openlayer/resources/governance/governance.py @@ -0,0 +1,230 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .rules import ( + RulesResource, + AsyncRulesResource, + RulesResourceWithRawResponse, + AsyncRulesResourceWithRawResponse, + RulesResourceWithStreamingResponse, + AsyncRulesResourceWithStreamingResponse, +) +from ..._compat import cached_property +from .rule_tags import ( + RuleTagsResource, + AsyncRuleTagsResource, + RuleTagsResourceWithRawResponse, + AsyncRuleTagsResourceWithRawResponse, + RuleTagsResourceWithStreamingResponse, + AsyncRuleTagsResourceWithStreamingResponse, +) +from .rule_stats import ( + RuleStatsResource, + AsyncRuleStatsResource, + RuleStatsResourceWithRawResponse, + AsyncRuleStatsResourceWithRawResponse, + RuleStatsResourceWithStreamingResponse, + AsyncRuleStatsResourceWithStreamingResponse, +) +from ..._resource import SyncAPIResource, AsyncAPIResource +from .rule_results import ( + RuleResultsResource, + AsyncRuleResultsResource, + RuleResultsResourceWithRawResponse, + AsyncRuleResultsResourceWithRawResponse, + RuleResultsResourceWithStreamingResponse, + AsyncRuleResultsResourceWithStreamingResponse, +) +from .frameworks.frameworks import ( + FrameworksResource, + AsyncFrameworksResource, + FrameworksResourceWithRawResponse, + AsyncFrameworksResourceWithRawResponse, + FrameworksResourceWithStreamingResponse, + AsyncFrameworksResourceWithStreamingResponse, +) + +__all__ = ["GovernanceResource", "AsyncGovernanceResource"] + + +class GovernanceResource(SyncAPIResource): + @cached_property + def frameworks(self) -> FrameworksResource: + return FrameworksResource(self._client) + + @cached_property + def rules(self) -> RulesResource: + return RulesResource(self._client) + + @cached_property + def rule_results(self) -> RuleResultsResource: + return RuleResultsResource(self._client) + + @cached_property + def rule_stats(self) -> RuleStatsResource: + return RuleStatsResource(self._client) + + @cached_property + def rule_tags(self) -> RuleTagsResource: + return RuleTagsResource(self._client) + + @cached_property + def with_raw_response(self) -> GovernanceResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return GovernanceResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> GovernanceResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return GovernanceResourceWithStreamingResponse(self) + + +class AsyncGovernanceResource(AsyncAPIResource): + @cached_property + def frameworks(self) -> AsyncFrameworksResource: + return AsyncFrameworksResource(self._client) + + @cached_property + def rules(self) -> AsyncRulesResource: + return AsyncRulesResource(self._client) + + @cached_property + def rule_results(self) -> AsyncRuleResultsResource: + return AsyncRuleResultsResource(self._client) + + @cached_property + def rule_stats(self) -> AsyncRuleStatsResource: + return AsyncRuleStatsResource(self._client) + + @cached_property + def rule_tags(self) -> AsyncRuleTagsResource: + return AsyncRuleTagsResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncGovernanceResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return AsyncGovernanceResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncGovernanceResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return AsyncGovernanceResourceWithStreamingResponse(self) + + +class GovernanceResourceWithRawResponse: + def __init__(self, governance: GovernanceResource) -> None: + self._governance = governance + + @cached_property + def frameworks(self) -> FrameworksResourceWithRawResponse: + return FrameworksResourceWithRawResponse(self._governance.frameworks) + + @cached_property + def rules(self) -> RulesResourceWithRawResponse: + return RulesResourceWithRawResponse(self._governance.rules) + + @cached_property + def rule_results(self) -> RuleResultsResourceWithRawResponse: + return RuleResultsResourceWithRawResponse(self._governance.rule_results) + + @cached_property + def rule_stats(self) -> RuleStatsResourceWithRawResponse: + return RuleStatsResourceWithRawResponse(self._governance.rule_stats) + + @cached_property + def rule_tags(self) -> RuleTagsResourceWithRawResponse: + return RuleTagsResourceWithRawResponse(self._governance.rule_tags) + + +class AsyncGovernanceResourceWithRawResponse: + def __init__(self, governance: AsyncGovernanceResource) -> None: + self._governance = governance + + @cached_property + def frameworks(self) -> AsyncFrameworksResourceWithRawResponse: + return AsyncFrameworksResourceWithRawResponse(self._governance.frameworks) + + @cached_property + def rules(self) -> AsyncRulesResourceWithRawResponse: + return AsyncRulesResourceWithRawResponse(self._governance.rules) + + @cached_property + def rule_results(self) -> AsyncRuleResultsResourceWithRawResponse: + return AsyncRuleResultsResourceWithRawResponse(self._governance.rule_results) + + @cached_property + def rule_stats(self) -> AsyncRuleStatsResourceWithRawResponse: + return AsyncRuleStatsResourceWithRawResponse(self._governance.rule_stats) + + @cached_property + def rule_tags(self) -> AsyncRuleTagsResourceWithRawResponse: + return AsyncRuleTagsResourceWithRawResponse(self._governance.rule_tags) + + +class GovernanceResourceWithStreamingResponse: + def __init__(self, governance: GovernanceResource) -> None: + self._governance = governance + + @cached_property + def frameworks(self) -> FrameworksResourceWithStreamingResponse: + return FrameworksResourceWithStreamingResponse(self._governance.frameworks) + + @cached_property + def rules(self) -> RulesResourceWithStreamingResponse: + return RulesResourceWithStreamingResponse(self._governance.rules) + + @cached_property + def rule_results(self) -> RuleResultsResourceWithStreamingResponse: + return RuleResultsResourceWithStreamingResponse(self._governance.rule_results) + + @cached_property + def rule_stats(self) -> RuleStatsResourceWithStreamingResponse: + return RuleStatsResourceWithStreamingResponse(self._governance.rule_stats) + + @cached_property + def rule_tags(self) -> RuleTagsResourceWithStreamingResponse: + return RuleTagsResourceWithStreamingResponse(self._governance.rule_tags) + + +class AsyncGovernanceResourceWithStreamingResponse: + def __init__(self, governance: AsyncGovernanceResource) -> None: + self._governance = governance + + @cached_property + def frameworks(self) -> AsyncFrameworksResourceWithStreamingResponse: + return AsyncFrameworksResourceWithStreamingResponse(self._governance.frameworks) + + @cached_property + def rules(self) -> AsyncRulesResourceWithStreamingResponse: + return AsyncRulesResourceWithStreamingResponse(self._governance.rules) + + @cached_property + def rule_results(self) -> AsyncRuleResultsResourceWithStreamingResponse: + return AsyncRuleResultsResourceWithStreamingResponse(self._governance.rule_results) + + @cached_property + def rule_stats(self) -> AsyncRuleStatsResourceWithStreamingResponse: + return AsyncRuleStatsResourceWithStreamingResponse(self._governance.rule_stats) + + @cached_property + def rule_tags(self) -> AsyncRuleTagsResourceWithStreamingResponse: + return AsyncRuleTagsResourceWithStreamingResponse(self._governance.rule_tags) diff --git a/src/openlayer/resources/governance/rule_results.py b/src/openlayer/resources/governance/rule_results.py new file mode 100644 index 00000000..563457aa --- /dev/null +++ b/src/openlayer/resources/governance/rule_results.py @@ -0,0 +1,785 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.governance import ( + rule_result_list_params, + rule_result_update_params, + rule_result_list_evidence_params, + rule_result_create_evidence_params, +) +from ...types.governance.rule_result_list_response import RuleResultListResponse +from ...types.governance.rule_result_update_response import RuleResultUpdateResponse +from ...types.governance.rule_result_retrieve_response import RuleResultRetrieveResponse +from ...types.governance.rule_result_list_evidence_response import RuleResultListEvidenceResponse +from ...types.governance.rule_result_create_evidence_response import RuleResultCreateEvidenceResponse + +__all__ = ["RuleResultsResource", "AsyncRuleResultsResource"] + + +class RuleResultsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RuleResultsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return RuleResultsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RuleResultsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return RuleResultsResourceWithStreamingResponse(self) + + def retrieve( + self, + rule_result_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleResultRetrieveResponse: + """ + Retrieve a rule result by its id. + + Alongside the status, the response carries the evaluation and renewal dates that + explain it: `dateLastEvaluated` and `dateOfNextEvaluation` for platform rules, + `dateOfLatestEvidence` and `dateOfRenewal` for evidence rules. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not rule_result_id: + raise ValueError(f"Expected a non-empty value for `rule_result_id` but received {rule_result_id!r}") + return self._get( + path_template("/rule-results/{rule_result_id}", rule_result_id=rule_result_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RuleResultRetrieveResponse, + ) + + def update( + self, + rule_result_id: str, + *, + assignee_id: Optional[str] | Omit = omit, + blocked_by: Iterable[rule_result_update_params.BlockedBy] | Omit = omit, + blocking: Iterable[rule_result_update_params.Blocking] | Omit = omit, + deactivated: bool | Omit = omit, + deactivated_reason: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleResultUpdateResponse: + """Update a rule result. + + Only the fields you send are changed. + + Use this to assign an owner, or to exclude a single result from compliance + without deactivating the rule everywhere. `deactivatedReason` is required when + setting `deactivated` to `true`. + + A result's `status` is computed by Openlayer and cannot be set directly. + + Args: + assignee_id: The user responsible for this result. + + blocked_by: Rule results that must pass before this one can be satisfied. + + blocking: Rule results that this one blocks. + + deactivated: Whether this result is excluded from compliance calculations. + + deactivated_reason: Why the result was excluded. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not rule_result_id: + raise ValueError(f"Expected a non-empty value for `rule_result_id` but received {rule_result_id!r}") + return self._patch( + path_template("/rule-results/{rule_result_id}", rule_result_id=rule_result_id), + body=maybe_transform( + { + "assignee_id": assignee_id, + "blocked_by": blocked_by, + "blocking": blocking, + "deactivated": deactivated, + "deactivated_reason": deactivated_reason, + }, + rule_result_update_params.RuleResultUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RuleResultUpdateResponse, + ) + + def list( + self, + workspace_id: str, + *, + enabled_framework_only: bool | Omit = omit, + framework_id: str | Omit = omit, + include_unframed: bool | Omit = omit, + page: int | Omit = omit, + per_page: int | Omit = omit, + project_id: str | Omit = omit, + rule_id: str | Omit = omit, + scope: Literal["project", "workspace"] | Omit = omit, + search_query: str | Omit = omit, + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] | Omit = omit, + type: Literal["platform", "evidence"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleResultListResponse: + """ + List rule results across a workspace. + + A rule result is the compliance status of one rule for one entity: a project for + project-scoped rules, or the workspace itself for workspace-scoped rules. This + is the endpoint to poll or export when you want your current compliance state, + filtered to a framework, a project, or a status. + + Args: + enabled_framework_only: Only include items belonging to at least one enabled framework. + + framework_id: Only include items belonging to this framework. + + include_unframed: Whether to include rules that are not part of any framework. + + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + project_id: Only include items that apply to this project. + + rule_id: Only include results of this rule. + + scope: Only include rules with this scope. + + search_query: Filter by a free-text search over names and descriptions. + + status: Only include items whose rule result has this compliance status. + + type: Only include rules of this type. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not workspace_id: + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._get( + path_template("/workspaces/{workspace_id}/rule-results", workspace_id=workspace_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "enabled_framework_only": enabled_framework_only, + "framework_id": framework_id, + "include_unframed": include_unframed, + "page": page, + "per_page": per_page, + "project_id": project_id, + "rule_id": rule_id, + "scope": scope, + "search_query": search_query, + "status": status, + "type": type, + }, + rule_result_list_params.RuleResultListParams, + ), + ), + cast_to=RuleResultListResponse, + ) + + def create_evidence( + self, + rule_result_id: str, + *, + description: Optional[str] | Omit = omit, + name: Optional[str] | Omit = omit, + storage_uri: Optional[str] | Omit = omit, + text: Optional[str] | Omit = omit, + url: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleResultCreateEvidenceResponse: + """ + Attach evidence to a rule result, satisfying an evidence rule. + + Send the field that matches the rule's `evidenceType`: `storageUri` for an + uploaded document, `text` for a written statement, or `url` for a link. + + For a document, upload the file first with `POST /storage/presigned-url` and + send the resulting storage URI as `storageUri`. + + Attaching evidence re-evaluates the rule result. If the rule sets + `renewalCadenceDays`, the renewal window restarts from this evidence. + + Args: + description: A description of what the evidence shows. + + name: The evidence name. + + storage_uri: Where the uploaded file is stored. Set when the rule's `evidenceType` is + `document`. + + text: The evidence text. Set when the rule's `evidenceType` is `text`. + + url: A link to the evidence. Set when the rule's `evidenceType` is `url`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not rule_result_id: + raise ValueError(f"Expected a non-empty value for `rule_result_id` but received {rule_result_id!r}") + return self._post( + path_template("/rule-results/{rule_result_id}/evidence", rule_result_id=rule_result_id), + body=maybe_transform( + { + "description": description, + "name": name, + "storage_uri": storage_uri, + "text": text, + "url": url, + }, + rule_result_create_evidence_params.RuleResultCreateEvidenceParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RuleResultCreateEvidenceResponse, + ) + + def list_evidence( + self, + rule_result_id: str, + *, + page: int | Omit = omit, + per_page: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleResultListEvidenceResponse: + """ + List the evidence attached to a rule result. + + Which field carries the evidence depends on the rule's `evidenceType`: + `storageUri` for uploaded documents, `text` for written statements, and `url` + for links. + + Args: + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not rule_result_id: + raise ValueError(f"Expected a non-empty value for `rule_result_id` but received {rule_result_id!r}") + return self._get( + path_template("/rule-results/{rule_result_id}/evidence", rule_result_id=rule_result_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "page": page, + "per_page": per_page, + }, + rule_result_list_evidence_params.RuleResultListEvidenceParams, + ), + ), + cast_to=RuleResultListEvidenceResponse, + ) + + +class AsyncRuleResultsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRuleResultsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return AsyncRuleResultsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRuleResultsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return AsyncRuleResultsResourceWithStreamingResponse(self) + + async def retrieve( + self, + rule_result_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleResultRetrieveResponse: + """ + Retrieve a rule result by its id. + + Alongside the status, the response carries the evaluation and renewal dates that + explain it: `dateLastEvaluated` and `dateOfNextEvaluation` for platform rules, + `dateOfLatestEvidence` and `dateOfRenewal` for evidence rules. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not rule_result_id: + raise ValueError(f"Expected a non-empty value for `rule_result_id` but received {rule_result_id!r}") + return await self._get( + path_template("/rule-results/{rule_result_id}", rule_result_id=rule_result_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RuleResultRetrieveResponse, + ) + + async def update( + self, + rule_result_id: str, + *, + assignee_id: Optional[str] | Omit = omit, + blocked_by: Iterable[rule_result_update_params.BlockedBy] | Omit = omit, + blocking: Iterable[rule_result_update_params.Blocking] | Omit = omit, + deactivated: bool | Omit = omit, + deactivated_reason: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleResultUpdateResponse: + """Update a rule result. + + Only the fields you send are changed. + + Use this to assign an owner, or to exclude a single result from compliance + without deactivating the rule everywhere. `deactivatedReason` is required when + setting `deactivated` to `true`. + + A result's `status` is computed by Openlayer and cannot be set directly. + + Args: + assignee_id: The user responsible for this result. + + blocked_by: Rule results that must pass before this one can be satisfied. + + blocking: Rule results that this one blocks. + + deactivated: Whether this result is excluded from compliance calculations. + + deactivated_reason: Why the result was excluded. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not rule_result_id: + raise ValueError(f"Expected a non-empty value for `rule_result_id` but received {rule_result_id!r}") + return await self._patch( + path_template("/rule-results/{rule_result_id}", rule_result_id=rule_result_id), + body=await async_maybe_transform( + { + "assignee_id": assignee_id, + "blocked_by": blocked_by, + "blocking": blocking, + "deactivated": deactivated, + "deactivated_reason": deactivated_reason, + }, + rule_result_update_params.RuleResultUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RuleResultUpdateResponse, + ) + + async def list( + self, + workspace_id: str, + *, + enabled_framework_only: bool | Omit = omit, + framework_id: str | Omit = omit, + include_unframed: bool | Omit = omit, + page: int | Omit = omit, + per_page: int | Omit = omit, + project_id: str | Omit = omit, + rule_id: str | Omit = omit, + scope: Literal["project", "workspace"] | Omit = omit, + search_query: str | Omit = omit, + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] | Omit = omit, + type: Literal["platform", "evidence"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleResultListResponse: + """ + List rule results across a workspace. + + A rule result is the compliance status of one rule for one entity: a project for + project-scoped rules, or the workspace itself for workspace-scoped rules. This + is the endpoint to poll or export when you want your current compliance state, + filtered to a framework, a project, or a status. + + Args: + enabled_framework_only: Only include items belonging to at least one enabled framework. + + framework_id: Only include items belonging to this framework. + + include_unframed: Whether to include rules that are not part of any framework. + + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + project_id: Only include items that apply to this project. + + rule_id: Only include results of this rule. + + scope: Only include rules with this scope. + + search_query: Filter by a free-text search over names and descriptions. + + status: Only include items whose rule result has this compliance status. + + type: Only include rules of this type. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not workspace_id: + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._get( + path_template("/workspaces/{workspace_id}/rule-results", workspace_id=workspace_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "enabled_framework_only": enabled_framework_only, + "framework_id": framework_id, + "include_unframed": include_unframed, + "page": page, + "per_page": per_page, + "project_id": project_id, + "rule_id": rule_id, + "scope": scope, + "search_query": search_query, + "status": status, + "type": type, + }, + rule_result_list_params.RuleResultListParams, + ), + ), + cast_to=RuleResultListResponse, + ) + + async def create_evidence( + self, + rule_result_id: str, + *, + description: Optional[str] | Omit = omit, + name: Optional[str] | Omit = omit, + storage_uri: Optional[str] | Omit = omit, + text: Optional[str] | Omit = omit, + url: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleResultCreateEvidenceResponse: + """ + Attach evidence to a rule result, satisfying an evidence rule. + + Send the field that matches the rule's `evidenceType`: `storageUri` for an + uploaded document, `text` for a written statement, or `url` for a link. + + For a document, upload the file first with `POST /storage/presigned-url` and + send the resulting storage URI as `storageUri`. + + Attaching evidence re-evaluates the rule result. If the rule sets + `renewalCadenceDays`, the renewal window restarts from this evidence. + + Args: + description: A description of what the evidence shows. + + name: The evidence name. + + storage_uri: Where the uploaded file is stored. Set when the rule's `evidenceType` is + `document`. + + text: The evidence text. Set when the rule's `evidenceType` is `text`. + + url: A link to the evidence. Set when the rule's `evidenceType` is `url`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not rule_result_id: + raise ValueError(f"Expected a non-empty value for `rule_result_id` but received {rule_result_id!r}") + return await self._post( + path_template("/rule-results/{rule_result_id}/evidence", rule_result_id=rule_result_id), + body=await async_maybe_transform( + { + "description": description, + "name": name, + "storage_uri": storage_uri, + "text": text, + "url": url, + }, + rule_result_create_evidence_params.RuleResultCreateEvidenceParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RuleResultCreateEvidenceResponse, + ) + + async def list_evidence( + self, + rule_result_id: str, + *, + page: int | Omit = omit, + per_page: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleResultListEvidenceResponse: + """ + List the evidence attached to a rule result. + + Which field carries the evidence depends on the rule's `evidenceType`: + `storageUri` for uploaded documents, `text` for written statements, and `url` + for links. + + Args: + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not rule_result_id: + raise ValueError(f"Expected a non-empty value for `rule_result_id` but received {rule_result_id!r}") + return await self._get( + path_template("/rule-results/{rule_result_id}/evidence", rule_result_id=rule_result_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "page": page, + "per_page": per_page, + }, + rule_result_list_evidence_params.RuleResultListEvidenceParams, + ), + ), + cast_to=RuleResultListEvidenceResponse, + ) + + +class RuleResultsResourceWithRawResponse: + def __init__(self, rule_results: RuleResultsResource) -> None: + self._rule_results = rule_results + + self.retrieve = to_raw_response_wrapper( + rule_results.retrieve, + ) + self.update = to_raw_response_wrapper( + rule_results.update, + ) + self.list = to_raw_response_wrapper( + rule_results.list, + ) + self.create_evidence = to_raw_response_wrapper( + rule_results.create_evidence, + ) + self.list_evidence = to_raw_response_wrapper( + rule_results.list_evidence, + ) + + +class AsyncRuleResultsResourceWithRawResponse: + def __init__(self, rule_results: AsyncRuleResultsResource) -> None: + self._rule_results = rule_results + + self.retrieve = async_to_raw_response_wrapper( + rule_results.retrieve, + ) + self.update = async_to_raw_response_wrapper( + rule_results.update, + ) + self.list = async_to_raw_response_wrapper( + rule_results.list, + ) + self.create_evidence = async_to_raw_response_wrapper( + rule_results.create_evidence, + ) + self.list_evidence = async_to_raw_response_wrapper( + rule_results.list_evidence, + ) + + +class RuleResultsResourceWithStreamingResponse: + def __init__(self, rule_results: RuleResultsResource) -> None: + self._rule_results = rule_results + + self.retrieve = to_streamed_response_wrapper( + rule_results.retrieve, + ) + self.update = to_streamed_response_wrapper( + rule_results.update, + ) + self.list = to_streamed_response_wrapper( + rule_results.list, + ) + self.create_evidence = to_streamed_response_wrapper( + rule_results.create_evidence, + ) + self.list_evidence = to_streamed_response_wrapper( + rule_results.list_evidence, + ) + + +class AsyncRuleResultsResourceWithStreamingResponse: + def __init__(self, rule_results: AsyncRuleResultsResource) -> None: + self._rule_results = rule_results + + self.retrieve = async_to_streamed_response_wrapper( + rule_results.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + rule_results.update, + ) + self.list = async_to_streamed_response_wrapper( + rule_results.list, + ) + self.create_evidence = async_to_streamed_response_wrapper( + rule_results.create_evidence, + ) + self.list_evidence = async_to_streamed_response_wrapper( + rule_results.list_evidence, + ) diff --git a/src/openlayer/resources/governance/rule_stats.py b/src/openlayer/resources/governance/rule_stats.py new file mode 100644 index 00000000..52edc7d5 --- /dev/null +++ b/src/openlayer/resources/governance/rule_stats.py @@ -0,0 +1,205 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.governance import rule_stat_retrieve_params +from ...types.governance.rule_stat_retrieve_response import RuleStatRetrieveResponse + +__all__ = ["RuleStatsResource", "AsyncRuleStatsResource"] + + +class RuleStatsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RuleStatsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return RuleStatsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RuleStatsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return RuleStatsResourceWithStreamingResponse(self) + + def retrieve( + self, + workspace_id: str, + *, + framework_id: str | Omit = omit, + project_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleStatRetrieveResponse: + """ + Get a compliance roll-up for a workspace: how many rules exist, and how many of + their results are passing, failing, pending, or due for renewal. + + Counts respect the filters you pass, so `frameworkId` gives you a single + framework's overall compliance and `projectId` gives you a single project's. + + Args: + framework_id: Only include items belonging to this framework. + + project_id: Only include items that apply to this project. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not workspace_id: + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._get( + path_template("/workspaces/{workspace_id}/rule-stats", workspace_id=workspace_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "framework_id": framework_id, + "project_id": project_id, + }, + rule_stat_retrieve_params.RuleStatRetrieveParams, + ), + ), + cast_to=RuleStatRetrieveResponse, + ) + + +class AsyncRuleStatsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRuleStatsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return AsyncRuleStatsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRuleStatsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return AsyncRuleStatsResourceWithStreamingResponse(self) + + async def retrieve( + self, + workspace_id: str, + *, + framework_id: str | Omit = omit, + project_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleStatRetrieveResponse: + """ + Get a compliance roll-up for a workspace: how many rules exist, and how many of + their results are passing, failing, pending, or due for renewal. + + Counts respect the filters you pass, so `frameworkId` gives you a single + framework's overall compliance and `projectId` gives you a single project's. + + Args: + framework_id: Only include items belonging to this framework. + + project_id: Only include items that apply to this project. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not workspace_id: + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._get( + path_template("/workspaces/{workspace_id}/rule-stats", workspace_id=workspace_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "framework_id": framework_id, + "project_id": project_id, + }, + rule_stat_retrieve_params.RuleStatRetrieveParams, + ), + ), + cast_to=RuleStatRetrieveResponse, + ) + + +class RuleStatsResourceWithRawResponse: + def __init__(self, rule_stats: RuleStatsResource) -> None: + self._rule_stats = rule_stats + + self.retrieve = to_raw_response_wrapper( + rule_stats.retrieve, + ) + + +class AsyncRuleStatsResourceWithRawResponse: + def __init__(self, rule_stats: AsyncRuleStatsResource) -> None: + self._rule_stats = rule_stats + + self.retrieve = async_to_raw_response_wrapper( + rule_stats.retrieve, + ) + + +class RuleStatsResourceWithStreamingResponse: + def __init__(self, rule_stats: RuleStatsResource) -> None: + self._rule_stats = rule_stats + + self.retrieve = to_streamed_response_wrapper( + rule_stats.retrieve, + ) + + +class AsyncRuleStatsResourceWithStreamingResponse: + def __init__(self, rule_stats: AsyncRuleStatsResource) -> None: + self._rule_stats = rule_stats + + self.retrieve = async_to_streamed_response_wrapper( + rule_stats.retrieve, + ) diff --git a/src/openlayer/resources/governance/rule_tags.py b/src/openlayer/resources/governance/rule_tags.py new file mode 100644 index 00000000..aea39688 --- /dev/null +++ b/src/openlayer/resources/governance/rule_tags.py @@ -0,0 +1,205 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.governance import rule_tag_list_params +from ...types.governance.rule_tag_list_response import RuleTagListResponse + +__all__ = ["RuleTagsResource", "AsyncRuleTagsResource"] + + +class RuleTagsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RuleTagsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return RuleTagsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RuleTagsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return RuleTagsResourceWithStreamingResponse(self) + + def list( + self, + workspace_id: str, + *, + page: int | Omit = omit, + per_page: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleTagListResponse: + """ + List the rule tags in a workspace. + + Tags group rules across frameworks, for example by team or by control family. + Use the ids returned here with the `tags` filter on + [List rules](/api-reference/rest/governance/list-rules). + + Args: + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not workspace_id: + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._get( + path_template("/workspaces/{workspace_id}/rule-tags", workspace_id=workspace_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "page": page, + "per_page": per_page, + }, + rule_tag_list_params.RuleTagListParams, + ), + ), + cast_to=RuleTagListResponse, + ) + + +class AsyncRuleTagsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRuleTagsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return AsyncRuleTagsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRuleTagsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return AsyncRuleTagsResourceWithStreamingResponse(self) + + async def list( + self, + workspace_id: str, + *, + page: int | Omit = omit, + per_page: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleTagListResponse: + """ + List the rule tags in a workspace. + + Tags group rules across frameworks, for example by team or by control family. + Use the ids returned here with the `tags` filter on + [List rules](/api-reference/rest/governance/list-rules). + + Args: + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not workspace_id: + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._get( + path_template("/workspaces/{workspace_id}/rule-tags", workspace_id=workspace_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "page": page, + "per_page": per_page, + }, + rule_tag_list_params.RuleTagListParams, + ), + ), + cast_to=RuleTagListResponse, + ) + + +class RuleTagsResourceWithRawResponse: + def __init__(self, rule_tags: RuleTagsResource) -> None: + self._rule_tags = rule_tags + + self.list = to_raw_response_wrapper( + rule_tags.list, + ) + + +class AsyncRuleTagsResourceWithRawResponse: + def __init__(self, rule_tags: AsyncRuleTagsResource) -> None: + self._rule_tags = rule_tags + + self.list = async_to_raw_response_wrapper( + rule_tags.list, + ) + + +class RuleTagsResourceWithStreamingResponse: + def __init__(self, rule_tags: RuleTagsResource) -> None: + self._rule_tags = rule_tags + + self.list = to_streamed_response_wrapper( + rule_tags.list, + ) + + +class AsyncRuleTagsResourceWithStreamingResponse: + def __init__(self, rule_tags: AsyncRuleTagsResource) -> None: + self._rule_tags = rule_tags + + self.list = async_to_streamed_response_wrapper( + rule_tags.list, + ) diff --git a/src/openlayer/resources/governance/rules.py b/src/openlayer/resources/governance/rules.py new file mode 100644 index 00000000..04251098 --- /dev/null +++ b/src/openlayer/resources/governance/rules.py @@ -0,0 +1,885 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Optional +from typing_extensions import Literal + +import httpx + +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.governance import rule_list_params, rule_create_params, rule_update_params +from ...types.governance.rule_list_response import RuleListResponse +from ...types.governance.rule_create_response import RuleCreateResponse +from ...types.governance.rule_update_response import RuleUpdateResponse +from ...types.governance.rule_retrieve_response import RuleRetrieveResponse + +__all__ = ["RulesResource", "AsyncRulesResource"] + + +class RulesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RulesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return RulesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RulesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return RulesResourceWithStreamingResponse(self) + + def create( + self, + workspace_id: str, + *, + name: str, + scope: Literal["project", "workspace"], + type: Literal["platform", "evidence"], + assignee_id: Optional[str] | Omit = omit, + automation_params: Optional[Dict[str, object]] | Omit = omit, + automation_type: Optional[str] | Omit = omit, + deactivated: bool | Omit = omit, + description: Optional[str] | Omit = omit, + evidence_type: Optional[Literal["document", "text", "url", "categoryValue"]] | Omit = omit, + renewal_cadence_days: Optional[int] | Omit = omit, + tag_ids: Optional[SequenceNotStr[str]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleCreateResponse: + """Create a governance rule in a workspace. + + A rule is one requirement. + + Its `type` decides how it is satisfied, and the two + types accept different fields: + + - `platform` rules are evaluated automatically from the state of your workspace. + Set `automationType` to the signal to check. Their `scope` must be `project`, + and `evidenceType` and `renewalCadenceDays` must be omitted or `null`. + - `evidence` rules are satisfied by attaching evidence. Set `evidenceType` to + the kind of evidence that satisfies them. `automationType` and + `automationParams` must be omitted or `null`. + + A new rule belongs to no framework. Map it to one from the Openlayer app. + + Args: + name: The rule name. + + scope: Whether the rule is evaluated once for the whole workspace, or once per project + the rule's frameworks apply to. + + type: `platform` rules are evaluated automatically from the state of your Openlayer + workspace. `evidence` rules are satisfied by attaching evidence. + + assignee_id: The user responsible for satisfying the rule. + + automation_params: Configuration for the platform check, when the automation takes parameters. + + automation_type: Which workspace signal a platform rule checks, for example + `monitoring_mode_enabled`, `test_setup`, or `project_owner_set`. `null` for + evidence rules. + + deactivated: Whether the rule is excluded from compliance calculations. + + description: What the rule requires. + + evidence_type: The kind of evidence that satisfies the rule. `null` for platform rules. + + renewal_cadence_days: How often evidence must be renewed, in days. Once evidence is older than this, + the rule result becomes `due_soon` and then `failing`. + + tag_ids: The ids of the rule tags to associate with the rule. Replaces the rule's tags. + Read them back from `tags`, and list the tags available in the workspace with + `GET /workspaces/{workspaceId}/rule-tags`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not workspace_id: + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._post( + path_template("/workspaces/{workspace_id}/rules", workspace_id=workspace_id), + body=maybe_transform( + { + "name": name, + "scope": scope, + "type": type, + "assignee_id": assignee_id, + "automation_params": automation_params, + "automation_type": automation_type, + "deactivated": deactivated, + "description": description, + "evidence_type": evidence_type, + "renewal_cadence_days": renewal_cadence_days, + "tag_ids": tag_ids, + }, + rule_create_params.RuleCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RuleCreateResponse, + ) + + def retrieve( + self, + rule_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleRetrieveResponse: + """ + Retrieve a governance rule by its id, including the frameworks it belongs to and + its tags. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not rule_id: + raise ValueError(f"Expected a non-empty value for `rule_id` but received {rule_id!r}") + return self._get( + path_template("/rules/{rule_id}", rule_id=rule_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RuleRetrieveResponse, + ) + + def update( + self, + rule_id: str, + *, + assignee_id: Optional[str] | Omit = omit, + deactivated: bool | Omit = omit, + description: Optional[str] | Omit = omit, + name: str | Omit = omit, + renewal_cadence_days: Optional[int] | Omit = omit, + tag_ids: Optional[SequenceNotStr[str]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleUpdateResponse: + """Update a governance rule. + + Only the fields you send are changed. + + Rules that ship with Openlayer report `immutable: true` and cannot be edited. + + A rule's `scope`, `type`, `evidenceType`, and automation are fixed once it + exists -- create a new rule instead of converting one. + + Args: + assignee_id: The user responsible for satisfying the rule. + + deactivated: Whether the rule is excluded from compliance calculations. + + description: What the rule requires. + + name: The rule name. + + renewal_cadence_days: How often evidence must be renewed, in days. Once evidence is older than this, + the rule result becomes `due_soon` and then `failing`. + + tag_ids: The ids of the rule tags to associate with the rule. Replaces the rule's tags. + Read them back from `tags`, and list the tags available in the workspace with + `GET /workspaces/{workspaceId}/rule-tags`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not rule_id: + raise ValueError(f"Expected a non-empty value for `rule_id` but received {rule_id!r}") + return self._put( + path_template("/rules/{rule_id}", rule_id=rule_id), + body=maybe_transform( + { + "assignee_id": assignee_id, + "deactivated": deactivated, + "description": description, + "name": name, + "renewal_cadence_days": renewal_cadence_days, + "tag_ids": tag_ids, + }, + rule_update_params.RuleUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RuleUpdateResponse, + ) + + def list( + self, + workspace_id: str, + *, + asc: bool | Omit = omit, + assignee_id: str | Omit = omit, + deactivated: bool | Omit = omit, + enabled_framework_only: bool | Omit = omit, + framework_id: str | Omit = omit, + group: Literal["open", "excluded", "done"] | Omit = omit, + include_results: bool | Omit = omit, + include_unframed: bool | Omit = omit, + page: int | Omit = omit, + per_page: int | Omit = omit, + project_id: str | Omit = omit, + scope: Literal["project", "workspace"] | Omit = omit, + search_query: str | Omit = omit, + sort_by: Literal["name", "status", "frameworks", "scope", "dateCreated"] | Omit = omit, + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] | Omit = omit, + tags: SequenceNotStr[str] | Omit = omit, + type: Literal["platform", "evidence"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleListResponse: + """ + List the governance rules in a workspace. + + A rule is a single requirement Openlayer tracks. `platform` rules are evaluated + automatically from the state of your workspace; `evidence` rules are satisfied + by attaching evidence. A rule can belong to several frameworks at once, and + rules that belong to none are returned too unless you pass + `includeUnframed=false`. + + Pass `includeResults=true` to get each rule's compliance results inline instead + of fetching them separately. + + Args: + asc: Whether to sort in ascending order. + + assignee_id: Only include rules assigned to this user. + + deactivated: Only include rules that are deactivated (or active). + + enabled_framework_only: Only include items belonging to at least one enabled framework. + + framework_id: Only include items belonging to this framework. + + group: Only include rules in one bucket of the compliance workflow. `open` covers rules + that still need attention, `done` covers rules that are fully satisfied, and + `excluded` covers rules that have been deactivated. + + include_results: Whether to include each rule's results inline, in a `results` array. + + include_unframed: Whether to include rules that are not part of any framework. + + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + project_id: Only include items that apply to this project. + + scope: Only include rules with this scope. + + search_query: Filter by a free-text search over names and descriptions. + + sort_by: The field to sort on. + + status: Only include items whose rule result has this compliance status. + + tags: Only include rules carrying all of these rule tags. Pass tag ids, which you can + look up with [List rule tags](/api-reference/rest/governance/list-rule-tags). + + type: Only include rules of this type. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not workspace_id: + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._get( + path_template("/workspaces/{workspace_id}/rules", workspace_id=workspace_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "asc": asc, + "assignee_id": assignee_id, + "deactivated": deactivated, + "enabled_framework_only": enabled_framework_only, + "framework_id": framework_id, + "group": group, + "include_results": include_results, + "include_unframed": include_unframed, + "page": page, + "per_page": per_page, + "project_id": project_id, + "scope": scope, + "search_query": search_query, + "sort_by": sort_by, + "status": status, + "tags": tags, + "type": type, + }, + rule_list_params.RuleListParams, + ), + ), + cast_to=RuleListResponse, + ) + + def delete( + self, + rule_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete a governance rule and its rule results. + + Only rules you created can be deleted. Rules that ship with Openlayer report + `immutable: true` and cannot be deleted -- exclude one from compliance by + setting `deactivated` with `PUT /rules/{ruleId}` instead. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not rule_id: + raise ValueError(f"Expected a non-empty value for `rule_id` but received {rule_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + path_template("/rules/{rule_id}", rule_id=rule_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class AsyncRulesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRulesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#accessing-raw-response-data-eg-headers + """ + return AsyncRulesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRulesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openlayer-ai/openlayer-python#with_streaming_response + """ + return AsyncRulesResourceWithStreamingResponse(self) + + async def create( + self, + workspace_id: str, + *, + name: str, + scope: Literal["project", "workspace"], + type: Literal["platform", "evidence"], + assignee_id: Optional[str] | Omit = omit, + automation_params: Optional[Dict[str, object]] | Omit = omit, + automation_type: Optional[str] | Omit = omit, + deactivated: bool | Omit = omit, + description: Optional[str] | Omit = omit, + evidence_type: Optional[Literal["document", "text", "url", "categoryValue"]] | Omit = omit, + renewal_cadence_days: Optional[int] | Omit = omit, + tag_ids: Optional[SequenceNotStr[str]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleCreateResponse: + """Create a governance rule in a workspace. + + A rule is one requirement. + + Its `type` decides how it is satisfied, and the two + types accept different fields: + + - `platform` rules are evaluated automatically from the state of your workspace. + Set `automationType` to the signal to check. Their `scope` must be `project`, + and `evidenceType` and `renewalCadenceDays` must be omitted or `null`. + - `evidence` rules are satisfied by attaching evidence. Set `evidenceType` to + the kind of evidence that satisfies them. `automationType` and + `automationParams` must be omitted or `null`. + + A new rule belongs to no framework. Map it to one from the Openlayer app. + + Args: + name: The rule name. + + scope: Whether the rule is evaluated once for the whole workspace, or once per project + the rule's frameworks apply to. + + type: `platform` rules are evaluated automatically from the state of your Openlayer + workspace. `evidence` rules are satisfied by attaching evidence. + + assignee_id: The user responsible for satisfying the rule. + + automation_params: Configuration for the platform check, when the automation takes parameters. + + automation_type: Which workspace signal a platform rule checks, for example + `monitoring_mode_enabled`, `test_setup`, or `project_owner_set`. `null` for + evidence rules. + + deactivated: Whether the rule is excluded from compliance calculations. + + description: What the rule requires. + + evidence_type: The kind of evidence that satisfies the rule. `null` for platform rules. + + renewal_cadence_days: How often evidence must be renewed, in days. Once evidence is older than this, + the rule result becomes `due_soon` and then `failing`. + + tag_ids: The ids of the rule tags to associate with the rule. Replaces the rule's tags. + Read them back from `tags`, and list the tags available in the workspace with + `GET /workspaces/{workspaceId}/rule-tags`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not workspace_id: + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._post( + path_template("/workspaces/{workspace_id}/rules", workspace_id=workspace_id), + body=await async_maybe_transform( + { + "name": name, + "scope": scope, + "type": type, + "assignee_id": assignee_id, + "automation_params": automation_params, + "automation_type": automation_type, + "deactivated": deactivated, + "description": description, + "evidence_type": evidence_type, + "renewal_cadence_days": renewal_cadence_days, + "tag_ids": tag_ids, + }, + rule_create_params.RuleCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RuleCreateResponse, + ) + + async def retrieve( + self, + rule_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleRetrieveResponse: + """ + Retrieve a governance rule by its id, including the frameworks it belongs to and + its tags. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not rule_id: + raise ValueError(f"Expected a non-empty value for `rule_id` but received {rule_id!r}") + return await self._get( + path_template("/rules/{rule_id}", rule_id=rule_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RuleRetrieveResponse, + ) + + async def update( + self, + rule_id: str, + *, + assignee_id: Optional[str] | Omit = omit, + deactivated: bool | Omit = omit, + description: Optional[str] | Omit = omit, + name: str | Omit = omit, + renewal_cadence_days: Optional[int] | Omit = omit, + tag_ids: Optional[SequenceNotStr[str]] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleUpdateResponse: + """Update a governance rule. + + Only the fields you send are changed. + + Rules that ship with Openlayer report `immutable: true` and cannot be edited. + + A rule's `scope`, `type`, `evidenceType`, and automation are fixed once it + exists -- create a new rule instead of converting one. + + Args: + assignee_id: The user responsible for satisfying the rule. + + deactivated: Whether the rule is excluded from compliance calculations. + + description: What the rule requires. + + name: The rule name. + + renewal_cadence_days: How often evidence must be renewed, in days. Once evidence is older than this, + the rule result becomes `due_soon` and then `failing`. + + tag_ids: The ids of the rule tags to associate with the rule. Replaces the rule's tags. + Read them back from `tags`, and list the tags available in the workspace with + `GET /workspaces/{workspaceId}/rule-tags`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not rule_id: + raise ValueError(f"Expected a non-empty value for `rule_id` but received {rule_id!r}") + return await self._put( + path_template("/rules/{rule_id}", rule_id=rule_id), + body=await async_maybe_transform( + { + "assignee_id": assignee_id, + "deactivated": deactivated, + "description": description, + "name": name, + "renewal_cadence_days": renewal_cadence_days, + "tag_ids": tag_ids, + }, + rule_update_params.RuleUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RuleUpdateResponse, + ) + + async def list( + self, + workspace_id: str, + *, + asc: bool | Omit = omit, + assignee_id: str | Omit = omit, + deactivated: bool | Omit = omit, + enabled_framework_only: bool | Omit = omit, + framework_id: str | Omit = omit, + group: Literal["open", "excluded", "done"] | Omit = omit, + include_results: bool | Omit = omit, + include_unframed: bool | Omit = omit, + page: int | Omit = omit, + per_page: int | Omit = omit, + project_id: str | Omit = omit, + scope: Literal["project", "workspace"] | Omit = omit, + search_query: str | Omit = omit, + sort_by: Literal["name", "status", "frameworks", "scope", "dateCreated"] | Omit = omit, + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] | Omit = omit, + tags: SequenceNotStr[str] | Omit = omit, + type: Literal["platform", "evidence"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleListResponse: + """ + List the governance rules in a workspace. + + A rule is a single requirement Openlayer tracks. `platform` rules are evaluated + automatically from the state of your workspace; `evidence` rules are satisfied + by attaching evidence. A rule can belong to several frameworks at once, and + rules that belong to none are returned too unless you pass + `includeUnframed=false`. + + Pass `includeResults=true` to get each rule's compliance results inline instead + of fetching them separately. + + Args: + asc: Whether to sort in ascending order. + + assignee_id: Only include rules assigned to this user. + + deactivated: Only include rules that are deactivated (or active). + + enabled_framework_only: Only include items belonging to at least one enabled framework. + + framework_id: Only include items belonging to this framework. + + group: Only include rules in one bucket of the compliance workflow. `open` covers rules + that still need attention, `done` covers rules that are fully satisfied, and + `excluded` covers rules that have been deactivated. + + include_results: Whether to include each rule's results inline, in a `results` array. + + include_unframed: Whether to include rules that are not part of any framework. + + page: The page to return in a paginated query. + + per_page: Maximum number of items to return per page. + + project_id: Only include items that apply to this project. + + scope: Only include rules with this scope. + + search_query: Filter by a free-text search over names and descriptions. + + sort_by: The field to sort on. + + status: Only include items whose rule result has this compliance status. + + tags: Only include rules carrying all of these rule tags. Pass tag ids, which you can + look up with [List rule tags](/api-reference/rest/governance/list-rule-tags). + + type: Only include rules of this type. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not workspace_id: + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._get( + path_template("/workspaces/{workspace_id}/rules", workspace_id=workspace_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "asc": asc, + "assignee_id": assignee_id, + "deactivated": deactivated, + "enabled_framework_only": enabled_framework_only, + "framework_id": framework_id, + "group": group, + "include_results": include_results, + "include_unframed": include_unframed, + "page": page, + "per_page": per_page, + "project_id": project_id, + "scope": scope, + "search_query": search_query, + "sort_by": sort_by, + "status": status, + "tags": tags, + "type": type, + }, + rule_list_params.RuleListParams, + ), + ), + cast_to=RuleListResponse, + ) + + async def delete( + self, + rule_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete a governance rule and its rule results. + + Only rules you created can be deleted. Rules that ship with Openlayer report + `immutable: true` and cannot be deleted -- exclude one from compliance by + setting `deactivated` with `PUT /rules/{ruleId}` instead. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not rule_id: + raise ValueError(f"Expected a non-empty value for `rule_id` but received {rule_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + path_template("/rules/{rule_id}", rule_id=rule_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class RulesResourceWithRawResponse: + def __init__(self, rules: RulesResource) -> None: + self._rules = rules + + self.create = to_raw_response_wrapper( + rules.create, + ) + self.retrieve = to_raw_response_wrapper( + rules.retrieve, + ) + self.update = to_raw_response_wrapper( + rules.update, + ) + self.list = to_raw_response_wrapper( + rules.list, + ) + self.delete = to_raw_response_wrapper( + rules.delete, + ) + + +class AsyncRulesResourceWithRawResponse: + def __init__(self, rules: AsyncRulesResource) -> None: + self._rules = rules + + self.create = async_to_raw_response_wrapper( + rules.create, + ) + self.retrieve = async_to_raw_response_wrapper( + rules.retrieve, + ) + self.update = async_to_raw_response_wrapper( + rules.update, + ) + self.list = async_to_raw_response_wrapper( + rules.list, + ) + self.delete = async_to_raw_response_wrapper( + rules.delete, + ) + + +class RulesResourceWithStreamingResponse: + def __init__(self, rules: RulesResource) -> None: + self._rules = rules + + self.create = to_streamed_response_wrapper( + rules.create, + ) + self.retrieve = to_streamed_response_wrapper( + rules.retrieve, + ) + self.update = to_streamed_response_wrapper( + rules.update, + ) + self.list = to_streamed_response_wrapper( + rules.list, + ) + self.delete = to_streamed_response_wrapper( + rules.delete, + ) + + +class AsyncRulesResourceWithStreamingResponse: + def __init__(self, rules: AsyncRulesResource) -> None: + self._rules = rules + + self.create = async_to_streamed_response_wrapper( + rules.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + rules.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + rules.update, + ) + self.list = async_to_streamed_response_wrapper( + rules.list, + ) + self.delete = async_to_streamed_response_wrapper( + rules.delete, + ) diff --git a/src/openlayer/resources/storage/presigned_url.py b/src/openlayer/resources/storage/presigned_url.py index 96886fe8..42a7a1de 100644 --- a/src/openlayer/resources/storage/presigned_url.py +++ b/src/openlayer/resources/storage/presigned_url.py @@ -15,8 +15,9 @@ async_to_streamed_response_wrapper, ) from ..._base_client import make_request_options -from ...types.storage import presigned_url_create_params +from ...types.storage import presigned_url_create_params, presigned_url_retrieve_params from ...types.storage.presigned_url_create_response import PresignedURLCreateResponse +from ...types.storage.presigned_url_retrieve_response import PresignedURLRetrieveResponse __all__ = ["PresignedURLResource", "AsyncPresignedURLResource"] @@ -80,6 +81,54 @@ def create( cast_to=PresignedURLCreateResponse, ) + def retrieve( + self, + *, + storage_uri: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PresignedURLRetrieveResponse: + """ + Exchange a `storageUri` for a short-lived presigned url you can download the + object from. + + Use it to collect anything the platform stored on your behalf -- for example the + archive a framework export leaves behind, whose `storageUri` comes back in the + background task's `outputs`. + + The workspace is taken from the API key, so there is nothing else to send. The + url is only issued for objects your workspace owns, and `404` covers both "no + such object" and "not yours". + + Args: + storage_uri: The object's storage uri. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/storage/presigned-url", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"storage_uri": storage_uri}, presigned_url_retrieve_params.PresignedURLRetrieveParams + ), + ), + cast_to=PresignedURLRetrieveResponse, + ) + class AsyncPresignedURLResource(AsyncAPIResource): @cached_property @@ -140,6 +189,54 @@ async def create( cast_to=PresignedURLCreateResponse, ) + async def retrieve( + self, + *, + storage_uri: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PresignedURLRetrieveResponse: + """ + Exchange a `storageUri` for a short-lived presigned url you can download the + object from. + + Use it to collect anything the platform stored on your behalf -- for example the + archive a framework export leaves behind, whose `storageUri` comes back in the + background task's `outputs`. + + The workspace is taken from the API key, so there is nothing else to send. The + url is only issued for objects your workspace owns, and `404` covers both "no + such object" and "not yours". + + Args: + storage_uri: The object's storage uri. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/storage/presigned-url", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"storage_uri": storage_uri}, presigned_url_retrieve_params.PresignedURLRetrieveParams + ), + ), + cast_to=PresignedURLRetrieveResponse, + ) + class PresignedURLResourceWithRawResponse: def __init__(self, presigned_url: PresignedURLResource) -> None: @@ -148,6 +245,9 @@ def __init__(self, presigned_url: PresignedURLResource) -> None: self.create = to_raw_response_wrapper( presigned_url.create, ) + self.retrieve = to_raw_response_wrapper( + presigned_url.retrieve, + ) class AsyncPresignedURLResourceWithRawResponse: @@ -157,6 +257,9 @@ def __init__(self, presigned_url: AsyncPresignedURLResource) -> None: self.create = async_to_raw_response_wrapper( presigned_url.create, ) + self.retrieve = async_to_raw_response_wrapper( + presigned_url.retrieve, + ) class PresignedURLResourceWithStreamingResponse: @@ -166,6 +269,9 @@ def __init__(self, presigned_url: PresignedURLResource) -> None: self.create = to_streamed_response_wrapper( presigned_url.create, ) + self.retrieve = to_streamed_response_wrapper( + presigned_url.retrieve, + ) class AsyncPresignedURLResourceWithStreamingResponse: @@ -175,3 +281,6 @@ def __init__(self, presigned_url: AsyncPresignedURLResource) -> None: self.create = async_to_streamed_response_wrapper( presigned_url.create, ) + self.retrieve = async_to_streamed_response_wrapper( + presigned_url.retrieve, + ) diff --git a/src/openlayer/types/__init__.py b/src/openlayer/types/__init__.py index b5752698..09a173ed 100644 --- a/src/openlayer/types/__init__.py +++ b/src/openlayer/types/__init__.py @@ -17,6 +17,7 @@ from .test_list_results_response import TestListResultsResponse as TestListResultsResponse from .workspace_retrieve_response import WorkspaceRetrieveResponse as WorkspaceRetrieveResponse from .inference_pipeline_update_params import InferencePipelineUpdateParams as InferencePipelineUpdateParams +from .background_task_retrieve_response import BackgroundTaskRetrieveResponse as BackgroundTaskRetrieveResponse from .inference_pipeline_retrieve_params import InferencePipelineRetrieveParams as InferencePipelineRetrieveParams from .inference_pipeline_update_response import InferencePipelineUpdateResponse as InferencePipelineUpdateResponse from .inference_pipeline_retrieve_response import InferencePipelineRetrieveResponse as InferencePipelineRetrieveResponse diff --git a/src/openlayer/types/background_task_retrieve_response.py b/src/openlayer/types/background_task_retrieve_response.py new file mode 100644 index 00000000..fb2ce9ac --- /dev/null +++ b/src/openlayer/types/background_task_retrieve_response.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["BackgroundTaskRetrieveResponse"] + + +class BackgroundTaskRetrieveResponse(BaseModel): + id: str + """The background task id.""" + + complete: bool + """Whether the task has finished. Check this before reading `outputs`.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """When the task was queued.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """When the task last reported progress.""" + + name: str + """The task's internal name, including the arguments it was queued with.""" + + progress: float + """How far along the task is, from 0 to 100.""" + + error: Optional[str] = None + """Why the task failed, or `null` if it has not failed.""" + + outputs: Optional[object] = None + """Whatever the task produced, keyed by name. + + `null` until the task completes. A framework export returns `storageUri` -- pass + it to `GET /storage/presigned-url` to download the archive -- along with + `filename`, `controlCount`, `evidenceCount` and `missingEvidenceCount`. + """ diff --git a/src/openlayer/types/governance/__init__.py b/src/openlayer/types/governance/__init__.py new file mode 100644 index 00000000..cf3af05a --- /dev/null +++ b/src/openlayer/types/governance/__init__.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .rule_list_params import RuleListParams as RuleListParams +from .rule_create_params import RuleCreateParams as RuleCreateParams +from .rule_list_response import RuleListResponse as RuleListResponse +from .rule_update_params import RuleUpdateParams as RuleUpdateParams +from .rule_create_response import RuleCreateResponse as RuleCreateResponse +from .rule_tag_list_params import RuleTagListParams as RuleTagListParams +from .rule_update_response import RuleUpdateResponse as RuleUpdateResponse +from .framework_list_params import FrameworkListParams as FrameworkListParams +from .rule_retrieve_response import RuleRetrieveResponse as RuleRetrieveResponse +from .rule_tag_list_response import RuleTagListResponse as RuleTagListResponse +from .framework_create_params import FrameworkCreateParams as FrameworkCreateParams +from .framework_export_params import FrameworkExportParams as FrameworkExportParams +from .framework_list_response import FrameworkListResponse as FrameworkListResponse +from .framework_update_params import FrameworkUpdateParams as FrameworkUpdateParams +from .rule_result_list_params import RuleResultListParams as RuleResultListParams +from .framework_create_response import FrameworkCreateResponse as FrameworkCreateResponse +from .framework_export_response import FrameworkExportResponse as FrameworkExportResponse +from .framework_update_response import FrameworkUpdateResponse as FrameworkUpdateResponse +from .rule_result_list_response import RuleResultListResponse as RuleResultListResponse +from .rule_result_update_params import RuleResultUpdateParams as RuleResultUpdateParams +from .rule_stat_retrieve_params import RuleStatRetrieveParams as RuleStatRetrieveParams +from .framework_list_rules_params import FrameworkListRulesParams as FrameworkListRulesParams +from .framework_retrieve_response import FrameworkRetrieveResponse as FrameworkRetrieveResponse +from .rule_result_update_response import RuleResultUpdateResponse as RuleResultUpdateResponse +from .rule_stat_retrieve_response import RuleStatRetrieveResponse as RuleStatRetrieveResponse +from .framework_list_rules_response import FrameworkListRulesResponse as FrameworkListRulesResponse +from .rule_result_retrieve_response import RuleResultRetrieveResponse as RuleResultRetrieveResponse +from .framework_list_projects_params import FrameworkListProjectsParams as FrameworkListProjectsParams +from .framework_list_projects_response import FrameworkListProjectsResponse as FrameworkListProjectsResponse +from .rule_result_list_evidence_params import RuleResultListEvidenceParams as RuleResultListEvidenceParams +from .rule_result_create_evidence_params import RuleResultCreateEvidenceParams as RuleResultCreateEvidenceParams +from .rule_result_list_evidence_response import RuleResultListEvidenceResponse as RuleResultListEvidenceResponse +from .rule_result_create_evidence_response import RuleResultCreateEvidenceResponse as RuleResultCreateEvidenceResponse +from .framework_list_project_rule_stats_params import ( + FrameworkListProjectRuleStatsParams as FrameworkListProjectRuleStatsParams, +) +from .framework_list_project_rule_stats_response import ( + FrameworkListProjectRuleStatsResponse as FrameworkListProjectRuleStatsResponse, +) diff --git a/src/openlayer/types/governance/framework_create_params.py b/src/openlayer/types/governance/framework_create_params.py new file mode 100644 index 00000000..9af36302 --- /dev/null +++ b/src/openlayer/types/governance/framework_create_params.py @@ -0,0 +1,67 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, Annotated, TypedDict + +from ..._types import SequenceNotStr +from ..._utils import PropertyInfo + +__all__ = ["FrameworkCreateParams", "ProjectSelector", "ProjectSelectorMatch"] + + +class FrameworkCreateParams(TypedDict, total=False): + name: Required[str] + """The framework name.""" + + description: Optional[str] + """A short description of the framework.""" + + enabled: bool + """Whether the framework is active. + + Rules of a disabled framework are not evaluated and do not count towards + compliance. + """ + + project_selector: Annotated[Optional[ProjectSelector], PropertyInfo(alias="projectSelector")] + """Determines which projects the framework applies to. + + An empty or `null` `match` array applies the framework to every project in the + workspace. + """ + + tags: SequenceNotStr[str] + """Free-form labels on the framework.""" + + +class ProjectSelectorMatch(TypedDict, total=False): + property: Required[Literal["taskType", "riskLevel", "riskTotalScore", "name", "ownerId", "modelTypes"]] + """The project property to match against.""" + + value: Required[object] + """The value to match against. + + Pass an array to match any of several values, or `null` to match projects where + the property is unset. Omit it for `exists` and `notExists`. + """ + + operator: str + """How to compare the project property with `value`. + + One of `equals`, `notEquals`, `contains`, `notContains`, `startsWith`, + `endsWith`, `in`, `notIn`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, + `lessThanOrEqual`, `equalsIgnoreCase`, `containsIgnoreCase`, `matches`, + `exists`, or `notExists`. + """ + + +class ProjectSelector(TypedDict, total=False): + """Determines which projects the framework applies to. + + An empty or `null` `match` array applies the framework to every project in the workspace. + """ + + match: Optional[Iterable[ProjectSelectorMatch]] + """Match criteria, ANDed together.""" diff --git a/src/openlayer/types/governance/framework_create_response.py b/src/openlayer/types/governance/framework_create_response.py new file mode 100644 index 00000000..4ff9942b --- /dev/null +++ b/src/openlayer/types/governance/framework_create_response.py @@ -0,0 +1,195 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = [ + "FrameworkCreateResponse", + "Avatar", + "ProjectSelector", + "ProjectSelectorMatch", + "RuleStats", + "RuleStatsProjectCompletion", + "RuleStatsRuleResults", +] + + +class Avatar(BaseModel): + """The icon shown for the framework.""" + + type: Literal["emoji", "imageUrl", "builtinImage"] + + value: str + + +class ProjectSelectorMatch(BaseModel): + property: Literal["taskType", "riskLevel", "riskTotalScore", "name", "ownerId", "modelTypes"] + """The project property to match against.""" + + value: object + """The value to match against. + + Pass an array to match any of several values, or `null` to match projects where + the property is unset. Omit it for `exists` and `notExists`. + """ + + operator: Optional[str] = None + """How to compare the project property with `value`. + + One of `equals`, `notEquals`, `contains`, `notContains`, `startsWith`, + `endsWith`, `in`, `notIn`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, + `lessThanOrEqual`, `equalsIgnoreCase`, `containsIgnoreCase`, `matches`, + `exists`, or `notExists`. + """ + + +class ProjectSelector(BaseModel): + """Determines which projects the framework applies to. + + An empty or `null` `match` array applies the framework to every project in the workspace. + """ + + match: Optional[List[ProjectSelectorMatch]] = None + """Match criteria, ANDed together.""" + + +class RuleStatsProjectCompletion(BaseModel): + """ + How many of the framework's projects fall into each completion band, where a project's completion is the share of its rule results that are passing or skipped. Projects with no evaluated results count as `low`. + + Zeroed when the request carries `projectId`: the bands compare a framework's projects against each other, which says nothing about a single project. + """ + + high: int + """Projects at 80% completion or above.""" + + low: int + """Projects below 20% completion.""" + + mid: int + """Projects at or above 20% but below 80% completion.""" + + +class RuleStatsRuleResults(BaseModel): + """ + Rule result counts by status for this framework, matching what `/workspaces/{workspaceId}/rule-stats?frameworkId=` reports. Narrowed to a single project when the request also carries `projectId`. + """ + + total: int + """The total number of rule results.""" + + total_due_soon: int = FieldInfo(alias="totalDueSoon") + """The number of rule results whose evidence is about to expire.""" + + total_error: int = FieldInfo(alias="totalError") + """The number of rule results that errored during evaluation.""" + + total_failing: int = FieldInfo(alias="totalFailing") + """The number of failing rule results.""" + + total_passing: int = FieldInfo(alias="totalPassing") + """The number of passing rule results.""" + + total_pending: int = FieldInfo(alias="totalPending") + """The number of rule results that have not been satisfied yet.""" + + total_running: int = FieldInfo(alias="totalRunning") + """The number of rule results currently being evaluated.""" + + total_skipped: int = FieldInfo(alias="totalSkipped") + """The number of skipped rule results.""" + + +class RuleStats(BaseModel): + """Compliance roll-up for the framework. + + Present only on `GET /workspaces/{workspaceId}/frameworks` when the request sets `includeRuleStats=true`. + """ + + project_completion: RuleStatsProjectCompletion = FieldInfo(alias="projectCompletion") + """ + How many of the framework's projects fall into each completion band, where a + project's completion is the share of its rule results that are passing or + skipped. Projects with no evaluated results count as `low`. + + Zeroed when the request carries `projectId`: the bands compare a framework's + projects against each other, which says nothing about a single project. + """ + + rule_results: RuleStatsRuleResults = FieldInfo(alias="ruleResults") + """ + Rule result counts by status for this framework, matching what + `/workspaces/{workspaceId}/rule-stats?frameworkId=` reports. Narrowed to a + single project when the request also carries `projectId`. + """ + + +class FrameworkCreateResponse(BaseModel): + id: str + """The framework id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + enabled: bool + """Whether the framework is active. + + Rules of a disabled framework are not evaluated and do not count towards + compliance. + """ + + name: str + """The framework name.""" + + tags: List[str] + """Free-form labels on the framework.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the framework belongs to.""" + + avatar: Optional[Avatar] = None + """The icon shown for the framework.""" + + built_in_slug: Optional[str] = FieldInfo(alias="builtInSlug", default=None) + """ + Identifies a framework that ships with Openlayer, for example `eu_ai_act`, + `iso_42001`, `nist_ai_rmf`, or `traiga`. `null` for frameworks you create + yourself. + """ + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The user who created the framework. `null` for built-in frameworks.""" + + description: Optional[str] = None + """A short description of the framework.""" + + extended_description: Optional[Dict[str, object]] = FieldInfo(alias="extendedDescription", default=None) + """A longer, rich-text description, as a TipTap JSON document.""" + + href: Optional[str] = None + """A link to the external standard or regulation the framework is based on.""" + + immutable: Optional[bool] = None + """Whether the framework definition is managed by Openlayer and cannot be edited.""" + + project_selector: Optional[ProjectSelector] = FieldInfo(alias="projectSelector", default=None) + """Determines which projects the framework applies to. + + An empty or `null` `match` array applies the framework to every project in the + workspace. + """ + + rule_stats: Optional[RuleStats] = FieldInfo(alias="ruleStats", default=None) + """Compliance roll-up for the framework. + + Present only on `GET /workspaces/{workspaceId}/frameworks` when the request sets + `includeRuleStats=true`. + """ diff --git a/src/openlayer/types/governance/framework_export_params.py b/src/openlayer/types/governance/framework_export_params.py new file mode 100644 index 00000000..43b661ae --- /dev/null +++ b/src/openlayer/types/governance/framework_export_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["FrameworkExportParams"] + + +class FrameworkExportParams(TypedDict, total=False): + project_id: Annotated[Optional[str], PropertyInfo(alias="projectId")] + """Scope the export to this project. It must belong to the framework.""" diff --git a/src/openlayer/types/governance/framework_export_response.py b/src/openlayer/types/governance/framework_export_response.py new file mode 100644 index 00000000..5e4df9f1 --- /dev/null +++ b/src/openlayer/types/governance/framework_export_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["FrameworkExportResponse"] + + +class FrameworkExportResponse(BaseModel): + task_result_id: str = FieldInfo(alias="taskResultId") + """The background task id, for `GET /background-tasks/{taskId}`.""" + + task_result_url: str = FieldInfo(alias="taskResultUrl") + """The path to poll for this export's status and result. + + Already `/v1`-prefixed, so it is relative to the API host rather than to the + `/v1` base url -- or just pass `taskResultId` to + `GET /background-tasks/{taskId}`. + """ diff --git a/src/openlayer/types/governance/framework_list_params.py b/src/openlayer/types/governance/framework_list_params.py new file mode 100644 index 00000000..de459770 --- /dev/null +++ b/src/openlayer/types/governance/framework_list_params.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Annotated, TypedDict + +from ..._types import SequenceNotStr +from ..._utils import PropertyInfo + +__all__ = ["FrameworkListParams"] + + +class FrameworkListParams(TypedDict, total=False): + asc: bool + """Whether to sort in ascending order.""" + + completion_operator: Annotated[Literal["is", ">", ">=", "<", "<=", "!="], PropertyInfo(alias="completionOperator")] + """How to compare each framework's completion percentage with `completionValue`. + + Must be sent together with `completionValue`. + """ + + completion_value: Annotated[int, PropertyInfo(alias="completionValue")] + """The completion percentage to compare against, from 0 to 100.""" + + enabled: bool + """Only include frameworks that are enabled (or disabled).""" + + include_rule_stats: Annotated[bool, PropertyInfo(alias="includeRuleStats")] + """ + Whether to include a `ruleStats` object on each framework, with its rule result + status counts and its per-project completion buckets. Computed over the returned + page only. + """ + + page: int + """The page to return in a paginated query.""" + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Maximum number of items to return per page.""" + + project_id: Annotated[str, PropertyInfo(alias="projectId")] + """Only include items that apply to this project.""" + + search_query: Annotated[str, PropertyInfo(alias="searchQuery")] + """Filter by a free-text search over names and descriptions.""" + + sort_column: Annotated[ + Literal["name", "enabled", "dateCreated", "dateUpdated", "overallCompletion", "projectCompletionBuckets"], + PropertyInfo(alias="sortColumn"), + ] + """The column to sort on.""" + + tags: SequenceNotStr[str] + """Only include frameworks carrying all of these tags.""" diff --git a/src/openlayer/types/governance/framework_list_project_rule_stats_params.py b/src/openlayer/types/governance/framework_list_project_rule_stats_params.py new file mode 100644 index 00000000..3070c462 --- /dev/null +++ b/src/openlayer/types/governance/framework_list_project_rule_stats_params.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["FrameworkListProjectRuleStatsParams"] + + +class FrameworkListProjectRuleStatsParams(TypedDict, total=False): + asc: bool + """Whether to sort in ascending order.""" + + page: int + """The page to return in a paginated query.""" + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Maximum number of items to return per page.""" + + sort_column: Annotated[ + Literal[ + "projectName", + "total", + "overallCompletion", + "totalPassing", + "totalFailing", + "totalSkipped", + "totalRunning", + "totalError", + "totalPending", + "totalDueSoon", + ], + PropertyInfo(alias="sortColumn"), + ] + """The column to sort on.""" diff --git a/src/openlayer/types/governance/framework_list_project_rule_stats_response.py b/src/openlayer/types/governance/framework_list_project_rule_stats_response.py new file mode 100644 index 00000000..7a4e7749 --- /dev/null +++ b/src/openlayer/types/governance/framework_list_project_rule_stats_response.py @@ -0,0 +1,117 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = [ + "FrameworkListProjectRuleStatsResponse", + "Item", + "ItemByRuleType", + "ItemByRuleTypeEvidence", + "ItemByRuleTypePlatform", +] + + +class ItemByRuleTypeEvidence(BaseModel): + total: int + """The total number of rule results.""" + + total_due_soon: int = FieldInfo(alias="totalDueSoon") + """The number of rule results whose evidence is about to expire.""" + + total_error: int = FieldInfo(alias="totalError") + """The number of rule results that errored during evaluation.""" + + total_failing: int = FieldInfo(alias="totalFailing") + """The number of failing rule results.""" + + total_passing: int = FieldInfo(alias="totalPassing") + """The number of passing rule results.""" + + total_pending: int = FieldInfo(alias="totalPending") + """The number of rule results that have not been satisfied yet.""" + + total_running: int = FieldInfo(alias="totalRunning") + """The number of rule results currently being evaluated.""" + + total_skipped: int = FieldInfo(alias="totalSkipped") + """The number of skipped rule results.""" + + +class ItemByRuleTypePlatform(BaseModel): + total: int + """The total number of rule results.""" + + total_due_soon: int = FieldInfo(alias="totalDueSoon") + """The number of rule results whose evidence is about to expire.""" + + total_error: int = FieldInfo(alias="totalError") + """The number of rule results that errored during evaluation.""" + + total_failing: int = FieldInfo(alias="totalFailing") + """The number of failing rule results.""" + + total_passing: int = FieldInfo(alias="totalPassing") + """The number of passing rule results.""" + + total_pending: int = FieldInfo(alias="totalPending") + """The number of rule results that have not been satisfied yet.""" + + total_running: int = FieldInfo(alias="totalRunning") + """The number of rule results currently being evaluated.""" + + total_skipped: int = FieldInfo(alias="totalSkipped") + """The number of skipped rule results.""" + + +class ItemByRuleType(BaseModel): + """The same counts, broken down by the type of the rule each result belongs to.""" + + evidence: Optional[ItemByRuleTypeEvidence] = None + + platform: Optional[ItemByRuleTypePlatform] = None + + +class Item(BaseModel): + project_id: str = FieldInfo(alias="projectId") + """The project id.""" + + project_name: str = FieldInfo(alias="projectName") + """The project name.""" + + task_type: str = FieldInfo(alias="taskType") + """The project's task type.""" + + total: int + """The total number of rule results.""" + + total_due_soon: int = FieldInfo(alias="totalDueSoon") + """The number of rule results whose evidence is about to expire.""" + + total_error: int = FieldInfo(alias="totalError") + """The number of rule results that errored during evaluation.""" + + total_failing: int = FieldInfo(alias="totalFailing") + """The number of failing rule results.""" + + total_passing: int = FieldInfo(alias="totalPassing") + """The number of passing rule results.""" + + total_pending: int = FieldInfo(alias="totalPending") + """The number of rule results that have not been satisfied yet.""" + + total_running: int = FieldInfo(alias="totalRunning") + """The number of rule results currently being evaluated.""" + + total_skipped: int = FieldInfo(alias="totalSkipped") + """The number of skipped rule results.""" + + by_rule_type: Optional[ItemByRuleType] = FieldInfo(alias="byRuleType", default=None) + """The same counts, broken down by the type of the rule each result belongs to.""" + + +class FrameworkListProjectRuleStatsResponse(BaseModel): + items: List[Item] diff --git a/src/openlayer/types/governance/framework_list_projects_params.py b/src/openlayer/types/governance/framework_list_projects_params.py new file mode 100644 index 00000000..a121ac1f --- /dev/null +++ b/src/openlayer/types/governance/framework_list_projects_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["FrameworkListProjectsParams"] + + +class FrameworkListProjectsParams(TypedDict, total=False): + page: int + """The page to return in a paginated query.""" + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Maximum number of items to return per page.""" diff --git a/src/openlayer/types/governance/framework_list_projects_response.py b/src/openlayer/types/governance/framework_list_projects_response.py new file mode 100644 index 00000000..c6f65e46 --- /dev/null +++ b/src/openlayer/types/governance/framework_list_projects_response.py @@ -0,0 +1,113 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["FrameworkListProjectsResponse", "Item", "ItemLinks", "ItemGitRepo"] + + +class ItemLinks(BaseModel): + """Links to the project.""" + + app: str + + +class ItemGitRepo(BaseModel): + id: str + + date_connected: datetime = FieldInfo(alias="dateConnected") + + date_updated: datetime = FieldInfo(alias="dateUpdated") + + git_account_id: str = FieldInfo(alias="gitAccountId") + + git_id: int = FieldInfo(alias="gitId") + + name: str + + private: bool + + project_id: str = FieldInfo(alias="projectId") + + slug: str + + url: str + + branch: Optional[str] = None + + root_dir: Optional[str] = FieldInfo(alias="rootDir", default=None) + + +class Item(BaseModel): + id: str + """The project id.""" + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The project creator id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The project creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The project last updated date.""" + + development_goal_count: int = FieldInfo(alias="developmentGoalCount") + """The number of tests in the development mode of the project.""" + + goal_count: int = FieldInfo(alias="goalCount") + """The total number of tests in the project.""" + + inference_pipeline_count: int = FieldInfo(alias="inferencePipelineCount") + """The number of inference pipelines in the project.""" + + links: ItemLinks + """Links to the project.""" + + monitoring_goal_count: int = FieldInfo(alias="monitoringGoalCount") + """The number of tests in the monitoring mode of the project.""" + + name: str + """The project name.""" + + source: Optional[Literal["web", "api"]] = None + """The source of the project.""" + + task_type: Literal["llm-base", "tabular-classification", "tabular-regression", "text-classification"] = FieldInfo( + alias="taskType" + ) + """The task type of the project.""" + + version_count: int = FieldInfo(alias="versionCount") + """The number of versions (commits) in the project.""" + + workspace_id: Optional[str] = FieldInfo(alias="workspaceId", default=None) + """The workspace id.""" + + data_retention_days: Optional[int] = FieldInfo(alias="dataRetentionDays", default=None) + """Number of days to retain monitoring data for this project. + + Null means data is retained indefinitely. + """ + + description: Optional[str] = None + """The project description.""" + + git_repo: Optional[ItemGitRepo] = FieldInfo(alias="gitRepo", default=None) + + api_model_developer: Optional[str] = FieldInfo(alias="modelDeveloper", default=None) + """Who developed the model used in this project.""" + + api_model_types: Optional[List[str]] = FieldInfo(alias="modelTypes", default=None) + """The kinds of model used in this project.""" + + purpose: Optional[str] = None + """What the system in this project is intended to do.""" + + +class FrameworkListProjectsResponse(BaseModel): + items: List[Item] diff --git a/src/openlayer/types/governance/framework_list_response.py b/src/openlayer/types/governance/framework_list_response.py new file mode 100644 index 00000000..f917e020 --- /dev/null +++ b/src/openlayer/types/governance/framework_list_response.py @@ -0,0 +1,200 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = [ + "FrameworkListResponse", + "Item", + "ItemAvatar", + "ItemProjectSelector", + "ItemProjectSelectorMatch", + "ItemRuleStats", + "ItemRuleStatsProjectCompletion", + "ItemRuleStatsRuleResults", +] + + +class ItemAvatar(BaseModel): + """The icon shown for the framework.""" + + type: Literal["emoji", "imageUrl", "builtinImage"] + + value: str + + +class ItemProjectSelectorMatch(BaseModel): + property: Literal["taskType", "riskLevel", "riskTotalScore", "name", "ownerId", "modelTypes"] + """The project property to match against.""" + + value: object + """The value to match against. + + Pass an array to match any of several values, or `null` to match projects where + the property is unset. Omit it for `exists` and `notExists`. + """ + + operator: Optional[str] = None + """How to compare the project property with `value`. + + One of `equals`, `notEquals`, `contains`, `notContains`, `startsWith`, + `endsWith`, `in`, `notIn`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, + `lessThanOrEqual`, `equalsIgnoreCase`, `containsIgnoreCase`, `matches`, + `exists`, or `notExists`. + """ + + +class ItemProjectSelector(BaseModel): + """Determines which projects the framework applies to. + + An empty or `null` `match` array applies the framework to every project in the workspace. + """ + + match: Optional[List[ItemProjectSelectorMatch]] = None + """Match criteria, ANDed together.""" + + +class ItemRuleStatsProjectCompletion(BaseModel): + """ + How many of the framework's projects fall into each completion band, where a project's completion is the share of its rule results that are passing or skipped. Projects with no evaluated results count as `low`. + + Zeroed when the request carries `projectId`: the bands compare a framework's projects against each other, which says nothing about a single project. + """ + + high: int + """Projects at 80% completion or above.""" + + low: int + """Projects below 20% completion.""" + + mid: int + """Projects at or above 20% but below 80% completion.""" + + +class ItemRuleStatsRuleResults(BaseModel): + """ + Rule result counts by status for this framework, matching what `/workspaces/{workspaceId}/rule-stats?frameworkId=` reports. Narrowed to a single project when the request also carries `projectId`. + """ + + total: int + """The total number of rule results.""" + + total_due_soon: int = FieldInfo(alias="totalDueSoon") + """The number of rule results whose evidence is about to expire.""" + + total_error: int = FieldInfo(alias="totalError") + """The number of rule results that errored during evaluation.""" + + total_failing: int = FieldInfo(alias="totalFailing") + """The number of failing rule results.""" + + total_passing: int = FieldInfo(alias="totalPassing") + """The number of passing rule results.""" + + total_pending: int = FieldInfo(alias="totalPending") + """The number of rule results that have not been satisfied yet.""" + + total_running: int = FieldInfo(alias="totalRunning") + """The number of rule results currently being evaluated.""" + + total_skipped: int = FieldInfo(alias="totalSkipped") + """The number of skipped rule results.""" + + +class ItemRuleStats(BaseModel): + """Compliance roll-up for the framework. + + Present only on `GET /workspaces/{workspaceId}/frameworks` when the request sets `includeRuleStats=true`. + """ + + project_completion: ItemRuleStatsProjectCompletion = FieldInfo(alias="projectCompletion") + """ + How many of the framework's projects fall into each completion band, where a + project's completion is the share of its rule results that are passing or + skipped. Projects with no evaluated results count as `low`. + + Zeroed when the request carries `projectId`: the bands compare a framework's + projects against each other, which says nothing about a single project. + """ + + rule_results: ItemRuleStatsRuleResults = FieldInfo(alias="ruleResults") + """ + Rule result counts by status for this framework, matching what + `/workspaces/{workspaceId}/rule-stats?frameworkId=` reports. Narrowed to a + single project when the request also carries `projectId`. + """ + + +class Item(BaseModel): + id: str + """The framework id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + enabled: bool + """Whether the framework is active. + + Rules of a disabled framework are not evaluated and do not count towards + compliance. + """ + + name: str + """The framework name.""" + + tags: List[str] + """Free-form labels on the framework.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the framework belongs to.""" + + avatar: Optional[ItemAvatar] = None + """The icon shown for the framework.""" + + built_in_slug: Optional[str] = FieldInfo(alias="builtInSlug", default=None) + """ + Identifies a framework that ships with Openlayer, for example `eu_ai_act`, + `iso_42001`, `nist_ai_rmf`, or `traiga`. `null` for frameworks you create + yourself. + """ + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The user who created the framework. `null` for built-in frameworks.""" + + description: Optional[str] = None + """A short description of the framework.""" + + extended_description: Optional[Dict[str, object]] = FieldInfo(alias="extendedDescription", default=None) + """A longer, rich-text description, as a TipTap JSON document.""" + + href: Optional[str] = None + """A link to the external standard or regulation the framework is based on.""" + + immutable: Optional[bool] = None + """Whether the framework definition is managed by Openlayer and cannot be edited.""" + + project_selector: Optional[ItemProjectSelector] = FieldInfo(alias="projectSelector", default=None) + """Determines which projects the framework applies to. + + An empty or `null` `match` array applies the framework to every project in the + workspace. + """ + + rule_stats: Optional[ItemRuleStats] = FieldInfo(alias="ruleStats", default=None) + """Compliance roll-up for the framework. + + Present only on `GET /workspaces/{workspaceId}/frameworks` when the request sets + `includeRuleStats=true`. + """ + + +class FrameworkListResponse(BaseModel): + items: List[Item] diff --git a/src/openlayer/types/governance/framework_list_rules_params.py b/src/openlayer/types/governance/framework_list_rules_params.py new file mode 100644 index 00000000..618af53a --- /dev/null +++ b/src/openlayer/types/governance/framework_list_rules_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["FrameworkListRulesParams"] + + +class FrameworkListRulesParams(TypedDict, total=False): + page: int + """The page to return in a paginated query.""" + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Maximum number of items to return per page.""" diff --git a/src/openlayer/types/governance/framework_list_rules_response.py b/src/openlayer/types/governance/framework_list_rules_response.py new file mode 100644 index 00000000..53698c24 --- /dev/null +++ b/src/openlayer/types/governance/framework_list_rules_response.py @@ -0,0 +1,242 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = [ + "FrameworkListRulesResponse", + "Item", + "ItemFramework", + "ItemFrameworkAvatar", + "ItemResult", + "ItemResultBlockedBy", + "ItemResultBlocking", + "ItemResultsSummary", + "ItemTag", +] + + +class ItemFrameworkAvatar(BaseModel): + """The icon shown for the framework.""" + + type: Literal["emoji", "imageUrl", "builtinImage"] + + value: str + + +class ItemFramework(BaseModel): + id: str + """The framework id.""" + + avatar: Optional[ItemFrameworkAvatar] = None + """The icon shown for the framework.""" + + built_in_slug: Optional[str] = FieldInfo(alias="builtInSlug", default=None) + """ + Identifies a framework that ships with Openlayer, for example `eu_ai_act`, + `iso_42001`, `nist_ai_rmf`, or `traiga`. `null` for frameworks you create + yourself. + """ + + enabled: bool + """Whether the framework is active. + + Rules of a disabled framework are not evaluated and do not count towards + compliance. + """ + + name: str + """The framework name.""" + + +class ItemResultBlockedBy(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class ItemResultBlocking(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class ItemResult(BaseModel): + id: str + """The rule result id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + deactivated: bool + """Whether this result is excluded from compliance calculations.""" + + rule_id: str = FieldInfo(alias="ruleId") + """The rule this result belongs to.""" + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """The compliance status of the rule for this entity.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule result belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for this result.""" + + blocked_by: Optional[List[ItemResultBlockedBy]] = FieldInfo(alias="blockedBy", default=None) + """Rule results that must pass before this one can be satisfied.""" + + blocking: Optional[List[ItemResultBlocking]] = None + """Rule results that this one blocks.""" + + date_last_evaluated: Optional[datetime] = FieldInfo(alias="dateLastEvaluated", default=None) + """When the rule was last evaluated. Platform rules only.""" + + date_of_latest_evidence: Optional[datetime] = FieldInfo(alias="dateOfLatestEvidence", default=None) + """When the most recent piece of evidence was attached. Evidence rules only.""" + + date_of_next_evaluation: Optional[datetime] = FieldInfo(alias="dateOfNextEvaluation", default=None) + """When the rule will next be evaluated. Platform rules only.""" + + date_of_renewal: Optional[datetime] = FieldInfo(alias="dateOfRenewal", default=None) + """When the evidence must be renewed. Evidence rules with a renewal cadence only.""" + + deactivated_reason: Optional[str] = FieldInfo(alias="deactivatedReason", default=None) + """Why the result was excluded.""" + + project_id: Optional[str] = FieldInfo(alias="projectId", default=None) + """The project this result was evaluated for. `null` for workspace-scoped rules.""" + + status_message: Optional[str] = FieldInfo(alias="statusMessage", default=None) + """A human-readable explanation of the status.""" + + +class ItemResultsSummary(BaseModel): + """ + Pass-rate counts across all of the rule's entities, independent of any status filter applied to the request. + """ + + passing: Optional[int] = None + + total: Optional[int] = None + + +class ItemTag(BaseModel): + id: str + """The rule tag id.""" + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The user who created the tag. `null` for tags that ship with Openlayer.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + immutable: bool + """Whether the tag is managed by Openlayer and cannot be edited or deleted.""" + + name: str + """The tag name.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the tag belongs to.""" + + color: Optional[str] = None + """The color the tag is displayed with.""" + + +class Item(BaseModel): + id: str + """The rule id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + name: str + """The rule name.""" + + scope: Literal["project", "workspace"] + """ + Whether the rule is evaluated once for the whole workspace, or once per project + the rule's frameworks apply to. + """ + + type: Literal["platform", "evidence"] + """ + `platform` rules are evaluated automatically from the state of your Openlayer + workspace. `evidence` rules are satisfied by attaching evidence. + """ + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for satisfying the rule.""" + + automation_params: Optional[Dict[str, object]] = FieldInfo(alias="automationParams", default=None) + """Configuration for the platform check, when the automation takes parameters.""" + + automation_type: Optional[str] = FieldInfo(alias="automationType", default=None) + """ + Which workspace signal a platform rule checks, for example + `monitoring_mode_enabled`, `test_setup`, or `project_owner_set`. `null` for + evidence rules. + """ + + deactivated: Optional[bool] = None + """Whether the rule is excluded from compliance calculations.""" + + description: Optional[str] = None + """What the rule requires.""" + + evidence_type: Optional[Literal["document", "text", "url", "categoryValue"]] = FieldInfo( + alias="evidenceType", default=None + ) + """The kind of evidence that satisfies the rule. `null` for platform rules.""" + + frameworks: Optional[List[ItemFramework]] = None + """The frameworks that include this rule.""" + + immutable: Optional[bool] = None + """Whether the rule is managed by Openlayer and cannot be edited.""" + + renewal_cadence_days: Optional[int] = FieldInfo(alias="renewalCadenceDays", default=None) + """How often evidence must be renewed, in days. + + Once evidence is older than this, the rule result becomes `due_soon` and then + `failing`. + """ + + results: Optional[List[ItemResult]] = None + """The rule's results, one per entity the rule is evaluated against. + + Only returned when `includeResults` is `true`. + """ + + results_summary: Optional[ItemResultsSummary] = FieldInfo(alias="resultsSummary", default=None) + """ + Pass-rate counts across all of the rule's entities, independent of any status + filter applied to the request. + """ + + tags: Optional[List[ItemTag]] = None + """The rule tags associated with the rule.""" + + +class FrameworkListRulesResponse(BaseModel): + items: List[Item] diff --git a/src/openlayer/types/governance/framework_retrieve_response.py b/src/openlayer/types/governance/framework_retrieve_response.py new file mode 100644 index 00000000..6c3f37da --- /dev/null +++ b/src/openlayer/types/governance/framework_retrieve_response.py @@ -0,0 +1,195 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = [ + "FrameworkRetrieveResponse", + "Avatar", + "ProjectSelector", + "ProjectSelectorMatch", + "RuleStats", + "RuleStatsProjectCompletion", + "RuleStatsRuleResults", +] + + +class Avatar(BaseModel): + """The icon shown for the framework.""" + + type: Literal["emoji", "imageUrl", "builtinImage"] + + value: str + + +class ProjectSelectorMatch(BaseModel): + property: Literal["taskType", "riskLevel", "riskTotalScore", "name", "ownerId", "modelTypes"] + """The project property to match against.""" + + value: object + """The value to match against. + + Pass an array to match any of several values, or `null` to match projects where + the property is unset. Omit it for `exists` and `notExists`. + """ + + operator: Optional[str] = None + """How to compare the project property with `value`. + + One of `equals`, `notEquals`, `contains`, `notContains`, `startsWith`, + `endsWith`, `in`, `notIn`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, + `lessThanOrEqual`, `equalsIgnoreCase`, `containsIgnoreCase`, `matches`, + `exists`, or `notExists`. + """ + + +class ProjectSelector(BaseModel): + """Determines which projects the framework applies to. + + An empty or `null` `match` array applies the framework to every project in the workspace. + """ + + match: Optional[List[ProjectSelectorMatch]] = None + """Match criteria, ANDed together.""" + + +class RuleStatsProjectCompletion(BaseModel): + """ + How many of the framework's projects fall into each completion band, where a project's completion is the share of its rule results that are passing or skipped. Projects with no evaluated results count as `low`. + + Zeroed when the request carries `projectId`: the bands compare a framework's projects against each other, which says nothing about a single project. + """ + + high: int + """Projects at 80% completion or above.""" + + low: int + """Projects below 20% completion.""" + + mid: int + """Projects at or above 20% but below 80% completion.""" + + +class RuleStatsRuleResults(BaseModel): + """ + Rule result counts by status for this framework, matching what `/workspaces/{workspaceId}/rule-stats?frameworkId=` reports. Narrowed to a single project when the request also carries `projectId`. + """ + + total: int + """The total number of rule results.""" + + total_due_soon: int = FieldInfo(alias="totalDueSoon") + """The number of rule results whose evidence is about to expire.""" + + total_error: int = FieldInfo(alias="totalError") + """The number of rule results that errored during evaluation.""" + + total_failing: int = FieldInfo(alias="totalFailing") + """The number of failing rule results.""" + + total_passing: int = FieldInfo(alias="totalPassing") + """The number of passing rule results.""" + + total_pending: int = FieldInfo(alias="totalPending") + """The number of rule results that have not been satisfied yet.""" + + total_running: int = FieldInfo(alias="totalRunning") + """The number of rule results currently being evaluated.""" + + total_skipped: int = FieldInfo(alias="totalSkipped") + """The number of skipped rule results.""" + + +class RuleStats(BaseModel): + """Compliance roll-up for the framework. + + Present only on `GET /workspaces/{workspaceId}/frameworks` when the request sets `includeRuleStats=true`. + """ + + project_completion: RuleStatsProjectCompletion = FieldInfo(alias="projectCompletion") + """ + How many of the framework's projects fall into each completion band, where a + project's completion is the share of its rule results that are passing or + skipped. Projects with no evaluated results count as `low`. + + Zeroed when the request carries `projectId`: the bands compare a framework's + projects against each other, which says nothing about a single project. + """ + + rule_results: RuleStatsRuleResults = FieldInfo(alias="ruleResults") + """ + Rule result counts by status for this framework, matching what + `/workspaces/{workspaceId}/rule-stats?frameworkId=` reports. Narrowed to a + single project when the request also carries `projectId`. + """ + + +class FrameworkRetrieveResponse(BaseModel): + id: str + """The framework id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + enabled: bool + """Whether the framework is active. + + Rules of a disabled framework are not evaluated and do not count towards + compliance. + """ + + name: str + """The framework name.""" + + tags: List[str] + """Free-form labels on the framework.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the framework belongs to.""" + + avatar: Optional[Avatar] = None + """The icon shown for the framework.""" + + built_in_slug: Optional[str] = FieldInfo(alias="builtInSlug", default=None) + """ + Identifies a framework that ships with Openlayer, for example `eu_ai_act`, + `iso_42001`, `nist_ai_rmf`, or `traiga`. `null` for frameworks you create + yourself. + """ + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The user who created the framework. `null` for built-in frameworks.""" + + description: Optional[str] = None + """A short description of the framework.""" + + extended_description: Optional[Dict[str, object]] = FieldInfo(alias="extendedDescription", default=None) + """A longer, rich-text description, as a TipTap JSON document.""" + + href: Optional[str] = None + """A link to the external standard or regulation the framework is based on.""" + + immutable: Optional[bool] = None + """Whether the framework definition is managed by Openlayer and cannot be edited.""" + + project_selector: Optional[ProjectSelector] = FieldInfo(alias="projectSelector", default=None) + """Determines which projects the framework applies to. + + An empty or `null` `match` array applies the framework to every project in the + workspace. + """ + + rule_stats: Optional[RuleStats] = FieldInfo(alias="ruleStats", default=None) + """Compliance roll-up for the framework. + + Present only on `GET /workspaces/{workspaceId}/frameworks` when the request sets + `includeRuleStats=true`. + """ diff --git a/src/openlayer/types/governance/framework_update_params.py b/src/openlayer/types/governance/framework_update_params.py new file mode 100644 index 00000000..28e1ad62 --- /dev/null +++ b/src/openlayer/types/governance/framework_update_params.py @@ -0,0 +1,84 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Iterable, Optional +from typing_extensions import Literal, Required, Annotated, TypedDict + +from ..._types import SequenceNotStr +from ..._utils import PropertyInfo + +__all__ = ["FrameworkUpdateParams", "Avatar", "ProjectSelector", "ProjectSelectorMatch"] + + +class FrameworkUpdateParams(TypedDict, total=False): + avatar: Optional[Avatar] + """The icon shown for the framework.""" + + description: Optional[str] + """A short description of the framework.""" + + enabled: bool + """Whether the framework is active. + + Rules of a disabled framework are not evaluated and do not count towards + compliance. + """ + + extended_description: Annotated[Optional[Dict[str, object]], PropertyInfo(alias="extendedDescription")] + """A longer, rich-text description, as a TipTap JSON document.""" + + href: Optional[str] + """A link to the external standard or regulation the framework is based on.""" + + name: str + """The framework name.""" + + project_selector: Annotated[Optional[ProjectSelector], PropertyInfo(alias="projectSelector")] + """Determines which projects the framework applies to. + + An empty or `null` `match` array applies the framework to every project in the + workspace. + """ + + tags: SequenceNotStr[str] + """Free-form labels on the framework.""" + + +class Avatar(TypedDict, total=False): + """The icon shown for the framework.""" + + type: Required[Literal["emoji", "imageUrl", "builtinImage"]] + + value: Required[str] + + +class ProjectSelectorMatch(TypedDict, total=False): + property: Required[Literal["taskType", "riskLevel", "riskTotalScore", "name", "ownerId", "modelTypes"]] + """The project property to match against.""" + + value: Required[object] + """The value to match against. + + Pass an array to match any of several values, or `null` to match projects where + the property is unset. Omit it for `exists` and `notExists`. + """ + + operator: str + """How to compare the project property with `value`. + + One of `equals`, `notEquals`, `contains`, `notContains`, `startsWith`, + `endsWith`, `in`, `notIn`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, + `lessThanOrEqual`, `equalsIgnoreCase`, `containsIgnoreCase`, `matches`, + `exists`, or `notExists`. + """ + + +class ProjectSelector(TypedDict, total=False): + """Determines which projects the framework applies to. + + An empty or `null` `match` array applies the framework to every project in the workspace. + """ + + match: Optional[Iterable[ProjectSelectorMatch]] + """Match criteria, ANDed together.""" diff --git a/src/openlayer/types/governance/framework_update_response.py b/src/openlayer/types/governance/framework_update_response.py new file mode 100644 index 00000000..ff9de4cc --- /dev/null +++ b/src/openlayer/types/governance/framework_update_response.py @@ -0,0 +1,195 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = [ + "FrameworkUpdateResponse", + "Avatar", + "ProjectSelector", + "ProjectSelectorMatch", + "RuleStats", + "RuleStatsProjectCompletion", + "RuleStatsRuleResults", +] + + +class Avatar(BaseModel): + """The icon shown for the framework.""" + + type: Literal["emoji", "imageUrl", "builtinImage"] + + value: str + + +class ProjectSelectorMatch(BaseModel): + property: Literal["taskType", "riskLevel", "riskTotalScore", "name", "ownerId", "modelTypes"] + """The project property to match against.""" + + value: object + """The value to match against. + + Pass an array to match any of several values, or `null` to match projects where + the property is unset. Omit it for `exists` and `notExists`. + """ + + operator: Optional[str] = None + """How to compare the project property with `value`. + + One of `equals`, `notEquals`, `contains`, `notContains`, `startsWith`, + `endsWith`, `in`, `notIn`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, + `lessThanOrEqual`, `equalsIgnoreCase`, `containsIgnoreCase`, `matches`, + `exists`, or `notExists`. + """ + + +class ProjectSelector(BaseModel): + """Determines which projects the framework applies to. + + An empty or `null` `match` array applies the framework to every project in the workspace. + """ + + match: Optional[List[ProjectSelectorMatch]] = None + """Match criteria, ANDed together.""" + + +class RuleStatsProjectCompletion(BaseModel): + """ + How many of the framework's projects fall into each completion band, where a project's completion is the share of its rule results that are passing or skipped. Projects with no evaluated results count as `low`. + + Zeroed when the request carries `projectId`: the bands compare a framework's projects against each other, which says nothing about a single project. + """ + + high: int + """Projects at 80% completion or above.""" + + low: int + """Projects below 20% completion.""" + + mid: int + """Projects at or above 20% but below 80% completion.""" + + +class RuleStatsRuleResults(BaseModel): + """ + Rule result counts by status for this framework, matching what `/workspaces/{workspaceId}/rule-stats?frameworkId=` reports. Narrowed to a single project when the request also carries `projectId`. + """ + + total: int + """The total number of rule results.""" + + total_due_soon: int = FieldInfo(alias="totalDueSoon") + """The number of rule results whose evidence is about to expire.""" + + total_error: int = FieldInfo(alias="totalError") + """The number of rule results that errored during evaluation.""" + + total_failing: int = FieldInfo(alias="totalFailing") + """The number of failing rule results.""" + + total_passing: int = FieldInfo(alias="totalPassing") + """The number of passing rule results.""" + + total_pending: int = FieldInfo(alias="totalPending") + """The number of rule results that have not been satisfied yet.""" + + total_running: int = FieldInfo(alias="totalRunning") + """The number of rule results currently being evaluated.""" + + total_skipped: int = FieldInfo(alias="totalSkipped") + """The number of skipped rule results.""" + + +class RuleStats(BaseModel): + """Compliance roll-up for the framework. + + Present only on `GET /workspaces/{workspaceId}/frameworks` when the request sets `includeRuleStats=true`. + """ + + project_completion: RuleStatsProjectCompletion = FieldInfo(alias="projectCompletion") + """ + How many of the framework's projects fall into each completion band, where a + project's completion is the share of its rule results that are passing or + skipped. Projects with no evaluated results count as `low`. + + Zeroed when the request carries `projectId`: the bands compare a framework's + projects against each other, which says nothing about a single project. + """ + + rule_results: RuleStatsRuleResults = FieldInfo(alias="ruleResults") + """ + Rule result counts by status for this framework, matching what + `/workspaces/{workspaceId}/rule-stats?frameworkId=` reports. Narrowed to a + single project when the request also carries `projectId`. + """ + + +class FrameworkUpdateResponse(BaseModel): + id: str + """The framework id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + enabled: bool + """Whether the framework is active. + + Rules of a disabled framework are not evaluated and do not count towards + compliance. + """ + + name: str + """The framework name.""" + + tags: List[str] + """Free-form labels on the framework.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the framework belongs to.""" + + avatar: Optional[Avatar] = None + """The icon shown for the framework.""" + + built_in_slug: Optional[str] = FieldInfo(alias="builtInSlug", default=None) + """ + Identifies a framework that ships with Openlayer, for example `eu_ai_act`, + `iso_42001`, `nist_ai_rmf`, or `traiga`. `null` for frameworks you create + yourself. + """ + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The user who created the framework. `null` for built-in frameworks.""" + + description: Optional[str] = None + """A short description of the framework.""" + + extended_description: Optional[Dict[str, object]] = FieldInfo(alias="extendedDescription", default=None) + """A longer, rich-text description, as a TipTap JSON document.""" + + href: Optional[str] = None + """A link to the external standard or regulation the framework is based on.""" + + immutable: Optional[bool] = None + """Whether the framework definition is managed by Openlayer and cannot be edited.""" + + project_selector: Optional[ProjectSelector] = FieldInfo(alias="projectSelector", default=None) + """Determines which projects the framework applies to. + + An empty or `null` `match` array applies the framework to every project in the + workspace. + """ + + rule_stats: Optional[RuleStats] = FieldInfo(alias="ruleStats", default=None) + """Compliance roll-up for the framework. + + Present only on `GET /workspaces/{workspaceId}/frameworks` when the request sets + `includeRuleStats=true`. + """ diff --git a/src/openlayer/types/governance/frameworks/__init__.py b/src/openlayer/types/governance/frameworks/__init__.py new file mode 100644 index 00000000..47f4d345 --- /dev/null +++ b/src/openlayer/types/governance/frameworks/__init__.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .document_list_params import DocumentListParams as DocumentListParams +from .document_list_response import DocumentListResponse as DocumentListResponse +from .section_list_rules_params import SectionListRulesParams as SectionListRulesParams +from .document_retrieve_response import DocumentRetrieveResponse as DocumentRetrieveResponse +from .section_list_rules_response import SectionListRulesResponse as SectionListRulesResponse +from .subsection_list_rules_params import SubsectionListRulesParams as SubsectionListRulesParams +from .subsection_list_rules_response import SubsectionListRulesResponse as SubsectionListRulesResponse diff --git a/src/openlayer/types/governance/frameworks/document_list_params.py b/src/openlayer/types/governance/frameworks/document_list_params.py new file mode 100644 index 00000000..91509d9e --- /dev/null +++ b/src/openlayer/types/governance/frameworks/document_list_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Annotated, TypedDict + +from ...._utils import PropertyInfo + +__all__ = ["DocumentListParams"] + + +class DocumentListParams(TypedDict, total=False): + page: int + """The page to return in a paginated query.""" + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Maximum number of items to return per page.""" diff --git a/src/openlayer/types/governance/frameworks/document_list_response.py b/src/openlayer/types/governance/frameworks/document_list_response.py new file mode 100644 index 00000000..f88a8a49 --- /dev/null +++ b/src/openlayer/types/governance/frameworks/document_list_response.py @@ -0,0 +1,217 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ...._models import BaseModel + +__all__ = [ + "DocumentListResponse", + "Item", + "ItemSection", + "ItemSectionRule", + "ItemSectionSubsection", + "ItemSectionSubsectionRule", +] + + +class ItemSectionRule(BaseModel): + id: str + """The rule id.""" + + name: str + """The rule name.""" + + scope: Literal["project", "workspace"] + """ + Whether the rule is evaluated once for the whole workspace, or once per project + the rule's frameworks apply to. + """ + + type: Literal["platform", "evidence"] + """ + `platform` rules are evaluated automatically from the state of your Openlayer + workspace. `evidence` rules are satisfied by attaching evidence. + """ + + automation_params: Optional[Dict[str, object]] = FieldInfo(alias="automationParams", default=None) + """Configuration for the platform check, when the automation takes parameters.""" + + automation_type: Optional[str] = FieldInfo(alias="automationType", default=None) + """ + Which workspace signal a platform rule checks, for example + `monitoring_mode_enabled`, `test_setup`, or `project_owner_set`. `null` for + evidence rules. + """ + + date_created: Optional[datetime] = FieldInfo(alias="dateCreated", default=None) + """The creation date.""" + + date_updated: Optional[datetime] = FieldInfo(alias="dateUpdated", default=None) + """The last update date.""" + + deactivated: Optional[bool] = None + """Whether the rule is excluded from compliance calculations.""" + + description: Optional[str] = None + """What the rule requires.""" + + evidence_type: Optional[Literal["document", "text", "url", "categoryValue"]] = FieldInfo( + alias="evidenceType", default=None + ) + """The kind of evidence that satisfies the rule. `null` for platform rules.""" + + immutable: Optional[bool] = None + """Whether the rule is managed by Openlayer and cannot be edited.""" + + renewal_cadence_days: Optional[int] = FieldInfo(alias="renewalCadenceDays", default=None) + """How often evidence must be renewed, in days. + + Once evidence is older than this, the rule result becomes `due_soon` and then + `failing`. + """ + + +class ItemSectionSubsectionRule(BaseModel): + id: str + """The rule id.""" + + name: str + """The rule name.""" + + scope: Literal["project", "workspace"] + """ + Whether the rule is evaluated once for the whole workspace, or once per project + the rule's frameworks apply to. + """ + + type: Literal["platform", "evidence"] + """ + `platform` rules are evaluated automatically from the state of your Openlayer + workspace. `evidence` rules are satisfied by attaching evidence. + """ + + automation_params: Optional[Dict[str, object]] = FieldInfo(alias="automationParams", default=None) + """Configuration for the platform check, when the automation takes parameters.""" + + automation_type: Optional[str] = FieldInfo(alias="automationType", default=None) + """ + Which workspace signal a platform rule checks, for example + `monitoring_mode_enabled`, `test_setup`, or `project_owner_set`. `null` for + evidence rules. + """ + + date_created: Optional[datetime] = FieldInfo(alias="dateCreated", default=None) + """The creation date.""" + + date_updated: Optional[datetime] = FieldInfo(alias="dateUpdated", default=None) + """The last update date.""" + + deactivated: Optional[bool] = None + """Whether the rule is excluded from compliance calculations.""" + + description: Optional[str] = None + """What the rule requires.""" + + evidence_type: Optional[Literal["document", "text", "url", "categoryValue"]] = FieldInfo( + alias="evidenceType", default=None + ) + """The kind of evidence that satisfies the rule. `null` for platform rules.""" + + immutable: Optional[bool] = None + """Whether the rule is managed by Openlayer and cannot be edited.""" + + renewal_cadence_days: Optional[int] = FieldInfo(alias="renewalCadenceDays", default=None) + """How often evidence must be renewed, in days. + + Once evidence is older than this, the rule result becomes `due_soon` and then + `failing`. + """ + + +class ItemSectionSubsection(BaseModel): + id: str + """The subsection id.""" + + number: str + """The subsection number as it appears in the source standard.""" + + section_id: str = FieldInfo(alias="sectionId") + """The section the subsection belongs to.""" + + sort_order: int = FieldInfo(alias="sortOrder") + """The position of the subsection within its section.""" + + title: str + """The subsection title.""" + + rule_count: Optional[int] = FieldInfo(alias="ruleCount", default=None) + """How many rules are linked to this subsection.""" + + rules: Optional[List[ItemSectionSubsectionRule]] = None + """The rules linked to this subsection.""" + + text: Optional[str] = None + """The subsection text. This is the requirement your rules are mapped against.""" + + +class ItemSection(BaseModel): + id: str + """The section id.""" + + document_id: str = FieldInfo(alias="documentId") + """The document the section belongs to.""" + + number: str + """The section number as it appears in the source standard.""" + + sort_order: int = FieldInfo(alias="sortOrder") + """The position of the section within the document.""" + + title: str + """The section title.""" + + rule_count: Optional[int] = FieldInfo(alias="ruleCount", default=None) + """How many rules are linked to this section, including its subsections. + + Use it to decide whether to fetch the section's rules. + """ + + rules: Optional[List[ItemSectionRule]] = None + """The rules linked directly to this section.""" + + subsections: Optional[List[ItemSectionSubsection]] = None + """The section's subsections, in display order.""" + + text: Optional[str] = None + """The section text.""" + + +class Item(BaseModel): + id: str + """The document id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + framework_id: str = FieldInfo(alias="frameworkId") + """The framework the document belongs to.""" + + title: str + """The document title.""" + + sections: Optional[List[ItemSection]] = None + """The document's sections, in display order. + + Only returned when retrieving a single document. + """ + + +class DocumentListResponse(BaseModel): + items: List[Item] diff --git a/src/openlayer/types/governance/frameworks/document_retrieve_response.py b/src/openlayer/types/governance/frameworks/document_retrieve_response.py new file mode 100644 index 00000000..cf22e299 --- /dev/null +++ b/src/openlayer/types/governance/frameworks/document_retrieve_response.py @@ -0,0 +1,206 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ...._models import BaseModel + +__all__ = ["DocumentRetrieveResponse", "Section", "SectionRule", "SectionSubsection", "SectionSubsectionRule"] + + +class SectionRule(BaseModel): + id: str + """The rule id.""" + + name: str + """The rule name.""" + + scope: Literal["project", "workspace"] + """ + Whether the rule is evaluated once for the whole workspace, or once per project + the rule's frameworks apply to. + """ + + type: Literal["platform", "evidence"] + """ + `platform` rules are evaluated automatically from the state of your Openlayer + workspace. `evidence` rules are satisfied by attaching evidence. + """ + + automation_params: Optional[Dict[str, object]] = FieldInfo(alias="automationParams", default=None) + """Configuration for the platform check, when the automation takes parameters.""" + + automation_type: Optional[str] = FieldInfo(alias="automationType", default=None) + """ + Which workspace signal a platform rule checks, for example + `monitoring_mode_enabled`, `test_setup`, or `project_owner_set`. `null` for + evidence rules. + """ + + date_created: Optional[datetime] = FieldInfo(alias="dateCreated", default=None) + """The creation date.""" + + date_updated: Optional[datetime] = FieldInfo(alias="dateUpdated", default=None) + """The last update date.""" + + deactivated: Optional[bool] = None + """Whether the rule is excluded from compliance calculations.""" + + description: Optional[str] = None + """What the rule requires.""" + + evidence_type: Optional[Literal["document", "text", "url", "categoryValue"]] = FieldInfo( + alias="evidenceType", default=None + ) + """The kind of evidence that satisfies the rule. `null` for platform rules.""" + + immutable: Optional[bool] = None + """Whether the rule is managed by Openlayer and cannot be edited.""" + + renewal_cadence_days: Optional[int] = FieldInfo(alias="renewalCadenceDays", default=None) + """How often evidence must be renewed, in days. + + Once evidence is older than this, the rule result becomes `due_soon` and then + `failing`. + """ + + +class SectionSubsectionRule(BaseModel): + id: str + """The rule id.""" + + name: str + """The rule name.""" + + scope: Literal["project", "workspace"] + """ + Whether the rule is evaluated once for the whole workspace, or once per project + the rule's frameworks apply to. + """ + + type: Literal["platform", "evidence"] + """ + `platform` rules are evaluated automatically from the state of your Openlayer + workspace. `evidence` rules are satisfied by attaching evidence. + """ + + automation_params: Optional[Dict[str, object]] = FieldInfo(alias="automationParams", default=None) + """Configuration for the platform check, when the automation takes parameters.""" + + automation_type: Optional[str] = FieldInfo(alias="automationType", default=None) + """ + Which workspace signal a platform rule checks, for example + `monitoring_mode_enabled`, `test_setup`, or `project_owner_set`. `null` for + evidence rules. + """ + + date_created: Optional[datetime] = FieldInfo(alias="dateCreated", default=None) + """The creation date.""" + + date_updated: Optional[datetime] = FieldInfo(alias="dateUpdated", default=None) + """The last update date.""" + + deactivated: Optional[bool] = None + """Whether the rule is excluded from compliance calculations.""" + + description: Optional[str] = None + """What the rule requires.""" + + evidence_type: Optional[Literal["document", "text", "url", "categoryValue"]] = FieldInfo( + alias="evidenceType", default=None + ) + """The kind of evidence that satisfies the rule. `null` for platform rules.""" + + immutable: Optional[bool] = None + """Whether the rule is managed by Openlayer and cannot be edited.""" + + renewal_cadence_days: Optional[int] = FieldInfo(alias="renewalCadenceDays", default=None) + """How often evidence must be renewed, in days. + + Once evidence is older than this, the rule result becomes `due_soon` and then + `failing`. + """ + + +class SectionSubsection(BaseModel): + id: str + """The subsection id.""" + + number: str + """The subsection number as it appears in the source standard.""" + + section_id: str = FieldInfo(alias="sectionId") + """The section the subsection belongs to.""" + + sort_order: int = FieldInfo(alias="sortOrder") + """The position of the subsection within its section.""" + + title: str + """The subsection title.""" + + rule_count: Optional[int] = FieldInfo(alias="ruleCount", default=None) + """How many rules are linked to this subsection.""" + + rules: Optional[List[SectionSubsectionRule]] = None + """The rules linked to this subsection.""" + + text: Optional[str] = None + """The subsection text. This is the requirement your rules are mapped against.""" + + +class Section(BaseModel): + id: str + """The section id.""" + + document_id: str = FieldInfo(alias="documentId") + """The document the section belongs to.""" + + number: str + """The section number as it appears in the source standard.""" + + sort_order: int = FieldInfo(alias="sortOrder") + """The position of the section within the document.""" + + title: str + """The section title.""" + + rule_count: Optional[int] = FieldInfo(alias="ruleCount", default=None) + """How many rules are linked to this section, including its subsections. + + Use it to decide whether to fetch the section's rules. + """ + + rules: Optional[List[SectionRule]] = None + """The rules linked directly to this section.""" + + subsections: Optional[List[SectionSubsection]] = None + """The section's subsections, in display order.""" + + text: Optional[str] = None + """The section text.""" + + +class DocumentRetrieveResponse(BaseModel): + id: str + """The document id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + framework_id: str = FieldInfo(alias="frameworkId") + """The framework the document belongs to.""" + + title: str + """The document title.""" + + sections: Optional[List[Section]] = None + """The document's sections, in display order. + + Only returned when retrieving a single document. + """ diff --git a/src/openlayer/types/governance/frameworks/section_list_rules_params.py b/src/openlayer/types/governance/frameworks/section_list_rules_params.py new file mode 100644 index 00000000..aa310f0c --- /dev/null +++ b/src/openlayer/types/governance/frameworks/section_list_rules_params.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, Annotated, TypedDict + +from ...._utils import PropertyInfo + +__all__ = ["SectionListRulesParams"] + + +class SectionListRulesParams(TypedDict, total=False): + framework_id: Required[Annotated[str, PropertyInfo(alias="frameworkId")]] + + include_results: Annotated[bool, PropertyInfo(alias="includeResults")] + """Whether to include each rule's results inline, in a `results` array.""" + + include_subsection_rules: Annotated[bool, PropertyInfo(alias="includeSubsectionRules")] + """Whether to also include the rules mapped to the section's subsections.""" + + page: int + """The page to return in a paginated query.""" + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Maximum number of items to return per page.""" + + project_id: Annotated[str, PropertyInfo(alias="projectId")] + """Only include items that apply to this project.""" + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """Only include items whose rule result has this compliance status.""" diff --git a/src/openlayer/types/governance/frameworks/section_list_rules_response.py b/src/openlayer/types/governance/frameworks/section_list_rules_response.py new file mode 100644 index 00000000..75b829b5 --- /dev/null +++ b/src/openlayer/types/governance/frameworks/section_list_rules_response.py @@ -0,0 +1,242 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ...._models import BaseModel + +__all__ = [ + "SectionListRulesResponse", + "Item", + "ItemFramework", + "ItemFrameworkAvatar", + "ItemResult", + "ItemResultBlockedBy", + "ItemResultBlocking", + "ItemResultsSummary", + "ItemTag", +] + + +class ItemFrameworkAvatar(BaseModel): + """The icon shown for the framework.""" + + type: Literal["emoji", "imageUrl", "builtinImage"] + + value: str + + +class ItemFramework(BaseModel): + id: str + """The framework id.""" + + avatar: Optional[ItemFrameworkAvatar] = None + """The icon shown for the framework.""" + + built_in_slug: Optional[str] = FieldInfo(alias="builtInSlug", default=None) + """ + Identifies a framework that ships with Openlayer, for example `eu_ai_act`, + `iso_42001`, `nist_ai_rmf`, or `traiga`. `null` for frameworks you create + yourself. + """ + + enabled: bool + """Whether the framework is active. + + Rules of a disabled framework are not evaluated and do not count towards + compliance. + """ + + name: str + """The framework name.""" + + +class ItemResultBlockedBy(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class ItemResultBlocking(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class ItemResult(BaseModel): + id: str + """The rule result id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + deactivated: bool + """Whether this result is excluded from compliance calculations.""" + + rule_id: str = FieldInfo(alias="ruleId") + """The rule this result belongs to.""" + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """The compliance status of the rule for this entity.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule result belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for this result.""" + + blocked_by: Optional[List[ItemResultBlockedBy]] = FieldInfo(alias="blockedBy", default=None) + """Rule results that must pass before this one can be satisfied.""" + + blocking: Optional[List[ItemResultBlocking]] = None + """Rule results that this one blocks.""" + + date_last_evaluated: Optional[datetime] = FieldInfo(alias="dateLastEvaluated", default=None) + """When the rule was last evaluated. Platform rules only.""" + + date_of_latest_evidence: Optional[datetime] = FieldInfo(alias="dateOfLatestEvidence", default=None) + """When the most recent piece of evidence was attached. Evidence rules only.""" + + date_of_next_evaluation: Optional[datetime] = FieldInfo(alias="dateOfNextEvaluation", default=None) + """When the rule will next be evaluated. Platform rules only.""" + + date_of_renewal: Optional[datetime] = FieldInfo(alias="dateOfRenewal", default=None) + """When the evidence must be renewed. Evidence rules with a renewal cadence only.""" + + deactivated_reason: Optional[str] = FieldInfo(alias="deactivatedReason", default=None) + """Why the result was excluded.""" + + project_id: Optional[str] = FieldInfo(alias="projectId", default=None) + """The project this result was evaluated for. `null` for workspace-scoped rules.""" + + status_message: Optional[str] = FieldInfo(alias="statusMessage", default=None) + """A human-readable explanation of the status.""" + + +class ItemResultsSummary(BaseModel): + """ + Pass-rate counts across all of the rule's entities, independent of any status filter applied to the request. + """ + + passing: Optional[int] = None + + total: Optional[int] = None + + +class ItemTag(BaseModel): + id: str + """The rule tag id.""" + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The user who created the tag. `null` for tags that ship with Openlayer.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + immutable: bool + """Whether the tag is managed by Openlayer and cannot be edited or deleted.""" + + name: str + """The tag name.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the tag belongs to.""" + + color: Optional[str] = None + """The color the tag is displayed with.""" + + +class Item(BaseModel): + id: str + """The rule id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + name: str + """The rule name.""" + + scope: Literal["project", "workspace"] + """ + Whether the rule is evaluated once for the whole workspace, or once per project + the rule's frameworks apply to. + """ + + type: Literal["platform", "evidence"] + """ + `platform` rules are evaluated automatically from the state of your Openlayer + workspace. `evidence` rules are satisfied by attaching evidence. + """ + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for satisfying the rule.""" + + automation_params: Optional[Dict[str, object]] = FieldInfo(alias="automationParams", default=None) + """Configuration for the platform check, when the automation takes parameters.""" + + automation_type: Optional[str] = FieldInfo(alias="automationType", default=None) + """ + Which workspace signal a platform rule checks, for example + `monitoring_mode_enabled`, `test_setup`, or `project_owner_set`. `null` for + evidence rules. + """ + + deactivated: Optional[bool] = None + """Whether the rule is excluded from compliance calculations.""" + + description: Optional[str] = None + """What the rule requires.""" + + evidence_type: Optional[Literal["document", "text", "url", "categoryValue"]] = FieldInfo( + alias="evidenceType", default=None + ) + """The kind of evidence that satisfies the rule. `null` for platform rules.""" + + frameworks: Optional[List[ItemFramework]] = None + """The frameworks that include this rule.""" + + immutable: Optional[bool] = None + """Whether the rule is managed by Openlayer and cannot be edited.""" + + renewal_cadence_days: Optional[int] = FieldInfo(alias="renewalCadenceDays", default=None) + """How often evidence must be renewed, in days. + + Once evidence is older than this, the rule result becomes `due_soon` and then + `failing`. + """ + + results: Optional[List[ItemResult]] = None + """The rule's results, one per entity the rule is evaluated against. + + Only returned when `includeResults` is `true`. + """ + + results_summary: Optional[ItemResultsSummary] = FieldInfo(alias="resultsSummary", default=None) + """ + Pass-rate counts across all of the rule's entities, independent of any status + filter applied to the request. + """ + + tags: Optional[List[ItemTag]] = None + """The rule tags associated with the rule.""" + + +class SectionListRulesResponse(BaseModel): + items: List[Item] diff --git a/src/openlayer/types/governance/frameworks/subsection_list_rules_params.py b/src/openlayer/types/governance/frameworks/subsection_list_rules_params.py new file mode 100644 index 00000000..7d88425c --- /dev/null +++ b/src/openlayer/types/governance/frameworks/subsection_list_rules_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, Annotated, TypedDict + +from ...._utils import PropertyInfo + +__all__ = ["SubsectionListRulesParams"] + + +class SubsectionListRulesParams(TypedDict, total=False): + framework_id: Required[Annotated[str, PropertyInfo(alias="frameworkId")]] + + include_results: Annotated[bool, PropertyInfo(alias="includeResults")] + """Whether to include each rule's results inline, in a `results` array.""" + + page: int + """The page to return in a paginated query.""" + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Maximum number of items to return per page.""" + + project_id: Annotated[str, PropertyInfo(alias="projectId")] + """Only include items that apply to this project.""" + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """Only include items whose rule result has this compliance status.""" diff --git a/src/openlayer/types/governance/frameworks/subsection_list_rules_response.py b/src/openlayer/types/governance/frameworks/subsection_list_rules_response.py new file mode 100644 index 00000000..1b934a4b --- /dev/null +++ b/src/openlayer/types/governance/frameworks/subsection_list_rules_response.py @@ -0,0 +1,242 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ...._models import BaseModel + +__all__ = [ + "SubsectionListRulesResponse", + "Item", + "ItemFramework", + "ItemFrameworkAvatar", + "ItemResult", + "ItemResultBlockedBy", + "ItemResultBlocking", + "ItemResultsSummary", + "ItemTag", +] + + +class ItemFrameworkAvatar(BaseModel): + """The icon shown for the framework.""" + + type: Literal["emoji", "imageUrl", "builtinImage"] + + value: str + + +class ItemFramework(BaseModel): + id: str + """The framework id.""" + + avatar: Optional[ItemFrameworkAvatar] = None + """The icon shown for the framework.""" + + built_in_slug: Optional[str] = FieldInfo(alias="builtInSlug", default=None) + """ + Identifies a framework that ships with Openlayer, for example `eu_ai_act`, + `iso_42001`, `nist_ai_rmf`, or `traiga`. `null` for frameworks you create + yourself. + """ + + enabled: bool + """Whether the framework is active. + + Rules of a disabled framework are not evaluated and do not count towards + compliance. + """ + + name: str + """The framework name.""" + + +class ItemResultBlockedBy(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class ItemResultBlocking(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class ItemResult(BaseModel): + id: str + """The rule result id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + deactivated: bool + """Whether this result is excluded from compliance calculations.""" + + rule_id: str = FieldInfo(alias="ruleId") + """The rule this result belongs to.""" + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """The compliance status of the rule for this entity.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule result belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for this result.""" + + blocked_by: Optional[List[ItemResultBlockedBy]] = FieldInfo(alias="blockedBy", default=None) + """Rule results that must pass before this one can be satisfied.""" + + blocking: Optional[List[ItemResultBlocking]] = None + """Rule results that this one blocks.""" + + date_last_evaluated: Optional[datetime] = FieldInfo(alias="dateLastEvaluated", default=None) + """When the rule was last evaluated. Platform rules only.""" + + date_of_latest_evidence: Optional[datetime] = FieldInfo(alias="dateOfLatestEvidence", default=None) + """When the most recent piece of evidence was attached. Evidence rules only.""" + + date_of_next_evaluation: Optional[datetime] = FieldInfo(alias="dateOfNextEvaluation", default=None) + """When the rule will next be evaluated. Platform rules only.""" + + date_of_renewal: Optional[datetime] = FieldInfo(alias="dateOfRenewal", default=None) + """When the evidence must be renewed. Evidence rules with a renewal cadence only.""" + + deactivated_reason: Optional[str] = FieldInfo(alias="deactivatedReason", default=None) + """Why the result was excluded.""" + + project_id: Optional[str] = FieldInfo(alias="projectId", default=None) + """The project this result was evaluated for. `null` for workspace-scoped rules.""" + + status_message: Optional[str] = FieldInfo(alias="statusMessage", default=None) + """A human-readable explanation of the status.""" + + +class ItemResultsSummary(BaseModel): + """ + Pass-rate counts across all of the rule's entities, independent of any status filter applied to the request. + """ + + passing: Optional[int] = None + + total: Optional[int] = None + + +class ItemTag(BaseModel): + id: str + """The rule tag id.""" + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The user who created the tag. `null` for tags that ship with Openlayer.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + immutable: bool + """Whether the tag is managed by Openlayer and cannot be edited or deleted.""" + + name: str + """The tag name.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the tag belongs to.""" + + color: Optional[str] = None + """The color the tag is displayed with.""" + + +class Item(BaseModel): + id: str + """The rule id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + name: str + """The rule name.""" + + scope: Literal["project", "workspace"] + """ + Whether the rule is evaluated once for the whole workspace, or once per project + the rule's frameworks apply to. + """ + + type: Literal["platform", "evidence"] + """ + `platform` rules are evaluated automatically from the state of your Openlayer + workspace. `evidence` rules are satisfied by attaching evidence. + """ + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for satisfying the rule.""" + + automation_params: Optional[Dict[str, object]] = FieldInfo(alias="automationParams", default=None) + """Configuration for the platform check, when the automation takes parameters.""" + + automation_type: Optional[str] = FieldInfo(alias="automationType", default=None) + """ + Which workspace signal a platform rule checks, for example + `monitoring_mode_enabled`, `test_setup`, or `project_owner_set`. `null` for + evidence rules. + """ + + deactivated: Optional[bool] = None + """Whether the rule is excluded from compliance calculations.""" + + description: Optional[str] = None + """What the rule requires.""" + + evidence_type: Optional[Literal["document", "text", "url", "categoryValue"]] = FieldInfo( + alias="evidenceType", default=None + ) + """The kind of evidence that satisfies the rule. `null` for platform rules.""" + + frameworks: Optional[List[ItemFramework]] = None + """The frameworks that include this rule.""" + + immutable: Optional[bool] = None + """Whether the rule is managed by Openlayer and cannot be edited.""" + + renewal_cadence_days: Optional[int] = FieldInfo(alias="renewalCadenceDays", default=None) + """How often evidence must be renewed, in days. + + Once evidence is older than this, the rule result becomes `due_soon` and then + `failing`. + """ + + results: Optional[List[ItemResult]] = None + """The rule's results, one per entity the rule is evaluated against. + + Only returned when `includeResults` is `true`. + """ + + results_summary: Optional[ItemResultsSummary] = FieldInfo(alias="resultsSummary", default=None) + """ + Pass-rate counts across all of the rule's entities, independent of any status + filter applied to the request. + """ + + tags: Optional[List[ItemTag]] = None + """The rule tags associated with the rule.""" + + +class SubsectionListRulesResponse(BaseModel): + items: List[Item] diff --git a/src/openlayer/types/governance/rule_create_params.py b/src/openlayer/types/governance/rule_create_params.py new file mode 100644 index 00000000..bb660fe7 --- /dev/null +++ b/src/openlayer/types/governance/rule_create_params.py @@ -0,0 +1,66 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Optional +from typing_extensions import Literal, Required, Annotated, TypedDict + +from ..._types import SequenceNotStr +from ..._utils import PropertyInfo + +__all__ = ["RuleCreateParams"] + + +class RuleCreateParams(TypedDict, total=False): + name: Required[str] + """The rule name.""" + + scope: Required[Literal["project", "workspace"]] + """ + Whether the rule is evaluated once for the whole workspace, or once per project + the rule's frameworks apply to. + """ + + type: Required[Literal["platform", "evidence"]] + """ + `platform` rules are evaluated automatically from the state of your Openlayer + workspace. `evidence` rules are satisfied by attaching evidence. + """ + + assignee_id: Annotated[Optional[str], PropertyInfo(alias="assigneeId")] + """The user responsible for satisfying the rule.""" + + automation_params: Annotated[Optional[Dict[str, object]], PropertyInfo(alias="automationParams")] + """Configuration for the platform check, when the automation takes parameters.""" + + automation_type: Annotated[Optional[str], PropertyInfo(alias="automationType")] + """ + Which workspace signal a platform rule checks, for example + `monitoring_mode_enabled`, `test_setup`, or `project_owner_set`. `null` for + evidence rules. + """ + + deactivated: bool + """Whether the rule is excluded from compliance calculations.""" + + description: Optional[str] + """What the rule requires.""" + + evidence_type: Annotated[ + Optional[Literal["document", "text", "url", "categoryValue"]], PropertyInfo(alias="evidenceType") + ] + """The kind of evidence that satisfies the rule. `null` for platform rules.""" + + renewal_cadence_days: Annotated[Optional[int], PropertyInfo(alias="renewalCadenceDays")] + """How often evidence must be renewed, in days. + + Once evidence is older than this, the rule result becomes `due_soon` and then + `failing`. + """ + + tag_ids: Annotated[Optional[SequenceNotStr[str]], PropertyInfo(alias="tagIds")] + """The ids of the rule tags to associate with the rule. + + Replaces the rule's tags. Read them back from `tags`, and list the tags + available in the workspace with `GET /workspaces/{workspaceId}/rule-tags`. + """ diff --git a/src/openlayer/types/governance/rule_create_response.py b/src/openlayer/types/governance/rule_create_response.py new file mode 100644 index 00000000..3f2475dd --- /dev/null +++ b/src/openlayer/types/governance/rule_create_response.py @@ -0,0 +1,237 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = [ + "RuleCreateResponse", + "Framework", + "FrameworkAvatar", + "Result", + "ResultBlockedBy", + "ResultBlocking", + "ResultsSummary", + "Tag", +] + + +class FrameworkAvatar(BaseModel): + """The icon shown for the framework.""" + + type: Literal["emoji", "imageUrl", "builtinImage"] + + value: str + + +class Framework(BaseModel): + id: str + """The framework id.""" + + avatar: Optional[FrameworkAvatar] = None + """The icon shown for the framework.""" + + built_in_slug: Optional[str] = FieldInfo(alias="builtInSlug", default=None) + """ + Identifies a framework that ships with Openlayer, for example `eu_ai_act`, + `iso_42001`, `nist_ai_rmf`, or `traiga`. `null` for frameworks you create + yourself. + """ + + enabled: bool + """Whether the framework is active. + + Rules of a disabled framework are not evaluated and do not count towards + compliance. + """ + + name: str + """The framework name.""" + + +class ResultBlockedBy(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class ResultBlocking(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class Result(BaseModel): + id: str + """The rule result id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + deactivated: bool + """Whether this result is excluded from compliance calculations.""" + + rule_id: str = FieldInfo(alias="ruleId") + """The rule this result belongs to.""" + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """The compliance status of the rule for this entity.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule result belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for this result.""" + + blocked_by: Optional[List[ResultBlockedBy]] = FieldInfo(alias="blockedBy", default=None) + """Rule results that must pass before this one can be satisfied.""" + + blocking: Optional[List[ResultBlocking]] = None + """Rule results that this one blocks.""" + + date_last_evaluated: Optional[datetime] = FieldInfo(alias="dateLastEvaluated", default=None) + """When the rule was last evaluated. Platform rules only.""" + + date_of_latest_evidence: Optional[datetime] = FieldInfo(alias="dateOfLatestEvidence", default=None) + """When the most recent piece of evidence was attached. Evidence rules only.""" + + date_of_next_evaluation: Optional[datetime] = FieldInfo(alias="dateOfNextEvaluation", default=None) + """When the rule will next be evaluated. Platform rules only.""" + + date_of_renewal: Optional[datetime] = FieldInfo(alias="dateOfRenewal", default=None) + """When the evidence must be renewed. Evidence rules with a renewal cadence only.""" + + deactivated_reason: Optional[str] = FieldInfo(alias="deactivatedReason", default=None) + """Why the result was excluded.""" + + project_id: Optional[str] = FieldInfo(alias="projectId", default=None) + """The project this result was evaluated for. `null` for workspace-scoped rules.""" + + status_message: Optional[str] = FieldInfo(alias="statusMessage", default=None) + """A human-readable explanation of the status.""" + + +class ResultsSummary(BaseModel): + """ + Pass-rate counts across all of the rule's entities, independent of any status filter applied to the request. + """ + + passing: Optional[int] = None + + total: Optional[int] = None + + +class Tag(BaseModel): + id: str + """The rule tag id.""" + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The user who created the tag. `null` for tags that ship with Openlayer.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + immutable: bool + """Whether the tag is managed by Openlayer and cannot be edited or deleted.""" + + name: str + """The tag name.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the tag belongs to.""" + + color: Optional[str] = None + """The color the tag is displayed with.""" + + +class RuleCreateResponse(BaseModel): + id: str + """The rule id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + name: str + """The rule name.""" + + scope: Literal["project", "workspace"] + """ + Whether the rule is evaluated once for the whole workspace, or once per project + the rule's frameworks apply to. + """ + + type: Literal["platform", "evidence"] + """ + `platform` rules are evaluated automatically from the state of your Openlayer + workspace. `evidence` rules are satisfied by attaching evidence. + """ + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for satisfying the rule.""" + + automation_params: Optional[Dict[str, object]] = FieldInfo(alias="automationParams", default=None) + """Configuration for the platform check, when the automation takes parameters.""" + + automation_type: Optional[str] = FieldInfo(alias="automationType", default=None) + """ + Which workspace signal a platform rule checks, for example + `monitoring_mode_enabled`, `test_setup`, or `project_owner_set`. `null` for + evidence rules. + """ + + deactivated: Optional[bool] = None + """Whether the rule is excluded from compliance calculations.""" + + description: Optional[str] = None + """What the rule requires.""" + + evidence_type: Optional[Literal["document", "text", "url", "categoryValue"]] = FieldInfo( + alias="evidenceType", default=None + ) + """The kind of evidence that satisfies the rule. `null` for platform rules.""" + + frameworks: Optional[List[Framework]] = None + """The frameworks that include this rule.""" + + immutable: Optional[bool] = None + """Whether the rule is managed by Openlayer and cannot be edited.""" + + renewal_cadence_days: Optional[int] = FieldInfo(alias="renewalCadenceDays", default=None) + """How often evidence must be renewed, in days. + + Once evidence is older than this, the rule result becomes `due_soon` and then + `failing`. + """ + + results: Optional[List[Result]] = None + """The rule's results, one per entity the rule is evaluated against. + + Only returned when `includeResults` is `true`. + """ + + results_summary: Optional[ResultsSummary] = FieldInfo(alias="resultsSummary", default=None) + """ + Pass-rate counts across all of the rule's entities, independent of any status + filter applied to the request. + """ + + tags: Optional[List[Tag]] = None + """The rule tags associated with the rule.""" diff --git a/src/openlayer/types/governance/rule_list_params.py b/src/openlayer/types/governance/rule_list_params.py new file mode 100644 index 00000000..85d82c8c --- /dev/null +++ b/src/openlayer/types/governance/rule_list_params.py @@ -0,0 +1,71 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Annotated, TypedDict + +from ..._types import SequenceNotStr +from ..._utils import PropertyInfo + +__all__ = ["RuleListParams"] + + +class RuleListParams(TypedDict, total=False): + asc: bool + """Whether to sort in ascending order.""" + + assignee_id: Annotated[str, PropertyInfo(alias="assigneeId")] + """Only include rules assigned to this user.""" + + deactivated: bool + """Only include rules that are deactivated (or active).""" + + enabled_framework_only: Annotated[bool, PropertyInfo(alias="enabledFrameworkOnly")] + """Only include items belonging to at least one enabled framework.""" + + framework_id: Annotated[str, PropertyInfo(alias="frameworkId")] + """Only include items belonging to this framework.""" + + group: Literal["open", "excluded", "done"] + """Only include rules in one bucket of the compliance workflow. + + `open` covers rules that still need attention, `done` covers rules that are + fully satisfied, and `excluded` covers rules that have been deactivated. + """ + + include_results: Annotated[bool, PropertyInfo(alias="includeResults")] + """Whether to include each rule's results inline, in a `results` array.""" + + include_unframed: Annotated[bool, PropertyInfo(alias="includeUnframed")] + """Whether to include rules that are not part of any framework.""" + + page: int + """The page to return in a paginated query.""" + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Maximum number of items to return per page.""" + + project_id: Annotated[str, PropertyInfo(alias="projectId")] + """Only include items that apply to this project.""" + + scope: Literal["project", "workspace"] + """Only include rules with this scope.""" + + search_query: Annotated[str, PropertyInfo(alias="searchQuery")] + """Filter by a free-text search over names and descriptions.""" + + sort_by: Annotated[Literal["name", "status", "frameworks", "scope", "dateCreated"], PropertyInfo(alias="sortBy")] + """The field to sort on.""" + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """Only include items whose rule result has this compliance status.""" + + tags: SequenceNotStr[str] + """Only include rules carrying all of these rule tags. + + Pass tag ids, which you can look up with + [List rule tags](/api-reference/rest/governance/list-rule-tags). + """ + + type: Literal["platform", "evidence"] + """Only include rules of this type.""" diff --git a/src/openlayer/types/governance/rule_list_response.py b/src/openlayer/types/governance/rule_list_response.py new file mode 100644 index 00000000..582fb42a --- /dev/null +++ b/src/openlayer/types/governance/rule_list_response.py @@ -0,0 +1,242 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = [ + "RuleListResponse", + "Item", + "ItemFramework", + "ItemFrameworkAvatar", + "ItemResult", + "ItemResultBlockedBy", + "ItemResultBlocking", + "ItemResultsSummary", + "ItemTag", +] + + +class ItemFrameworkAvatar(BaseModel): + """The icon shown for the framework.""" + + type: Literal["emoji", "imageUrl", "builtinImage"] + + value: str + + +class ItemFramework(BaseModel): + id: str + """The framework id.""" + + avatar: Optional[ItemFrameworkAvatar] = None + """The icon shown for the framework.""" + + built_in_slug: Optional[str] = FieldInfo(alias="builtInSlug", default=None) + """ + Identifies a framework that ships with Openlayer, for example `eu_ai_act`, + `iso_42001`, `nist_ai_rmf`, or `traiga`. `null` for frameworks you create + yourself. + """ + + enabled: bool + """Whether the framework is active. + + Rules of a disabled framework are not evaluated and do not count towards + compliance. + """ + + name: str + """The framework name.""" + + +class ItemResultBlockedBy(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class ItemResultBlocking(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class ItemResult(BaseModel): + id: str + """The rule result id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + deactivated: bool + """Whether this result is excluded from compliance calculations.""" + + rule_id: str = FieldInfo(alias="ruleId") + """The rule this result belongs to.""" + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """The compliance status of the rule for this entity.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule result belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for this result.""" + + blocked_by: Optional[List[ItemResultBlockedBy]] = FieldInfo(alias="blockedBy", default=None) + """Rule results that must pass before this one can be satisfied.""" + + blocking: Optional[List[ItemResultBlocking]] = None + """Rule results that this one blocks.""" + + date_last_evaluated: Optional[datetime] = FieldInfo(alias="dateLastEvaluated", default=None) + """When the rule was last evaluated. Platform rules only.""" + + date_of_latest_evidence: Optional[datetime] = FieldInfo(alias="dateOfLatestEvidence", default=None) + """When the most recent piece of evidence was attached. Evidence rules only.""" + + date_of_next_evaluation: Optional[datetime] = FieldInfo(alias="dateOfNextEvaluation", default=None) + """When the rule will next be evaluated. Platform rules only.""" + + date_of_renewal: Optional[datetime] = FieldInfo(alias="dateOfRenewal", default=None) + """When the evidence must be renewed. Evidence rules with a renewal cadence only.""" + + deactivated_reason: Optional[str] = FieldInfo(alias="deactivatedReason", default=None) + """Why the result was excluded.""" + + project_id: Optional[str] = FieldInfo(alias="projectId", default=None) + """The project this result was evaluated for. `null` for workspace-scoped rules.""" + + status_message: Optional[str] = FieldInfo(alias="statusMessage", default=None) + """A human-readable explanation of the status.""" + + +class ItemResultsSummary(BaseModel): + """ + Pass-rate counts across all of the rule's entities, independent of any status filter applied to the request. + """ + + passing: Optional[int] = None + + total: Optional[int] = None + + +class ItemTag(BaseModel): + id: str + """The rule tag id.""" + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The user who created the tag. `null` for tags that ship with Openlayer.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + immutable: bool + """Whether the tag is managed by Openlayer and cannot be edited or deleted.""" + + name: str + """The tag name.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the tag belongs to.""" + + color: Optional[str] = None + """The color the tag is displayed with.""" + + +class Item(BaseModel): + id: str + """The rule id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + name: str + """The rule name.""" + + scope: Literal["project", "workspace"] + """ + Whether the rule is evaluated once for the whole workspace, or once per project + the rule's frameworks apply to. + """ + + type: Literal["platform", "evidence"] + """ + `platform` rules are evaluated automatically from the state of your Openlayer + workspace. `evidence` rules are satisfied by attaching evidence. + """ + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for satisfying the rule.""" + + automation_params: Optional[Dict[str, object]] = FieldInfo(alias="automationParams", default=None) + """Configuration for the platform check, when the automation takes parameters.""" + + automation_type: Optional[str] = FieldInfo(alias="automationType", default=None) + """ + Which workspace signal a platform rule checks, for example + `monitoring_mode_enabled`, `test_setup`, or `project_owner_set`. `null` for + evidence rules. + """ + + deactivated: Optional[bool] = None + """Whether the rule is excluded from compliance calculations.""" + + description: Optional[str] = None + """What the rule requires.""" + + evidence_type: Optional[Literal["document", "text", "url", "categoryValue"]] = FieldInfo( + alias="evidenceType", default=None + ) + """The kind of evidence that satisfies the rule. `null` for platform rules.""" + + frameworks: Optional[List[ItemFramework]] = None + """The frameworks that include this rule.""" + + immutable: Optional[bool] = None + """Whether the rule is managed by Openlayer and cannot be edited.""" + + renewal_cadence_days: Optional[int] = FieldInfo(alias="renewalCadenceDays", default=None) + """How often evidence must be renewed, in days. + + Once evidence is older than this, the rule result becomes `due_soon` and then + `failing`. + """ + + results: Optional[List[ItemResult]] = None + """The rule's results, one per entity the rule is evaluated against. + + Only returned when `includeResults` is `true`. + """ + + results_summary: Optional[ItemResultsSummary] = FieldInfo(alias="resultsSummary", default=None) + """ + Pass-rate counts across all of the rule's entities, independent of any status + filter applied to the request. + """ + + tags: Optional[List[ItemTag]] = None + """The rule tags associated with the rule.""" + + +class RuleListResponse(BaseModel): + items: List[Item] diff --git a/src/openlayer/types/governance/rule_result_create_evidence_params.py b/src/openlayer/types/governance/rule_result_create_evidence_params.py new file mode 100644 index 00000000..a049e054 --- /dev/null +++ b/src/openlayer/types/governance/rule_result_create_evidence_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["RuleResultCreateEvidenceParams"] + + +class RuleResultCreateEvidenceParams(TypedDict, total=False): + description: Optional[str] + """A description of what the evidence shows.""" + + name: Optional[str] + """The evidence name.""" + + storage_uri: Annotated[Optional[str], PropertyInfo(alias="storageUri")] + """Where the uploaded file is stored. + + Set when the rule's `evidenceType` is `document`. + """ + + text: Optional[str] + """The evidence text. Set when the rule's `evidenceType` is `text`.""" + + url: Optional[str] + """A link to the evidence. Set when the rule's `evidenceType` is `url`.""" diff --git a/src/openlayer/types/governance/rule_result_create_evidence_response.py b/src/openlayer/types/governance/rule_result_create_evidence_response.py new file mode 100644 index 00000000..92e21736 --- /dev/null +++ b/src/openlayer/types/governance/rule_result_create_evidence_response.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["RuleResultCreateEvidenceResponse"] + + +class RuleResultCreateEvidenceResponse(BaseModel): + id: str + """The evidence id.""" + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The user who attached the evidence.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + description: Optional[str] = None + """A description of what the evidence shows.""" + + name: Optional[str] = None + """The evidence name.""" + + storage_uri: Optional[str] = FieldInfo(alias="storageUri", default=None) + """Where the uploaded file is stored. + + Set when the rule's `evidenceType` is `document`. + """ + + text: Optional[str] = None + """The evidence text. Set when the rule's `evidenceType` is `text`.""" + + url: Optional[str] = None + """A link to the evidence. Set when the rule's `evidenceType` is `url`.""" diff --git a/src/openlayer/types/governance/rule_result_list_evidence_params.py b/src/openlayer/types/governance/rule_result_list_evidence_params.py new file mode 100644 index 00000000..cef9b326 --- /dev/null +++ b/src/openlayer/types/governance/rule_result_list_evidence_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["RuleResultListEvidenceParams"] + + +class RuleResultListEvidenceParams(TypedDict, total=False): + page: int + """The page to return in a paginated query.""" + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Maximum number of items to return per page.""" diff --git a/src/openlayer/types/governance/rule_result_list_evidence_response.py b/src/openlayer/types/governance/rule_result_list_evidence_response.py new file mode 100644 index 00000000..6e45b19e --- /dev/null +++ b/src/openlayer/types/governance/rule_result_list_evidence_response.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["RuleResultListEvidenceResponse", "Item"] + + +class Item(BaseModel): + id: str + """The evidence id.""" + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The user who attached the evidence.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + description: Optional[str] = None + """A description of what the evidence shows.""" + + name: Optional[str] = None + """The evidence name.""" + + storage_uri: Optional[str] = FieldInfo(alias="storageUri", default=None) + """Where the uploaded file is stored. + + Set when the rule's `evidenceType` is `document`. + """ + + text: Optional[str] = None + """The evidence text. Set when the rule's `evidenceType` is `text`.""" + + url: Optional[str] = None + """A link to the evidence. Set when the rule's `evidenceType` is `url`.""" + + +class RuleResultListEvidenceResponse(BaseModel): + items: List[Item] diff --git a/src/openlayer/types/governance/rule_result_list_params.py b/src/openlayer/types/governance/rule_result_list_params.py new file mode 100644 index 00000000..7ddf514e --- /dev/null +++ b/src/openlayer/types/governance/rule_result_list_params.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["RuleResultListParams"] + + +class RuleResultListParams(TypedDict, total=False): + enabled_framework_only: Annotated[bool, PropertyInfo(alias="enabledFrameworkOnly")] + """Only include items belonging to at least one enabled framework.""" + + framework_id: Annotated[str, PropertyInfo(alias="frameworkId")] + """Only include items belonging to this framework.""" + + include_unframed: Annotated[bool, PropertyInfo(alias="includeUnframed")] + """Whether to include rules that are not part of any framework.""" + + page: int + """The page to return in a paginated query.""" + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Maximum number of items to return per page.""" + + project_id: Annotated[str, PropertyInfo(alias="projectId")] + """Only include items that apply to this project.""" + + rule_id: Annotated[str, PropertyInfo(alias="ruleId")] + """Only include results of this rule.""" + + scope: Literal["project", "workspace"] + """Only include rules with this scope.""" + + search_query: Annotated[str, PropertyInfo(alias="searchQuery")] + """Filter by a free-text search over names and descriptions.""" + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """Only include items whose rule result has this compliance status.""" + + type: Literal["platform", "evidence"] + """Only include rules of this type.""" diff --git a/src/openlayer/types/governance/rule_result_list_response.py b/src/openlayer/types/governance/rule_result_list_response.py new file mode 100644 index 00000000..639e7b66 --- /dev/null +++ b/src/openlayer/types/governance/rule_result_list_response.py @@ -0,0 +1,82 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["RuleResultListResponse", "Item", "ItemBlockedBy", "ItemBlocking"] + + +class ItemBlockedBy(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class ItemBlocking(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class Item(BaseModel): + id: str + """The rule result id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + deactivated: bool + """Whether this result is excluded from compliance calculations.""" + + rule_id: str = FieldInfo(alias="ruleId") + """The rule this result belongs to.""" + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """The compliance status of the rule for this entity.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule result belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for this result.""" + + blocked_by: Optional[List[ItemBlockedBy]] = FieldInfo(alias="blockedBy", default=None) + """Rule results that must pass before this one can be satisfied.""" + + blocking: Optional[List[ItemBlocking]] = None + """Rule results that this one blocks.""" + + date_last_evaluated: Optional[datetime] = FieldInfo(alias="dateLastEvaluated", default=None) + """When the rule was last evaluated. Platform rules only.""" + + date_of_latest_evidence: Optional[datetime] = FieldInfo(alias="dateOfLatestEvidence", default=None) + """When the most recent piece of evidence was attached. Evidence rules only.""" + + date_of_next_evaluation: Optional[datetime] = FieldInfo(alias="dateOfNextEvaluation", default=None) + """When the rule will next be evaluated. Platform rules only.""" + + date_of_renewal: Optional[datetime] = FieldInfo(alias="dateOfRenewal", default=None) + """When the evidence must be renewed. Evidence rules with a renewal cadence only.""" + + deactivated_reason: Optional[str] = FieldInfo(alias="deactivatedReason", default=None) + """Why the result was excluded.""" + + project_id: Optional[str] = FieldInfo(alias="projectId", default=None) + """The project this result was evaluated for. `null` for workspace-scoped rules.""" + + status_message: Optional[str] = FieldInfo(alias="statusMessage", default=None) + """A human-readable explanation of the status.""" + + +class RuleResultListResponse(BaseModel): + items: List[Item] diff --git a/src/openlayer/types/governance/rule_result_retrieve_response.py b/src/openlayer/types/governance/rule_result_retrieve_response.py new file mode 100644 index 00000000..b7f4db18 --- /dev/null +++ b/src/openlayer/types/governance/rule_result_retrieve_response.py @@ -0,0 +1,78 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["RuleResultRetrieveResponse", "BlockedBy", "Blocking"] + + +class BlockedBy(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class Blocking(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class RuleResultRetrieveResponse(BaseModel): + id: str + """The rule result id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + deactivated: bool + """Whether this result is excluded from compliance calculations.""" + + rule_id: str = FieldInfo(alias="ruleId") + """The rule this result belongs to.""" + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """The compliance status of the rule for this entity.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule result belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for this result.""" + + blocked_by: Optional[List[BlockedBy]] = FieldInfo(alias="blockedBy", default=None) + """Rule results that must pass before this one can be satisfied.""" + + blocking: Optional[List[Blocking]] = None + """Rule results that this one blocks.""" + + date_last_evaluated: Optional[datetime] = FieldInfo(alias="dateLastEvaluated", default=None) + """When the rule was last evaluated. Platform rules only.""" + + date_of_latest_evidence: Optional[datetime] = FieldInfo(alias="dateOfLatestEvidence", default=None) + """When the most recent piece of evidence was attached. Evidence rules only.""" + + date_of_next_evaluation: Optional[datetime] = FieldInfo(alias="dateOfNextEvaluation", default=None) + """When the rule will next be evaluated. Platform rules only.""" + + date_of_renewal: Optional[datetime] = FieldInfo(alias="dateOfRenewal", default=None) + """When the evidence must be renewed. Evidence rules with a renewal cadence only.""" + + deactivated_reason: Optional[str] = FieldInfo(alias="deactivatedReason", default=None) + """Why the result was excluded.""" + + project_id: Optional[str] = FieldInfo(alias="projectId", default=None) + """The project this result was evaluated for. `null` for workspace-scoped rules.""" + + status_message: Optional[str] = FieldInfo(alias="statusMessage", default=None) + """A human-readable explanation of the status.""" diff --git a/src/openlayer/types/governance/rule_result_update_params.py b/src/openlayer/types/governance/rule_result_update_params.py new file mode 100644 index 00000000..6c6ed1a7 --- /dev/null +++ b/src/openlayer/types/governance/rule_result_update_params.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["RuleResultUpdateParams", "BlockedBy", "Blocking"] + + +class RuleResultUpdateParams(TypedDict, total=False): + assignee_id: Annotated[Optional[str], PropertyInfo(alias="assigneeId")] + """The user responsible for this result.""" + + blocked_by: Annotated[Iterable[BlockedBy], PropertyInfo(alias="blockedBy")] + """Rule results that must pass before this one can be satisfied.""" + + blocking: Iterable[Blocking] + """Rule results that this one blocks.""" + + deactivated: bool + """Whether this result is excluded from compliance calculations.""" + + deactivated_reason: Annotated[Optional[str], PropertyInfo(alias="deactivatedReason")] + """Why the result was excluded.""" + + +class BlockedBy(TypedDict, total=False): + id: str + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """The compliance status of the rule for this entity.""" + + +class Blocking(TypedDict, total=False): + id: str + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """The compliance status of the rule for this entity.""" diff --git a/src/openlayer/types/governance/rule_result_update_response.py b/src/openlayer/types/governance/rule_result_update_response.py new file mode 100644 index 00000000..299af399 --- /dev/null +++ b/src/openlayer/types/governance/rule_result_update_response.py @@ -0,0 +1,78 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["RuleResultUpdateResponse", "BlockedBy", "Blocking"] + + +class BlockedBy(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class Blocking(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class RuleResultUpdateResponse(BaseModel): + id: str + """The rule result id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + deactivated: bool + """Whether this result is excluded from compliance calculations.""" + + rule_id: str = FieldInfo(alias="ruleId") + """The rule this result belongs to.""" + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """The compliance status of the rule for this entity.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule result belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for this result.""" + + blocked_by: Optional[List[BlockedBy]] = FieldInfo(alias="blockedBy", default=None) + """Rule results that must pass before this one can be satisfied.""" + + blocking: Optional[List[Blocking]] = None + """Rule results that this one blocks.""" + + date_last_evaluated: Optional[datetime] = FieldInfo(alias="dateLastEvaluated", default=None) + """When the rule was last evaluated. Platform rules only.""" + + date_of_latest_evidence: Optional[datetime] = FieldInfo(alias="dateOfLatestEvidence", default=None) + """When the most recent piece of evidence was attached. Evidence rules only.""" + + date_of_next_evaluation: Optional[datetime] = FieldInfo(alias="dateOfNextEvaluation", default=None) + """When the rule will next be evaluated. Platform rules only.""" + + date_of_renewal: Optional[datetime] = FieldInfo(alias="dateOfRenewal", default=None) + """When the evidence must be renewed. Evidence rules with a renewal cadence only.""" + + deactivated_reason: Optional[str] = FieldInfo(alias="deactivatedReason", default=None) + """Why the result was excluded.""" + + project_id: Optional[str] = FieldInfo(alias="projectId", default=None) + """The project this result was evaluated for. `null` for workspace-scoped rules.""" + + status_message: Optional[str] = FieldInfo(alias="statusMessage", default=None) + """A human-readable explanation of the status.""" diff --git a/src/openlayer/types/governance/rule_retrieve_response.py b/src/openlayer/types/governance/rule_retrieve_response.py new file mode 100644 index 00000000..055abe65 --- /dev/null +++ b/src/openlayer/types/governance/rule_retrieve_response.py @@ -0,0 +1,237 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = [ + "RuleRetrieveResponse", + "Framework", + "FrameworkAvatar", + "Result", + "ResultBlockedBy", + "ResultBlocking", + "ResultsSummary", + "Tag", +] + + +class FrameworkAvatar(BaseModel): + """The icon shown for the framework.""" + + type: Literal["emoji", "imageUrl", "builtinImage"] + + value: str + + +class Framework(BaseModel): + id: str + """The framework id.""" + + avatar: Optional[FrameworkAvatar] = None + """The icon shown for the framework.""" + + built_in_slug: Optional[str] = FieldInfo(alias="builtInSlug", default=None) + """ + Identifies a framework that ships with Openlayer, for example `eu_ai_act`, + `iso_42001`, `nist_ai_rmf`, or `traiga`. `null` for frameworks you create + yourself. + """ + + enabled: bool + """Whether the framework is active. + + Rules of a disabled framework are not evaluated and do not count towards + compliance. + """ + + name: str + """The framework name.""" + + +class ResultBlockedBy(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class ResultBlocking(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class Result(BaseModel): + id: str + """The rule result id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + deactivated: bool + """Whether this result is excluded from compliance calculations.""" + + rule_id: str = FieldInfo(alias="ruleId") + """The rule this result belongs to.""" + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """The compliance status of the rule for this entity.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule result belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for this result.""" + + blocked_by: Optional[List[ResultBlockedBy]] = FieldInfo(alias="blockedBy", default=None) + """Rule results that must pass before this one can be satisfied.""" + + blocking: Optional[List[ResultBlocking]] = None + """Rule results that this one blocks.""" + + date_last_evaluated: Optional[datetime] = FieldInfo(alias="dateLastEvaluated", default=None) + """When the rule was last evaluated. Platform rules only.""" + + date_of_latest_evidence: Optional[datetime] = FieldInfo(alias="dateOfLatestEvidence", default=None) + """When the most recent piece of evidence was attached. Evidence rules only.""" + + date_of_next_evaluation: Optional[datetime] = FieldInfo(alias="dateOfNextEvaluation", default=None) + """When the rule will next be evaluated. Platform rules only.""" + + date_of_renewal: Optional[datetime] = FieldInfo(alias="dateOfRenewal", default=None) + """When the evidence must be renewed. Evidence rules with a renewal cadence only.""" + + deactivated_reason: Optional[str] = FieldInfo(alias="deactivatedReason", default=None) + """Why the result was excluded.""" + + project_id: Optional[str] = FieldInfo(alias="projectId", default=None) + """The project this result was evaluated for. `null` for workspace-scoped rules.""" + + status_message: Optional[str] = FieldInfo(alias="statusMessage", default=None) + """A human-readable explanation of the status.""" + + +class ResultsSummary(BaseModel): + """ + Pass-rate counts across all of the rule's entities, independent of any status filter applied to the request. + """ + + passing: Optional[int] = None + + total: Optional[int] = None + + +class Tag(BaseModel): + id: str + """The rule tag id.""" + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The user who created the tag. `null` for tags that ship with Openlayer.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + immutable: bool + """Whether the tag is managed by Openlayer and cannot be edited or deleted.""" + + name: str + """The tag name.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the tag belongs to.""" + + color: Optional[str] = None + """The color the tag is displayed with.""" + + +class RuleRetrieveResponse(BaseModel): + id: str + """The rule id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + name: str + """The rule name.""" + + scope: Literal["project", "workspace"] + """ + Whether the rule is evaluated once for the whole workspace, or once per project + the rule's frameworks apply to. + """ + + type: Literal["platform", "evidence"] + """ + `platform` rules are evaluated automatically from the state of your Openlayer + workspace. `evidence` rules are satisfied by attaching evidence. + """ + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for satisfying the rule.""" + + automation_params: Optional[Dict[str, object]] = FieldInfo(alias="automationParams", default=None) + """Configuration for the platform check, when the automation takes parameters.""" + + automation_type: Optional[str] = FieldInfo(alias="automationType", default=None) + """ + Which workspace signal a platform rule checks, for example + `monitoring_mode_enabled`, `test_setup`, or `project_owner_set`. `null` for + evidence rules. + """ + + deactivated: Optional[bool] = None + """Whether the rule is excluded from compliance calculations.""" + + description: Optional[str] = None + """What the rule requires.""" + + evidence_type: Optional[Literal["document", "text", "url", "categoryValue"]] = FieldInfo( + alias="evidenceType", default=None + ) + """The kind of evidence that satisfies the rule. `null` for platform rules.""" + + frameworks: Optional[List[Framework]] = None + """The frameworks that include this rule.""" + + immutable: Optional[bool] = None + """Whether the rule is managed by Openlayer and cannot be edited.""" + + renewal_cadence_days: Optional[int] = FieldInfo(alias="renewalCadenceDays", default=None) + """How often evidence must be renewed, in days. + + Once evidence is older than this, the rule result becomes `due_soon` and then + `failing`. + """ + + results: Optional[List[Result]] = None + """The rule's results, one per entity the rule is evaluated against. + + Only returned when `includeResults` is `true`. + """ + + results_summary: Optional[ResultsSummary] = FieldInfo(alias="resultsSummary", default=None) + """ + Pass-rate counts across all of the rule's entities, independent of any status + filter applied to the request. + """ + + tags: Optional[List[Tag]] = None + """The rule tags associated with the rule.""" diff --git a/src/openlayer/types/governance/rule_stat_retrieve_params.py b/src/openlayer/types/governance/rule_stat_retrieve_params.py new file mode 100644 index 00000000..b817c194 --- /dev/null +++ b/src/openlayer/types/governance/rule_stat_retrieve_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["RuleStatRetrieveParams"] + + +class RuleStatRetrieveParams(TypedDict, total=False): + framework_id: Annotated[str, PropertyInfo(alias="frameworkId")] + """Only include items belonging to this framework.""" + + project_id: Annotated[str, PropertyInfo(alias="projectId")] + """Only include items that apply to this project.""" diff --git a/src/openlayer/types/governance/rule_stat_retrieve_response.py b/src/openlayer/types/governance/rule_stat_retrieve_response.py new file mode 100644 index 00000000..84ab85d6 --- /dev/null +++ b/src/openlayer/types/governance/rule_stat_retrieve_response.py @@ -0,0 +1,211 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = [ + "RuleStatRetrieveResponse", + "RuleResults", + "RuleResultsByRuleScope", + "RuleResultsByRuleScopeProject", + "RuleResultsByRuleScopeWorkspace", + "RuleResultsByRuleType", + "RuleResultsByRuleTypeEvidence", + "RuleResultsByRuleTypePlatform", + "Rules", + "RulesByScope", + "RulesByType", +] + + +class RuleResultsByRuleScopeProject(BaseModel): + total: int + """The total number of rule results.""" + + total_due_soon: int = FieldInfo(alias="totalDueSoon") + """The number of rule results whose evidence is about to expire.""" + + total_error: int = FieldInfo(alias="totalError") + """The number of rule results that errored during evaluation.""" + + total_failing: int = FieldInfo(alias="totalFailing") + """The number of failing rule results.""" + + total_passing: int = FieldInfo(alias="totalPassing") + """The number of passing rule results.""" + + total_pending: int = FieldInfo(alias="totalPending") + """The number of rule results that have not been satisfied yet.""" + + total_running: int = FieldInfo(alias="totalRunning") + """The number of rule results currently being evaluated.""" + + total_skipped: int = FieldInfo(alias="totalSkipped") + """The number of skipped rule results.""" + + +class RuleResultsByRuleScopeWorkspace(BaseModel): + total: int + """The total number of rule results.""" + + total_due_soon: int = FieldInfo(alias="totalDueSoon") + """The number of rule results whose evidence is about to expire.""" + + total_error: int = FieldInfo(alias="totalError") + """The number of rule results that errored during evaluation.""" + + total_failing: int = FieldInfo(alias="totalFailing") + """The number of failing rule results.""" + + total_passing: int = FieldInfo(alias="totalPassing") + """The number of passing rule results.""" + + total_pending: int = FieldInfo(alias="totalPending") + """The number of rule results that have not been satisfied yet.""" + + total_running: int = FieldInfo(alias="totalRunning") + """The number of rule results currently being evaluated.""" + + total_skipped: int = FieldInfo(alias="totalSkipped") + """The number of skipped rule results.""" + + +class RuleResultsByRuleScope(BaseModel): + project: Optional[RuleResultsByRuleScopeProject] = None + + workspace: Optional[RuleResultsByRuleScopeWorkspace] = None + + +class RuleResultsByRuleTypeEvidence(BaseModel): + total: int + """The total number of rule results.""" + + total_due_soon: int = FieldInfo(alias="totalDueSoon") + """The number of rule results whose evidence is about to expire.""" + + total_error: int = FieldInfo(alias="totalError") + """The number of rule results that errored during evaluation.""" + + total_failing: int = FieldInfo(alias="totalFailing") + """The number of failing rule results.""" + + total_passing: int = FieldInfo(alias="totalPassing") + """The number of passing rule results.""" + + total_pending: int = FieldInfo(alias="totalPending") + """The number of rule results that have not been satisfied yet.""" + + total_running: int = FieldInfo(alias="totalRunning") + """The number of rule results currently being evaluated.""" + + total_skipped: int = FieldInfo(alias="totalSkipped") + """The number of skipped rule results.""" + + +class RuleResultsByRuleTypePlatform(BaseModel): + total: int + """The total number of rule results.""" + + total_due_soon: int = FieldInfo(alias="totalDueSoon") + """The number of rule results whose evidence is about to expire.""" + + total_error: int = FieldInfo(alias="totalError") + """The number of rule results that errored during evaluation.""" + + total_failing: int = FieldInfo(alias="totalFailing") + """The number of failing rule results.""" + + total_passing: int = FieldInfo(alias="totalPassing") + """The number of passing rule results.""" + + total_pending: int = FieldInfo(alias="totalPending") + """The number of rule results that have not been satisfied yet.""" + + total_running: int = FieldInfo(alias="totalRunning") + """The number of rule results currently being evaluated.""" + + total_skipped: int = FieldInfo(alias="totalSkipped") + """The number of skipped rule results.""" + + +class RuleResultsByRuleType(BaseModel): + evidence: Optional[RuleResultsByRuleTypeEvidence] = None + + platform: Optional[RuleResultsByRuleTypePlatform] = None + + +class RuleResults(BaseModel): + """ + Counts of rule results, after any filters in the request, with breakdowns by the type and scope of the rule each result belongs to. + """ + + total: int + """The total number of rule results.""" + + total_due_soon: int = FieldInfo(alias="totalDueSoon") + """The number of rule results whose evidence is about to expire.""" + + total_error: int = FieldInfo(alias="totalError") + """The number of rule results that errored during evaluation.""" + + total_failing: int = FieldInfo(alias="totalFailing") + """The number of failing rule results.""" + + total_passing: int = FieldInfo(alias="totalPassing") + """The number of passing rule results.""" + + total_pending: int = FieldInfo(alias="totalPending") + """The number of rule results that have not been satisfied yet.""" + + total_running: int = FieldInfo(alias="totalRunning") + """The number of rule results currently being evaluated.""" + + total_skipped: int = FieldInfo(alias="totalSkipped") + """The number of skipped rule results.""" + + by_rule_scope: Optional[RuleResultsByRuleScope] = FieldInfo(alias="byRuleScope", default=None) + + by_rule_type: Optional[RuleResultsByRuleType] = FieldInfo(alias="byRuleType", default=None) + + +class RulesByScope(BaseModel): + """Rule counts by scope.""" + + project: Optional[int] = None + + workspace: Optional[int] = None + + +class RulesByType(BaseModel): + """Rule counts by type.""" + + evidence: Optional[int] = None + + platform: Optional[int] = None + + +class Rules(BaseModel): + """Counts of the rules themselves, after any filters in the request.""" + + by_scope: Optional[RulesByScope] = FieldInfo(alias="byScope", default=None) + """Rule counts by scope.""" + + by_type: Optional[RulesByType] = FieldInfo(alias="byType", default=None) + """Rule counts by type.""" + + total: Optional[int] = None + """The total number of rules.""" + + +class RuleStatRetrieveResponse(BaseModel): + rule_results: RuleResults = FieldInfo(alias="ruleResults") + """ + Counts of rule results, after any filters in the request, with breakdowns by the + type and scope of the rule each result belongs to. + """ + + rules: Rules + """Counts of the rules themselves, after any filters in the request.""" diff --git a/src/openlayer/types/governance/rule_tag_list_params.py b/src/openlayer/types/governance/rule_tag_list_params.py new file mode 100644 index 00000000..aecc9e77 --- /dev/null +++ b/src/openlayer/types/governance/rule_tag_list_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["RuleTagListParams"] + + +class RuleTagListParams(TypedDict, total=False): + page: int + """The page to return in a paginated query.""" + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Maximum number of items to return per page.""" diff --git a/src/openlayer/types/governance/rule_tag_list_response.py b/src/openlayer/types/governance/rule_tag_list_response.py new file mode 100644 index 00000000..12b53d39 --- /dev/null +++ b/src/openlayer/types/governance/rule_tag_list_response.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["RuleTagListResponse", "Item"] + + +class Item(BaseModel): + id: str + """The rule tag id.""" + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The user who created the tag. `null` for tags that ship with Openlayer.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + immutable: bool + """Whether the tag is managed by Openlayer and cannot be edited or deleted.""" + + name: str + """The tag name.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the tag belongs to.""" + + color: Optional[str] = None + """The color the tag is displayed with.""" + + +class RuleTagListResponse(BaseModel): + items: List[Item] diff --git a/src/openlayer/types/governance/rule_update_params.py b/src/openlayer/types/governance/rule_update_params.py new file mode 100644 index 00000000..f8cf2ed3 --- /dev/null +++ b/src/openlayer/types/governance/rule_update_params.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Annotated, TypedDict + +from ..._types import SequenceNotStr +from ..._utils import PropertyInfo + +__all__ = ["RuleUpdateParams"] + + +class RuleUpdateParams(TypedDict, total=False): + assignee_id: Annotated[Optional[str], PropertyInfo(alias="assigneeId")] + """The user responsible for satisfying the rule.""" + + deactivated: bool + """Whether the rule is excluded from compliance calculations.""" + + description: Optional[str] + """What the rule requires.""" + + name: str + """The rule name.""" + + renewal_cadence_days: Annotated[Optional[int], PropertyInfo(alias="renewalCadenceDays")] + """How often evidence must be renewed, in days. + + Once evidence is older than this, the rule result becomes `due_soon` and then + `failing`. + """ + + tag_ids: Annotated[Optional[SequenceNotStr[str]], PropertyInfo(alias="tagIds")] + """The ids of the rule tags to associate with the rule. + + Replaces the rule's tags. Read them back from `tags`, and list the tags + available in the workspace with `GET /workspaces/{workspaceId}/rule-tags`. + """ diff --git a/src/openlayer/types/governance/rule_update_response.py b/src/openlayer/types/governance/rule_update_response.py new file mode 100644 index 00000000..275ba842 --- /dev/null +++ b/src/openlayer/types/governance/rule_update_response.py @@ -0,0 +1,237 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = [ + "RuleUpdateResponse", + "Framework", + "FrameworkAvatar", + "Result", + "ResultBlockedBy", + "ResultBlocking", + "ResultsSummary", + "Tag", +] + + +class FrameworkAvatar(BaseModel): + """The icon shown for the framework.""" + + type: Literal["emoji", "imageUrl", "builtinImage"] + + value: str + + +class Framework(BaseModel): + id: str + """The framework id.""" + + avatar: Optional[FrameworkAvatar] = None + """The icon shown for the framework.""" + + built_in_slug: Optional[str] = FieldInfo(alias="builtInSlug", default=None) + """ + Identifies a framework that ships with Openlayer, for example `eu_ai_act`, + `iso_42001`, `nist_ai_rmf`, or `traiga`. `null` for frameworks you create + yourself. + """ + + enabled: bool + """Whether the framework is active. + + Rules of a disabled framework are not evaluated and do not count towards + compliance. + """ + + name: str + """The framework name.""" + + +class ResultBlockedBy(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class ResultBlocking(BaseModel): + id: Optional[str] = None + + status: Optional[Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"]] = None + """The compliance status of the rule for this entity.""" + + +class Result(BaseModel): + id: str + """The rule result id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + deactivated: bool + """Whether this result is excluded from compliance calculations.""" + + rule_id: str = FieldInfo(alias="ruleId") + """The rule this result belongs to.""" + + status: Literal["running", "passing", "failing", "skipped", "error", "pending", "due_soon"] + """The compliance status of the rule for this entity.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule result belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for this result.""" + + blocked_by: Optional[List[ResultBlockedBy]] = FieldInfo(alias="blockedBy", default=None) + """Rule results that must pass before this one can be satisfied.""" + + blocking: Optional[List[ResultBlocking]] = None + """Rule results that this one blocks.""" + + date_last_evaluated: Optional[datetime] = FieldInfo(alias="dateLastEvaluated", default=None) + """When the rule was last evaluated. Platform rules only.""" + + date_of_latest_evidence: Optional[datetime] = FieldInfo(alias="dateOfLatestEvidence", default=None) + """When the most recent piece of evidence was attached. Evidence rules only.""" + + date_of_next_evaluation: Optional[datetime] = FieldInfo(alias="dateOfNextEvaluation", default=None) + """When the rule will next be evaluated. Platform rules only.""" + + date_of_renewal: Optional[datetime] = FieldInfo(alias="dateOfRenewal", default=None) + """When the evidence must be renewed. Evidence rules with a renewal cadence only.""" + + deactivated_reason: Optional[str] = FieldInfo(alias="deactivatedReason", default=None) + """Why the result was excluded.""" + + project_id: Optional[str] = FieldInfo(alias="projectId", default=None) + """The project this result was evaluated for. `null` for workspace-scoped rules.""" + + status_message: Optional[str] = FieldInfo(alias="statusMessage", default=None) + """A human-readable explanation of the status.""" + + +class ResultsSummary(BaseModel): + """ + Pass-rate counts across all of the rule's entities, independent of any status filter applied to the request. + """ + + passing: Optional[int] = None + + total: Optional[int] = None + + +class Tag(BaseModel): + id: str + """The rule tag id.""" + + creator_id: Optional[str] = FieldInfo(alias="creatorId", default=None) + """The user who created the tag. `null` for tags that ship with Openlayer.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + immutable: bool + """Whether the tag is managed by Openlayer and cannot be edited or deleted.""" + + name: str + """The tag name.""" + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the tag belongs to.""" + + color: Optional[str] = None + """The color the tag is displayed with.""" + + +class RuleUpdateResponse(BaseModel): + id: str + """The rule id.""" + + date_created: datetime = FieldInfo(alias="dateCreated") + """The creation date.""" + + date_updated: datetime = FieldInfo(alias="dateUpdated") + """The last update date.""" + + name: str + """The rule name.""" + + scope: Literal["project", "workspace"] + """ + Whether the rule is evaluated once for the whole workspace, or once per project + the rule's frameworks apply to. + """ + + type: Literal["platform", "evidence"] + """ + `platform` rules are evaluated automatically from the state of your Openlayer + workspace. `evidence` rules are satisfied by attaching evidence. + """ + + workspace_id: str = FieldInfo(alias="workspaceId") + """The id of the workspace the rule belongs to.""" + + assignee_id: Optional[str] = FieldInfo(alias="assigneeId", default=None) + """The user responsible for satisfying the rule.""" + + automation_params: Optional[Dict[str, object]] = FieldInfo(alias="automationParams", default=None) + """Configuration for the platform check, when the automation takes parameters.""" + + automation_type: Optional[str] = FieldInfo(alias="automationType", default=None) + """ + Which workspace signal a platform rule checks, for example + `monitoring_mode_enabled`, `test_setup`, or `project_owner_set`. `null` for + evidence rules. + """ + + deactivated: Optional[bool] = None + """Whether the rule is excluded from compliance calculations.""" + + description: Optional[str] = None + """What the rule requires.""" + + evidence_type: Optional[Literal["document", "text", "url", "categoryValue"]] = FieldInfo( + alias="evidenceType", default=None + ) + """The kind of evidence that satisfies the rule. `null` for platform rules.""" + + frameworks: Optional[List[Framework]] = None + """The frameworks that include this rule.""" + + immutable: Optional[bool] = None + """Whether the rule is managed by Openlayer and cannot be edited.""" + + renewal_cadence_days: Optional[int] = FieldInfo(alias="renewalCadenceDays", default=None) + """How often evidence must be renewed, in days. + + Once evidence is older than this, the rule result becomes `due_soon` and then + `failing`. + """ + + results: Optional[List[Result]] = None + """The rule's results, one per entity the rule is evaluated against. + + Only returned when `includeResults` is `true`. + """ + + results_summary: Optional[ResultsSummary] = FieldInfo(alias="resultsSummary", default=None) + """ + Pass-rate counts across all of the rule's entities, independent of any status + filter applied to the request. + """ + + tags: Optional[List[Tag]] = None + """The rule tags associated with the rule.""" diff --git a/src/openlayer/types/inference_pipeline_retrieve_response.py b/src/openlayer/types/inference_pipeline_retrieve_response.py index d91c59b9..3829955e 100644 --- a/src/openlayer/types/inference_pipeline_retrieve_response.py +++ b/src/openlayer/types/inference_pipeline_retrieve_response.py @@ -259,7 +259,7 @@ class Project(BaseModel): name: str """The project name.""" - source: Optional[Literal["web", "api", "null"]] = None + source: Optional[Literal["web", "api"]] = None """The source of the project.""" task_type: Literal["llm-base", "tabular-classification", "tabular-regression", "text-classification"] = FieldInfo( diff --git a/src/openlayer/types/inference_pipeline_update_response.py b/src/openlayer/types/inference_pipeline_update_response.py index 49c10a6e..cc6721ae 100644 --- a/src/openlayer/types/inference_pipeline_update_response.py +++ b/src/openlayer/types/inference_pipeline_update_response.py @@ -259,7 +259,7 @@ class Project(BaseModel): name: str """The project name.""" - source: Optional[Literal["web", "api", "null"]] = None + source: Optional[Literal["web", "api"]] = None """The source of the project.""" task_type: Literal["llm-base", "tabular-classification", "tabular-regression", "text-classification"] = FieldInfo( diff --git a/src/openlayer/types/project_create_response.py b/src/openlayer/types/project_create_response.py index ee049acf..9801ef99 100644 --- a/src/openlayer/types/project_create_response.py +++ b/src/openlayer/types/project_create_response.py @@ -74,7 +74,7 @@ class ProjectCreateResponse(BaseModel): name: str """The project name.""" - source: Optional[Literal["web", "api", "null"]] = None + source: Optional[Literal["web", "api"]] = None """The source of the project.""" task_type: Literal["llm-base", "tabular-classification", "tabular-regression", "text-classification"] = FieldInfo( diff --git a/src/openlayer/types/project_list_response.py b/src/openlayer/types/project_list_response.py index 1461ddae..8e19432c 100644 --- a/src/openlayer/types/project_list_response.py +++ b/src/openlayer/types/project_list_response.py @@ -74,7 +74,7 @@ class Item(BaseModel): name: str """The project name.""" - source: Optional[Literal["web", "api", "null"]] = None + source: Optional[Literal["web", "api"]] = None """The source of the project.""" task_type: Literal["llm-base", "tabular-classification", "tabular-regression", "text-classification"] = FieldInfo( diff --git a/src/openlayer/types/project_update_response.py b/src/openlayer/types/project_update_response.py index 26d804e3..c9d24101 100644 --- a/src/openlayer/types/project_update_response.py +++ b/src/openlayer/types/project_update_response.py @@ -74,7 +74,7 @@ class ProjectUpdateResponse(BaseModel): name: str """The project name.""" - source: Optional[Literal["web", "api", "null"]] = None + source: Optional[Literal["web", "api"]] = None """The source of the project.""" task_type: Literal["llm-base", "tabular-classification", "tabular-regression", "text-classification"] = FieldInfo( diff --git a/src/openlayer/types/projects/inference_pipeline_create_response.py b/src/openlayer/types/projects/inference_pipeline_create_response.py index 55ebce2c..a41ead18 100644 --- a/src/openlayer/types/projects/inference_pipeline_create_response.py +++ b/src/openlayer/types/projects/inference_pipeline_create_response.py @@ -259,7 +259,7 @@ class Project(BaseModel): name: str """The project name.""" - source: Optional[Literal["web", "api", "null"]] = None + source: Optional[Literal["web", "api"]] = None """The source of the project.""" task_type: Literal["llm-base", "tabular-classification", "tabular-regression", "text-classification"] = FieldInfo( diff --git a/src/openlayer/types/projects/inference_pipeline_list_response.py b/src/openlayer/types/projects/inference_pipeline_list_response.py index 96e12347..b77606bd 100644 --- a/src/openlayer/types/projects/inference_pipeline_list_response.py +++ b/src/openlayer/types/projects/inference_pipeline_list_response.py @@ -260,7 +260,7 @@ class ItemProject(BaseModel): name: str """The project name.""" - source: Optional[Literal["web", "api", "null"]] = None + source: Optional[Literal["web", "api"]] = None """The source of the project.""" task_type: Literal["llm-base", "tabular-classification", "tabular-regression", "text-classification"] = FieldInfo( diff --git a/src/openlayer/types/storage/__init__.py b/src/openlayer/types/storage/__init__.py index 1e6151a5..6638e0a5 100644 --- a/src/openlayer/types/storage/__init__.py +++ b/src/openlayer/types/storage/__init__.py @@ -4,3 +4,5 @@ from .presigned_url_create_params import PresignedURLCreateParams as PresignedURLCreateParams from .presigned_url_create_response import PresignedURLCreateResponse as PresignedURLCreateResponse +from .presigned_url_retrieve_params import PresignedURLRetrieveParams as PresignedURLRetrieveParams +from .presigned_url_retrieve_response import PresignedURLRetrieveResponse as PresignedURLRetrieveResponse diff --git a/src/openlayer/types/storage/presigned_url_retrieve_params.py b/src/openlayer/types/storage/presigned_url_retrieve_params.py new file mode 100644 index 00000000..6868b697 --- /dev/null +++ b/src/openlayer/types/storage/presigned_url_retrieve_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["PresignedURLRetrieveParams"] + + +class PresignedURLRetrieveParams(TypedDict, total=False): + storage_uri: Required[Annotated[str, PropertyInfo(alias="storageUri")]] + """The object's storage uri.""" diff --git a/src/openlayer/types/storage/presigned_url_retrieve_response.py b/src/openlayer/types/storage/presigned_url_retrieve_response.py new file mode 100644 index 00000000..b9579e80 --- /dev/null +++ b/src/openlayer/types/storage/presigned_url_retrieve_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["PresignedURLRetrieveResponse"] + + +class PresignedURLRetrieveResponse(BaseModel): + url: str + """The presigned url. Short-lived -- download promptly.""" diff --git a/tests/api_resources/governance/__init__.py b/tests/api_resources/governance/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/governance/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/governance/frameworks/__init__.py b/tests/api_resources/governance/frameworks/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/governance/frameworks/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/governance/frameworks/test_documents.py b/tests/api_resources/governance/frameworks/test_documents.py new file mode 100644 index 00000000..9dc3cb6e --- /dev/null +++ b/tests/api_resources/governance/frameworks/test_documents.py @@ -0,0 +1,214 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openlayer import Openlayer, AsyncOpenlayer +from tests.utils import assert_matches_type +from openlayer.types.governance.frameworks import DocumentListResponse, DocumentRetrieveResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestDocuments: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Openlayer) -> None: + document = client.governance.frameworks.documents.retrieve( + document_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(DocumentRetrieveResponse, document, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Openlayer) -> None: + response = client.governance.frameworks.documents.with_raw_response.retrieve( + document_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + document = response.parse() + assert_matches_type(DocumentRetrieveResponse, document, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Openlayer) -> None: + with client.governance.frameworks.documents.with_streaming_response.retrieve( + document_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + document = response.parse() + assert_matches_type(DocumentRetrieveResponse, document, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + client.governance.frameworks.documents.with_raw_response.retrieve( + document_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `document_id` but received ''"): + client.governance.frameworks.documents.with_raw_response.retrieve( + document_id="", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + def test_method_list(self, client: Openlayer) -> None: + document = client.governance.frameworks.documents.list( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(DocumentListResponse, document, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Openlayer) -> None: + document = client.governance.frameworks.documents.list( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page=1, + per_page=1, + ) + assert_matches_type(DocumentListResponse, document, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Openlayer) -> None: + response = client.governance.frameworks.documents.with_raw_response.list( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + document = response.parse() + assert_matches_type(DocumentListResponse, document, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Openlayer) -> None: + with client.governance.frameworks.documents.with_streaming_response.list( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + document = response.parse() + assert_matches_type(DocumentListResponse, document, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + client.governance.frameworks.documents.with_raw_response.list( + framework_id="", + ) + + +class TestAsyncDocuments: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenlayer) -> None: + document = await async_client.governance.frameworks.documents.retrieve( + document_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(DocumentRetrieveResponse, document, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.frameworks.documents.with_raw_response.retrieve( + document_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + document = await response.parse() + assert_matches_type(DocumentRetrieveResponse, document, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.frameworks.documents.with_streaming_response.retrieve( + document_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + document = await response.parse() + assert_matches_type(DocumentRetrieveResponse, document, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + await async_client.governance.frameworks.documents.with_raw_response.retrieve( + document_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `document_id` but received ''"): + await async_client.governance.frameworks.documents.with_raw_response.retrieve( + document_id="", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenlayer) -> None: + document = await async_client.governance.frameworks.documents.list( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(DocumentListResponse, document, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenlayer) -> None: + document = await async_client.governance.frameworks.documents.list( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page=1, + per_page=1, + ) + assert_matches_type(DocumentListResponse, document, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.frameworks.documents.with_raw_response.list( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + document = await response.parse() + assert_matches_type(DocumentListResponse, document, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.frameworks.documents.with_streaming_response.list( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + document = await response.parse() + assert_matches_type(DocumentListResponse, document, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + await async_client.governance.frameworks.documents.with_raw_response.list( + framework_id="", + ) diff --git a/tests/api_resources/governance/frameworks/test_sections.py b/tests/api_resources/governance/frameworks/test_sections.py new file mode 100644 index 00000000..e9155913 --- /dev/null +++ b/tests/api_resources/governance/frameworks/test_sections.py @@ -0,0 +1,148 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openlayer import Openlayer, AsyncOpenlayer +from tests.utils import assert_matches_type +from openlayer.types.governance.frameworks import SectionListRulesResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSections: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list_rules(self, client: Openlayer) -> None: + section = client.governance.frameworks.sections.list_rules( + section_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(SectionListRulesResponse, section, path=["response"]) + + @parametrize + def test_method_list_rules_with_all_params(self, client: Openlayer) -> None: + section = client.governance.frameworks.sections.list_rules( + section_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + include_results=True, + include_subsection_rules=True, + page=1, + per_page=1, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="passing", + ) + assert_matches_type(SectionListRulesResponse, section, path=["response"]) + + @parametrize + def test_raw_response_list_rules(self, client: Openlayer) -> None: + response = client.governance.frameworks.sections.with_raw_response.list_rules( + section_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + section = response.parse() + assert_matches_type(SectionListRulesResponse, section, path=["response"]) + + @parametrize + def test_streaming_response_list_rules(self, client: Openlayer) -> None: + with client.governance.frameworks.sections.with_streaming_response.list_rules( + section_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + section = response.parse() + assert_matches_type(SectionListRulesResponse, section, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list_rules(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + client.governance.frameworks.sections.with_raw_response.list_rules( + section_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `section_id` but received ''"): + client.governance.frameworks.sections.with_raw_response.list_rules( + section_id="", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + +class TestAsyncSections: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list_rules(self, async_client: AsyncOpenlayer) -> None: + section = await async_client.governance.frameworks.sections.list_rules( + section_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(SectionListRulesResponse, section, path=["response"]) + + @parametrize + async def test_method_list_rules_with_all_params(self, async_client: AsyncOpenlayer) -> None: + section = await async_client.governance.frameworks.sections.list_rules( + section_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + include_results=True, + include_subsection_rules=True, + page=1, + per_page=1, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="passing", + ) + assert_matches_type(SectionListRulesResponse, section, path=["response"]) + + @parametrize + async def test_raw_response_list_rules(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.frameworks.sections.with_raw_response.list_rules( + section_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + section = await response.parse() + assert_matches_type(SectionListRulesResponse, section, path=["response"]) + + @parametrize + async def test_streaming_response_list_rules(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.frameworks.sections.with_streaming_response.list_rules( + section_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + section = await response.parse() + assert_matches_type(SectionListRulesResponse, section, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list_rules(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + await async_client.governance.frameworks.sections.with_raw_response.list_rules( + section_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `section_id` but received ''"): + await async_client.governance.frameworks.sections.with_raw_response.list_rules( + section_id="", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) diff --git a/tests/api_resources/governance/frameworks/test_subsections.py b/tests/api_resources/governance/frameworks/test_subsections.py new file mode 100644 index 00000000..e84421af --- /dev/null +++ b/tests/api_resources/governance/frameworks/test_subsections.py @@ -0,0 +1,146 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openlayer import Openlayer, AsyncOpenlayer +from tests.utils import assert_matches_type +from openlayer.types.governance.frameworks import SubsectionListRulesResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSubsections: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list_rules(self, client: Openlayer) -> None: + subsection = client.governance.frameworks.subsections.list_rules( + subsection_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(SubsectionListRulesResponse, subsection, path=["response"]) + + @parametrize + def test_method_list_rules_with_all_params(self, client: Openlayer) -> None: + subsection = client.governance.frameworks.subsections.list_rules( + subsection_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + include_results=True, + page=1, + per_page=1, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="passing", + ) + assert_matches_type(SubsectionListRulesResponse, subsection, path=["response"]) + + @parametrize + def test_raw_response_list_rules(self, client: Openlayer) -> None: + response = client.governance.frameworks.subsections.with_raw_response.list_rules( + subsection_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + subsection = response.parse() + assert_matches_type(SubsectionListRulesResponse, subsection, path=["response"]) + + @parametrize + def test_streaming_response_list_rules(self, client: Openlayer) -> None: + with client.governance.frameworks.subsections.with_streaming_response.list_rules( + subsection_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + subsection = response.parse() + assert_matches_type(SubsectionListRulesResponse, subsection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list_rules(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + client.governance.frameworks.subsections.with_raw_response.list_rules( + subsection_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `subsection_id` but received ''"): + client.governance.frameworks.subsections.with_raw_response.list_rules( + subsection_id="", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + +class TestAsyncSubsections: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list_rules(self, async_client: AsyncOpenlayer) -> None: + subsection = await async_client.governance.frameworks.subsections.list_rules( + subsection_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(SubsectionListRulesResponse, subsection, path=["response"]) + + @parametrize + async def test_method_list_rules_with_all_params(self, async_client: AsyncOpenlayer) -> None: + subsection = await async_client.governance.frameworks.subsections.list_rules( + subsection_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + include_results=True, + page=1, + per_page=1, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + status="passing", + ) + assert_matches_type(SubsectionListRulesResponse, subsection, path=["response"]) + + @parametrize + async def test_raw_response_list_rules(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.frameworks.subsections.with_raw_response.list_rules( + subsection_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + subsection = await response.parse() + assert_matches_type(SubsectionListRulesResponse, subsection, path=["response"]) + + @parametrize + async def test_streaming_response_list_rules(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.frameworks.subsections.with_streaming_response.list_rules( + subsection_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + subsection = await response.parse() + assert_matches_type(SubsectionListRulesResponse, subsection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list_rules(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + await async_client.governance.frameworks.subsections.with_raw_response.list_rules( + subsection_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `subsection_id` but received ''"): + await async_client.governance.frameworks.subsections.with_raw_response.list_rules( + subsection_id="", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) diff --git a/tests/api_resources/governance/test_frameworks.py b/tests/api_resources/governance/test_frameworks.py new file mode 100644 index 00000000..80c54218 --- /dev/null +++ b/tests/api_resources/governance/test_frameworks.py @@ -0,0 +1,851 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openlayer import Openlayer, AsyncOpenlayer +from tests.utils import assert_matches_type +from openlayer.types.governance import ( + FrameworkListResponse, + FrameworkCreateResponse, + FrameworkExportResponse, + FrameworkUpdateResponse, + FrameworkRetrieveResponse, + FrameworkListRulesResponse, + FrameworkListProjectsResponse, + FrameworkListProjectRuleStatsResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestFrameworks: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Openlayer) -> None: + framework = client.governance.frameworks.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="EU AI Act", + ) + assert_matches_type(FrameworkCreateResponse, framework, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Openlayer) -> None: + framework = client.governance.frameworks.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="EU AI Act", + description="Requirements for high-risk AI systems under the EU AI Act.", + enabled=True, + project_selector={ + "match": [ + { + "property": "riskLevel", + "value": ["high", "critical"], + "operator": "operator", + } + ] + }, + tags=["regulation", "eu"], + ) + assert_matches_type(FrameworkCreateResponse, framework, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Openlayer) -> None: + response = client.governance.frameworks.with_raw_response.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="EU AI Act", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = response.parse() + assert_matches_type(FrameworkCreateResponse, framework, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Openlayer) -> None: + with client.governance.frameworks.with_streaming_response.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="EU AI Act", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = response.parse() + assert_matches_type(FrameworkCreateResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `workspace_id` but received ''"): + client.governance.frameworks.with_raw_response.create( + workspace_id="", + name="EU AI Act", + ) + + @parametrize + def test_method_retrieve(self, client: Openlayer) -> None: + framework = client.governance.frameworks.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FrameworkRetrieveResponse, framework, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Openlayer) -> None: + response = client.governance.frameworks.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = response.parse() + assert_matches_type(FrameworkRetrieveResponse, framework, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Openlayer) -> None: + with client.governance.frameworks.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = response.parse() + assert_matches_type(FrameworkRetrieveResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + client.governance.frameworks.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: Openlayer) -> None: + framework = client.governance.frameworks.update( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FrameworkUpdateResponse, framework, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Openlayer) -> None: + framework = client.governance.frameworks.update( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + avatar={ + "type": "emoji", + "value": "🧭", + }, + description="Requirements for high-risk AI systems under the EU AI Act.", + enabled=True, + extended_description={"foo": "bar"}, + href="href", + name="EU AI Act", + project_selector={ + "match": [ + { + "property": "riskLevel", + "value": ["high", "critical"], + "operator": "operator", + } + ] + }, + tags=["regulation", "eu"], + ) + assert_matches_type(FrameworkUpdateResponse, framework, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Openlayer) -> None: + response = client.governance.frameworks.with_raw_response.update( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = response.parse() + assert_matches_type(FrameworkUpdateResponse, framework, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Openlayer) -> None: + with client.governance.frameworks.with_streaming_response.update( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = response.parse() + assert_matches_type(FrameworkUpdateResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + client.governance.frameworks.with_raw_response.update( + framework_id="", + ) + + @parametrize + def test_method_list(self, client: Openlayer) -> None: + framework = client.governance.frameworks.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FrameworkListResponse, framework, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Openlayer) -> None: + framework = client.governance.frameworks.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + asc=True, + completion_operator="is", + completion_value=0, + enabled=True, + include_rule_stats=True, + page=1, + per_page=1, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + search_query="searchQuery", + sort_column="name", + tags=["string"], + ) + assert_matches_type(FrameworkListResponse, framework, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Openlayer) -> None: + response = client.governance.frameworks.with_raw_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = response.parse() + assert_matches_type(FrameworkListResponse, framework, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Openlayer) -> None: + with client.governance.frameworks.with_streaming_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = response.parse() + assert_matches_type(FrameworkListResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `workspace_id` but received ''"): + client.governance.frameworks.with_raw_response.list( + workspace_id="", + ) + + @parametrize + def test_method_export(self, client: Openlayer) -> None: + framework = client.governance.frameworks.export( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FrameworkExportResponse, framework, path=["response"]) + + @parametrize + def test_method_export_with_all_params(self, client: Openlayer) -> None: + framework = client.governance.frameworks.export( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="3fa85f64-5717-4562-b3fc-2c963f66afa6", + ) + assert_matches_type(FrameworkExportResponse, framework, path=["response"]) + + @parametrize + def test_raw_response_export(self, client: Openlayer) -> None: + response = client.governance.frameworks.with_raw_response.export( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = response.parse() + assert_matches_type(FrameworkExportResponse, framework, path=["response"]) + + @parametrize + def test_streaming_response_export(self, client: Openlayer) -> None: + with client.governance.frameworks.with_streaming_response.export( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = response.parse() + assert_matches_type(FrameworkExportResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_export(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + client.governance.frameworks.with_raw_response.export( + framework_id="", + ) + + @parametrize + def test_method_list_project_rule_stats(self, client: Openlayer) -> None: + framework = client.governance.frameworks.list_project_rule_stats( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FrameworkListProjectRuleStatsResponse, framework, path=["response"]) + + @parametrize + def test_method_list_project_rule_stats_with_all_params(self, client: Openlayer) -> None: + framework = client.governance.frameworks.list_project_rule_stats( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + asc=True, + page=1, + per_page=1, + sort_column="projectName", + ) + assert_matches_type(FrameworkListProjectRuleStatsResponse, framework, path=["response"]) + + @parametrize + def test_raw_response_list_project_rule_stats(self, client: Openlayer) -> None: + response = client.governance.frameworks.with_raw_response.list_project_rule_stats( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = response.parse() + assert_matches_type(FrameworkListProjectRuleStatsResponse, framework, path=["response"]) + + @parametrize + def test_streaming_response_list_project_rule_stats(self, client: Openlayer) -> None: + with client.governance.frameworks.with_streaming_response.list_project_rule_stats( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = response.parse() + assert_matches_type(FrameworkListProjectRuleStatsResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list_project_rule_stats(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + client.governance.frameworks.with_raw_response.list_project_rule_stats( + framework_id="", + ) + + @parametrize + def test_method_list_projects(self, client: Openlayer) -> None: + framework = client.governance.frameworks.list_projects( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FrameworkListProjectsResponse, framework, path=["response"]) + + @parametrize + def test_method_list_projects_with_all_params(self, client: Openlayer) -> None: + framework = client.governance.frameworks.list_projects( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page=1, + per_page=1, + ) + assert_matches_type(FrameworkListProjectsResponse, framework, path=["response"]) + + @parametrize + def test_raw_response_list_projects(self, client: Openlayer) -> None: + response = client.governance.frameworks.with_raw_response.list_projects( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = response.parse() + assert_matches_type(FrameworkListProjectsResponse, framework, path=["response"]) + + @parametrize + def test_streaming_response_list_projects(self, client: Openlayer) -> None: + with client.governance.frameworks.with_streaming_response.list_projects( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = response.parse() + assert_matches_type(FrameworkListProjectsResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list_projects(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + client.governance.frameworks.with_raw_response.list_projects( + framework_id="", + ) + + @parametrize + def test_method_list_rules(self, client: Openlayer) -> None: + framework = client.governance.frameworks.list_rules( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FrameworkListRulesResponse, framework, path=["response"]) + + @parametrize + def test_method_list_rules_with_all_params(self, client: Openlayer) -> None: + framework = client.governance.frameworks.list_rules( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page=1, + per_page=1, + ) + assert_matches_type(FrameworkListRulesResponse, framework, path=["response"]) + + @parametrize + def test_raw_response_list_rules(self, client: Openlayer) -> None: + response = client.governance.frameworks.with_raw_response.list_rules( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = response.parse() + assert_matches_type(FrameworkListRulesResponse, framework, path=["response"]) + + @parametrize + def test_streaming_response_list_rules(self, client: Openlayer) -> None: + with client.governance.frameworks.with_streaming_response.list_rules( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = response.parse() + assert_matches_type(FrameworkListRulesResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list_rules(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + client.governance.frameworks.with_raw_response.list_rules( + framework_id="", + ) + + +class TestAsyncFrameworks: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="EU AI Act", + ) + assert_matches_type(FrameworkCreateResponse, framework, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="EU AI Act", + description="Requirements for high-risk AI systems under the EU AI Act.", + enabled=True, + project_selector={ + "match": [ + { + "property": "riskLevel", + "value": ["high", "critical"], + "operator": "operator", + } + ] + }, + tags=["regulation", "eu"], + ) + assert_matches_type(FrameworkCreateResponse, framework, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.frameworks.with_raw_response.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="EU AI Act", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = await response.parse() + assert_matches_type(FrameworkCreateResponse, framework, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.frameworks.with_streaming_response.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="EU AI Act", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = await response.parse() + assert_matches_type(FrameworkCreateResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `workspace_id` but received ''"): + await async_client.governance.frameworks.with_raw_response.create( + workspace_id="", + name="EU AI Act", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FrameworkRetrieveResponse, framework, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.frameworks.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = await response.parse() + assert_matches_type(FrameworkRetrieveResponse, framework, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.frameworks.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = await response.parse() + assert_matches_type(FrameworkRetrieveResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + await async_client.governance.frameworks.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.update( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FrameworkUpdateResponse, framework, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.update( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + avatar={ + "type": "emoji", + "value": "🧭", + }, + description="Requirements for high-risk AI systems under the EU AI Act.", + enabled=True, + extended_description={"foo": "bar"}, + href="href", + name="EU AI Act", + project_selector={ + "match": [ + { + "property": "riskLevel", + "value": ["high", "critical"], + "operator": "operator", + } + ] + }, + tags=["regulation", "eu"], + ) + assert_matches_type(FrameworkUpdateResponse, framework, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.frameworks.with_raw_response.update( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = await response.parse() + assert_matches_type(FrameworkUpdateResponse, framework, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.frameworks.with_streaming_response.update( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = await response.parse() + assert_matches_type(FrameworkUpdateResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + await async_client.governance.frameworks.with_raw_response.update( + framework_id="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FrameworkListResponse, framework, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + asc=True, + completion_operator="is", + completion_value=0, + enabled=True, + include_rule_stats=True, + page=1, + per_page=1, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + search_query="searchQuery", + sort_column="name", + tags=["string"], + ) + assert_matches_type(FrameworkListResponse, framework, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.frameworks.with_raw_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = await response.parse() + assert_matches_type(FrameworkListResponse, framework, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.frameworks.with_streaming_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = await response.parse() + assert_matches_type(FrameworkListResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `workspace_id` but received ''"): + await async_client.governance.frameworks.with_raw_response.list( + workspace_id="", + ) + + @parametrize + async def test_method_export(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.export( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FrameworkExportResponse, framework, path=["response"]) + + @parametrize + async def test_method_export_with_all_params(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.export( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="3fa85f64-5717-4562-b3fc-2c963f66afa6", + ) + assert_matches_type(FrameworkExportResponse, framework, path=["response"]) + + @parametrize + async def test_raw_response_export(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.frameworks.with_raw_response.export( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = await response.parse() + assert_matches_type(FrameworkExportResponse, framework, path=["response"]) + + @parametrize + async def test_streaming_response_export(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.frameworks.with_streaming_response.export( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = await response.parse() + assert_matches_type(FrameworkExportResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_export(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + await async_client.governance.frameworks.with_raw_response.export( + framework_id="", + ) + + @parametrize + async def test_method_list_project_rule_stats(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.list_project_rule_stats( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FrameworkListProjectRuleStatsResponse, framework, path=["response"]) + + @parametrize + async def test_method_list_project_rule_stats_with_all_params(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.list_project_rule_stats( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + asc=True, + page=1, + per_page=1, + sort_column="projectName", + ) + assert_matches_type(FrameworkListProjectRuleStatsResponse, framework, path=["response"]) + + @parametrize + async def test_raw_response_list_project_rule_stats(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.frameworks.with_raw_response.list_project_rule_stats( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = await response.parse() + assert_matches_type(FrameworkListProjectRuleStatsResponse, framework, path=["response"]) + + @parametrize + async def test_streaming_response_list_project_rule_stats(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.frameworks.with_streaming_response.list_project_rule_stats( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = await response.parse() + assert_matches_type(FrameworkListProjectRuleStatsResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list_project_rule_stats(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + await async_client.governance.frameworks.with_raw_response.list_project_rule_stats( + framework_id="", + ) + + @parametrize + async def test_method_list_projects(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.list_projects( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FrameworkListProjectsResponse, framework, path=["response"]) + + @parametrize + async def test_method_list_projects_with_all_params(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.list_projects( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page=1, + per_page=1, + ) + assert_matches_type(FrameworkListProjectsResponse, framework, path=["response"]) + + @parametrize + async def test_raw_response_list_projects(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.frameworks.with_raw_response.list_projects( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = await response.parse() + assert_matches_type(FrameworkListProjectsResponse, framework, path=["response"]) + + @parametrize + async def test_streaming_response_list_projects(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.frameworks.with_streaming_response.list_projects( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = await response.parse() + assert_matches_type(FrameworkListProjectsResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list_projects(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + await async_client.governance.frameworks.with_raw_response.list_projects( + framework_id="", + ) + + @parametrize + async def test_method_list_rules(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.list_rules( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FrameworkListRulesResponse, framework, path=["response"]) + + @parametrize + async def test_method_list_rules_with_all_params(self, async_client: AsyncOpenlayer) -> None: + framework = await async_client.governance.frameworks.list_rules( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page=1, + per_page=1, + ) + assert_matches_type(FrameworkListRulesResponse, framework, path=["response"]) + + @parametrize + async def test_raw_response_list_rules(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.frameworks.with_raw_response.list_rules( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + framework = await response.parse() + assert_matches_type(FrameworkListRulesResponse, framework, path=["response"]) + + @parametrize + async def test_streaming_response_list_rules(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.frameworks.with_streaming_response.list_rules( + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + framework = await response.parse() + assert_matches_type(FrameworkListRulesResponse, framework, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list_rules(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `framework_id` but received ''"): + await async_client.governance.frameworks.with_raw_response.list_rules( + framework_id="", + ) diff --git a/tests/api_resources/governance/test_rule_results.py b/tests/api_resources/governance/test_rule_results.py new file mode 100644 index 00000000..10414236 --- /dev/null +++ b/tests/api_resources/governance/test_rule_results.py @@ -0,0 +1,532 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openlayer import Openlayer, AsyncOpenlayer +from tests.utils import assert_matches_type +from openlayer.types.governance import ( + RuleResultListResponse, + RuleResultUpdateResponse, + RuleResultRetrieveResponse, + RuleResultListEvidenceResponse, + RuleResultCreateEvidenceResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRuleResults: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Openlayer) -> None: + rule_result = client.governance.rule_results.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleResultRetrieveResponse, rule_result, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Openlayer) -> None: + response = client.governance.rule_results.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule_result = response.parse() + assert_matches_type(RuleResultRetrieveResponse, rule_result, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Openlayer) -> None: + with client.governance.rule_results.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule_result = response.parse() + assert_matches_type(RuleResultRetrieveResponse, rule_result, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rule_result_id` but received ''"): + client.governance.rule_results.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: Openlayer) -> None: + rule_result = client.governance.rule_results.update( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleResultUpdateResponse, rule_result, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Openlayer) -> None: + rule_result = client.governance.rule_results.update( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + assignee_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + blocked_by=[ + { + "id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "status": "passing", + } + ], + blocking=[ + { + "id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "status": "passing", + } + ], + deactivated=True, + deactivated_reason="deactivatedReason", + ) + assert_matches_type(RuleResultUpdateResponse, rule_result, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Openlayer) -> None: + response = client.governance.rule_results.with_raw_response.update( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule_result = response.parse() + assert_matches_type(RuleResultUpdateResponse, rule_result, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Openlayer) -> None: + with client.governance.rule_results.with_streaming_response.update( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule_result = response.parse() + assert_matches_type(RuleResultUpdateResponse, rule_result, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rule_result_id` but received ''"): + client.governance.rule_results.with_raw_response.update( + rule_result_id="", + ) + + @parametrize + def test_method_list(self, client: Openlayer) -> None: + rule_result = client.governance.rule_results.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleResultListResponse, rule_result, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Openlayer) -> None: + rule_result = client.governance.rule_results.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + enabled_framework_only=True, + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + include_unframed=True, + page=1, + per_page=1, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + rule_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + scope="project", + search_query="searchQuery", + status="passing", + type="platform", + ) + assert_matches_type(RuleResultListResponse, rule_result, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Openlayer) -> None: + response = client.governance.rule_results.with_raw_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule_result = response.parse() + assert_matches_type(RuleResultListResponse, rule_result, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Openlayer) -> None: + with client.governance.rule_results.with_streaming_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule_result = response.parse() + assert_matches_type(RuleResultListResponse, rule_result, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `workspace_id` but received ''"): + client.governance.rule_results.with_raw_response.list( + workspace_id="", + ) + + @parametrize + def test_method_create_evidence(self, client: Openlayer) -> None: + rule_result = client.governance.rule_results.create_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleResultCreateEvidenceResponse, rule_result, path=["response"]) + + @parametrize + def test_method_create_evidence_with_all_params(self, client: Openlayer) -> None: + rule_result = client.governance.rule_results.create_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + description="description", + name="Model risk assessment 2026", + storage_uri="s3://openlayer-evidence/evidence.pdf", + text="text", + url="https://openlayer.com/evidence", + ) + assert_matches_type(RuleResultCreateEvidenceResponse, rule_result, path=["response"]) + + @parametrize + def test_raw_response_create_evidence(self, client: Openlayer) -> None: + response = client.governance.rule_results.with_raw_response.create_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule_result = response.parse() + assert_matches_type(RuleResultCreateEvidenceResponse, rule_result, path=["response"]) + + @parametrize + def test_streaming_response_create_evidence(self, client: Openlayer) -> None: + with client.governance.rule_results.with_streaming_response.create_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule_result = response.parse() + assert_matches_type(RuleResultCreateEvidenceResponse, rule_result, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create_evidence(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rule_result_id` but received ''"): + client.governance.rule_results.with_raw_response.create_evidence( + rule_result_id="", + ) + + @parametrize + def test_method_list_evidence(self, client: Openlayer) -> None: + rule_result = client.governance.rule_results.list_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleResultListEvidenceResponse, rule_result, path=["response"]) + + @parametrize + def test_method_list_evidence_with_all_params(self, client: Openlayer) -> None: + rule_result = client.governance.rule_results.list_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page=1, + per_page=1, + ) + assert_matches_type(RuleResultListEvidenceResponse, rule_result, path=["response"]) + + @parametrize + def test_raw_response_list_evidence(self, client: Openlayer) -> None: + response = client.governance.rule_results.with_raw_response.list_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule_result = response.parse() + assert_matches_type(RuleResultListEvidenceResponse, rule_result, path=["response"]) + + @parametrize + def test_streaming_response_list_evidence(self, client: Openlayer) -> None: + with client.governance.rule_results.with_streaming_response.list_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule_result = response.parse() + assert_matches_type(RuleResultListEvidenceResponse, rule_result, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list_evidence(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rule_result_id` but received ''"): + client.governance.rule_results.with_raw_response.list_evidence( + rule_result_id="", + ) + + +class TestAsyncRuleResults: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenlayer) -> None: + rule_result = await async_client.governance.rule_results.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleResultRetrieveResponse, rule_result, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.rule_results.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule_result = await response.parse() + assert_matches_type(RuleResultRetrieveResponse, rule_result, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.rule_results.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule_result = await response.parse() + assert_matches_type(RuleResultRetrieveResponse, rule_result, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rule_result_id` but received ''"): + await async_client.governance.rule_results.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenlayer) -> None: + rule_result = await async_client.governance.rule_results.update( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleResultUpdateResponse, rule_result, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncOpenlayer) -> None: + rule_result = await async_client.governance.rule_results.update( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + assignee_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + blocked_by=[ + { + "id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "status": "passing", + } + ], + blocking=[ + { + "id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "status": "passing", + } + ], + deactivated=True, + deactivated_reason="deactivatedReason", + ) + assert_matches_type(RuleResultUpdateResponse, rule_result, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.rule_results.with_raw_response.update( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule_result = await response.parse() + assert_matches_type(RuleResultUpdateResponse, rule_result, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.rule_results.with_streaming_response.update( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule_result = await response.parse() + assert_matches_type(RuleResultUpdateResponse, rule_result, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rule_result_id` but received ''"): + await async_client.governance.rule_results.with_raw_response.update( + rule_result_id="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenlayer) -> None: + rule_result = await async_client.governance.rule_results.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleResultListResponse, rule_result, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenlayer) -> None: + rule_result = await async_client.governance.rule_results.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + enabled_framework_only=True, + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + include_unframed=True, + page=1, + per_page=1, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + rule_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + scope="project", + search_query="searchQuery", + status="passing", + type="platform", + ) + assert_matches_type(RuleResultListResponse, rule_result, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.rule_results.with_raw_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule_result = await response.parse() + assert_matches_type(RuleResultListResponse, rule_result, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.rule_results.with_streaming_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule_result = await response.parse() + assert_matches_type(RuleResultListResponse, rule_result, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `workspace_id` but received ''"): + await async_client.governance.rule_results.with_raw_response.list( + workspace_id="", + ) + + @parametrize + async def test_method_create_evidence(self, async_client: AsyncOpenlayer) -> None: + rule_result = await async_client.governance.rule_results.create_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleResultCreateEvidenceResponse, rule_result, path=["response"]) + + @parametrize + async def test_method_create_evidence_with_all_params(self, async_client: AsyncOpenlayer) -> None: + rule_result = await async_client.governance.rule_results.create_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + description="description", + name="Model risk assessment 2026", + storage_uri="s3://openlayer-evidence/evidence.pdf", + text="text", + url="https://openlayer.com/evidence", + ) + assert_matches_type(RuleResultCreateEvidenceResponse, rule_result, path=["response"]) + + @parametrize + async def test_raw_response_create_evidence(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.rule_results.with_raw_response.create_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule_result = await response.parse() + assert_matches_type(RuleResultCreateEvidenceResponse, rule_result, path=["response"]) + + @parametrize + async def test_streaming_response_create_evidence(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.rule_results.with_streaming_response.create_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule_result = await response.parse() + assert_matches_type(RuleResultCreateEvidenceResponse, rule_result, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create_evidence(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rule_result_id` but received ''"): + await async_client.governance.rule_results.with_raw_response.create_evidence( + rule_result_id="", + ) + + @parametrize + async def test_method_list_evidence(self, async_client: AsyncOpenlayer) -> None: + rule_result = await async_client.governance.rule_results.list_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleResultListEvidenceResponse, rule_result, path=["response"]) + + @parametrize + async def test_method_list_evidence_with_all_params(self, async_client: AsyncOpenlayer) -> None: + rule_result = await async_client.governance.rule_results.list_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page=1, + per_page=1, + ) + assert_matches_type(RuleResultListEvidenceResponse, rule_result, path=["response"]) + + @parametrize + async def test_raw_response_list_evidence(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.rule_results.with_raw_response.list_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule_result = await response.parse() + assert_matches_type(RuleResultListEvidenceResponse, rule_result, path=["response"]) + + @parametrize + async def test_streaming_response_list_evidence(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.rule_results.with_streaming_response.list_evidence( + rule_result_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule_result = await response.parse() + assert_matches_type(RuleResultListEvidenceResponse, rule_result, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list_evidence(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rule_result_id` but received ''"): + await async_client.governance.rule_results.with_raw_response.list_evidence( + rule_result_id="", + ) diff --git a/tests/api_resources/governance/test_rule_stats.py b/tests/api_resources/governance/test_rule_stats.py new file mode 100644 index 00000000..6cd74947 --- /dev/null +++ b/tests/api_resources/governance/test_rule_stats.py @@ -0,0 +1,118 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openlayer import Openlayer, AsyncOpenlayer +from tests.utils import assert_matches_type +from openlayer.types.governance import RuleStatRetrieveResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRuleStats: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Openlayer) -> None: + rule_stat = client.governance.rule_stats.retrieve( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleStatRetrieveResponse, rule_stat, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: Openlayer) -> None: + rule_stat = client.governance.rule_stats.retrieve( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleStatRetrieveResponse, rule_stat, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Openlayer) -> None: + response = client.governance.rule_stats.with_raw_response.retrieve( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule_stat = response.parse() + assert_matches_type(RuleStatRetrieveResponse, rule_stat, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Openlayer) -> None: + with client.governance.rule_stats.with_streaming_response.retrieve( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule_stat = response.parse() + assert_matches_type(RuleStatRetrieveResponse, rule_stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `workspace_id` but received ''"): + client.governance.rule_stats.with_raw_response.retrieve( + workspace_id="", + ) + + +class TestAsyncRuleStats: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenlayer) -> None: + rule_stat = await async_client.governance.rule_stats.retrieve( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleStatRetrieveResponse, rule_stat, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncOpenlayer) -> None: + rule_stat = await async_client.governance.rule_stats.retrieve( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleStatRetrieveResponse, rule_stat, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.rule_stats.with_raw_response.retrieve( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule_stat = await response.parse() + assert_matches_type(RuleStatRetrieveResponse, rule_stat, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.rule_stats.with_streaming_response.retrieve( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule_stat = await response.parse() + assert_matches_type(RuleStatRetrieveResponse, rule_stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `workspace_id` but received ''"): + await async_client.governance.rule_stats.with_raw_response.retrieve( + workspace_id="", + ) diff --git a/tests/api_resources/governance/test_rule_tags.py b/tests/api_resources/governance/test_rule_tags.py new file mode 100644 index 00000000..349b03f9 --- /dev/null +++ b/tests/api_resources/governance/test_rule_tags.py @@ -0,0 +1,118 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openlayer import Openlayer, AsyncOpenlayer +from tests.utils import assert_matches_type +from openlayer.types.governance import RuleTagListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRuleTags: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: Openlayer) -> None: + rule_tag = client.governance.rule_tags.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleTagListResponse, rule_tag, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Openlayer) -> None: + rule_tag = client.governance.rule_tags.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page=1, + per_page=1, + ) + assert_matches_type(RuleTagListResponse, rule_tag, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Openlayer) -> None: + response = client.governance.rule_tags.with_raw_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule_tag = response.parse() + assert_matches_type(RuleTagListResponse, rule_tag, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Openlayer) -> None: + with client.governance.rule_tags.with_streaming_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule_tag = response.parse() + assert_matches_type(RuleTagListResponse, rule_tag, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `workspace_id` but received ''"): + client.governance.rule_tags.with_raw_response.list( + workspace_id="", + ) + + +class TestAsyncRuleTags: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenlayer) -> None: + rule_tag = await async_client.governance.rule_tags.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleTagListResponse, rule_tag, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenlayer) -> None: + rule_tag = await async_client.governance.rule_tags.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + page=1, + per_page=1, + ) + assert_matches_type(RuleTagListResponse, rule_tag, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.rule_tags.with_raw_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule_tag = await response.parse() + assert_matches_type(RuleTagListResponse, rule_tag, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.rule_tags.with_streaming_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule_tag = await response.parse() + assert_matches_type(RuleTagListResponse, rule_tag, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `workspace_id` but received ''"): + await async_client.governance.rule_tags.with_raw_response.list( + workspace_id="", + ) diff --git a/tests/api_resources/governance/test_rules.py b/tests/api_resources/governance/test_rules.py new file mode 100644 index 00000000..f8de60e8 --- /dev/null +++ b/tests/api_resources/governance/test_rules.py @@ -0,0 +1,543 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openlayer import Openlayer, AsyncOpenlayer +from tests.utils import assert_matches_type +from openlayer.types.governance import ( + RuleListResponse, + RuleCreateResponse, + RuleUpdateResponse, + RuleRetrieveResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRules: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Openlayer) -> None: + rule = client.governance.rules.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="Monitoring enabled", + scope="project", + type="platform", + ) + assert_matches_type(RuleCreateResponse, rule, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Openlayer) -> None: + rule = client.governance.rules.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="Monitoring enabled", + scope="project", + type="platform", + assignee_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + automation_params={"foo": "bar"}, + automation_type="monitoring_mode_enabled", + deactivated=True, + description="Each project must have Openlayer monitoring mode enabled.", + evidence_type="document", + renewal_cadence_days=90, + tag_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + ) + assert_matches_type(RuleCreateResponse, rule, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Openlayer) -> None: + response = client.governance.rules.with_raw_response.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="Monitoring enabled", + scope="project", + type="platform", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule = response.parse() + assert_matches_type(RuleCreateResponse, rule, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Openlayer) -> None: + with client.governance.rules.with_streaming_response.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="Monitoring enabled", + scope="project", + type="platform", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule = response.parse() + assert_matches_type(RuleCreateResponse, rule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `workspace_id` but received ''"): + client.governance.rules.with_raw_response.create( + workspace_id="", + name="Monitoring enabled", + scope="project", + type="platform", + ) + + @parametrize + def test_method_retrieve(self, client: Openlayer) -> None: + rule = client.governance.rules.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleRetrieveResponse, rule, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Openlayer) -> None: + response = client.governance.rules.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule = response.parse() + assert_matches_type(RuleRetrieveResponse, rule, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Openlayer) -> None: + with client.governance.rules.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule = response.parse() + assert_matches_type(RuleRetrieveResponse, rule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rule_id` but received ''"): + client.governance.rules.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: Openlayer) -> None: + rule = client.governance.rules.update( + rule_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleUpdateResponse, rule, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Openlayer) -> None: + rule = client.governance.rules.update( + rule_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + assignee_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + deactivated=True, + description="Each project must have Openlayer monitoring mode enabled.", + name="Monitoring enabled", + renewal_cadence_days=90, + tag_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + ) + assert_matches_type(RuleUpdateResponse, rule, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Openlayer) -> None: + response = client.governance.rules.with_raw_response.update( + rule_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule = response.parse() + assert_matches_type(RuleUpdateResponse, rule, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Openlayer) -> None: + with client.governance.rules.with_streaming_response.update( + rule_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule = response.parse() + assert_matches_type(RuleUpdateResponse, rule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rule_id` but received ''"): + client.governance.rules.with_raw_response.update( + rule_id="", + ) + + @parametrize + def test_method_list(self, client: Openlayer) -> None: + rule = client.governance.rules.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleListResponse, rule, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Openlayer) -> None: + rule = client.governance.rules.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + asc=True, + assignee_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + deactivated=True, + enabled_framework_only=True, + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + group="open", + include_results=True, + include_unframed=True, + page=1, + per_page=1, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + scope="project", + search_query="searchQuery", + sort_by="name", + status="passing", + tags=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + type="platform", + ) + assert_matches_type(RuleListResponse, rule, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Openlayer) -> None: + response = client.governance.rules.with_raw_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule = response.parse() + assert_matches_type(RuleListResponse, rule, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Openlayer) -> None: + with client.governance.rules.with_streaming_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule = response.parse() + assert_matches_type(RuleListResponse, rule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `workspace_id` but received ''"): + client.governance.rules.with_raw_response.list( + workspace_id="", + ) + + @parametrize + def test_method_delete(self, client: Openlayer) -> None: + rule = client.governance.rules.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert rule is None + + @parametrize + def test_raw_response_delete(self, client: Openlayer) -> None: + response = client.governance.rules.with_raw_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule = response.parse() + assert rule is None + + @parametrize + def test_streaming_response_delete(self, client: Openlayer) -> None: + with client.governance.rules.with_streaming_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule = response.parse() + assert rule is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rule_id` but received ''"): + client.governance.rules.with_raw_response.delete( + "", + ) + + +class TestAsyncRules: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenlayer) -> None: + rule = await async_client.governance.rules.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="Monitoring enabled", + scope="project", + type="platform", + ) + assert_matches_type(RuleCreateResponse, rule, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenlayer) -> None: + rule = await async_client.governance.rules.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="Monitoring enabled", + scope="project", + type="platform", + assignee_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + automation_params={"foo": "bar"}, + automation_type="monitoring_mode_enabled", + deactivated=True, + description="Each project must have Openlayer monitoring mode enabled.", + evidence_type="document", + renewal_cadence_days=90, + tag_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + ) + assert_matches_type(RuleCreateResponse, rule, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.rules.with_raw_response.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="Monitoring enabled", + scope="project", + type="platform", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule = await response.parse() + assert_matches_type(RuleCreateResponse, rule, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.rules.with_streaming_response.create( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="Monitoring enabled", + scope="project", + type="platform", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule = await response.parse() + assert_matches_type(RuleCreateResponse, rule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `workspace_id` but received ''"): + await async_client.governance.rules.with_raw_response.create( + workspace_id="", + name="Monitoring enabled", + scope="project", + type="platform", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenlayer) -> None: + rule = await async_client.governance.rules.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleRetrieveResponse, rule, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.rules.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule = await response.parse() + assert_matches_type(RuleRetrieveResponse, rule, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.rules.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule = await response.parse() + assert_matches_type(RuleRetrieveResponse, rule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rule_id` but received ''"): + await async_client.governance.rules.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncOpenlayer) -> None: + rule = await async_client.governance.rules.update( + rule_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleUpdateResponse, rule, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncOpenlayer) -> None: + rule = await async_client.governance.rules.update( + rule_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + assignee_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + deactivated=True, + description="Each project must have Openlayer monitoring mode enabled.", + name="Monitoring enabled", + renewal_cadence_days=90, + tag_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + ) + assert_matches_type(RuleUpdateResponse, rule, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.rules.with_raw_response.update( + rule_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule = await response.parse() + assert_matches_type(RuleUpdateResponse, rule, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.rules.with_streaming_response.update( + rule_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule = await response.parse() + assert_matches_type(RuleUpdateResponse, rule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rule_id` but received ''"): + await async_client.governance.rules.with_raw_response.update( + rule_id="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenlayer) -> None: + rule = await async_client.governance.rules.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(RuleListResponse, rule, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenlayer) -> None: + rule = await async_client.governance.rules.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + asc=True, + assignee_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + deactivated=True, + enabled_framework_only=True, + framework_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + group="open", + include_results=True, + include_unframed=True, + page=1, + per_page=1, + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + scope="project", + search_query="searchQuery", + sort_by="name", + status="passing", + tags=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + type="platform", + ) + assert_matches_type(RuleListResponse, rule, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.rules.with_raw_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule = await response.parse() + assert_matches_type(RuleListResponse, rule, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.rules.with_streaming_response.list( + workspace_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule = await response.parse() + assert_matches_type(RuleListResponse, rule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `workspace_id` but received ''"): + await async_client.governance.rules.with_raw_response.list( + workspace_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenlayer) -> None: + rule = await async_client.governance.rules.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert rule is None + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.governance.rules.with_raw_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + rule = await response.parse() + assert rule is None + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenlayer) -> None: + async with async_client.governance.rules.with_streaming_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + rule = await response.parse() + assert rule is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `rule_id` but received ''"): + await async_client.governance.rules.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/storage/test_presigned_url.py b/tests/api_resources/storage/test_presigned_url.py index 4f2daa29..6ef87cc8 100644 --- a/tests/api_resources/storage/test_presigned_url.py +++ b/tests/api_resources/storage/test_presigned_url.py @@ -9,7 +9,10 @@ from openlayer import Openlayer, AsyncOpenlayer from tests.utils import assert_matches_type -from openlayer.types.storage import PresignedURLCreateResponse +from openlayer.types.storage import ( + PresignedURLCreateResponse, + PresignedURLRetrieveResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -48,6 +51,37 @@ def test_streaming_response_create(self, client: Openlayer) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_retrieve(self, client: Openlayer) -> None: + presigned_url = client.storage.presigned_url.retrieve( + storage_uri="storageUri", + ) + assert_matches_type(PresignedURLRetrieveResponse, presigned_url, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Openlayer) -> None: + response = client.storage.presigned_url.with_raw_response.retrieve( + storage_uri="storageUri", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + presigned_url = response.parse() + assert_matches_type(PresignedURLRetrieveResponse, presigned_url, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Openlayer) -> None: + with client.storage.presigned_url.with_streaming_response.retrieve( + storage_uri="storageUri", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + presigned_url = response.parse() + assert_matches_type(PresignedURLRetrieveResponse, presigned_url, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncPresignedURL: parametrize = pytest.mark.parametrize( @@ -84,3 +118,34 @@ async def test_streaming_response_create(self, async_client: AsyncOpenlayer) -> assert_matches_type(PresignedURLCreateResponse, presigned_url, path=["response"]) assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenlayer) -> None: + presigned_url = await async_client.storage.presigned_url.retrieve( + storage_uri="storageUri", + ) + assert_matches_type(PresignedURLRetrieveResponse, presigned_url, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.storage.presigned_url.with_raw_response.retrieve( + storage_uri="storageUri", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + presigned_url = await response.parse() + assert_matches_type(PresignedURLRetrieveResponse, presigned_url, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenlayer) -> None: + async with async_client.storage.presigned_url.with_streaming_response.retrieve( + storage_uri="storageUri", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + presigned_url = await response.parse() + assert_matches_type(PresignedURLRetrieveResponse, presigned_url, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_background_tasks.py b/tests/api_resources/test_background_tasks.py new file mode 100644 index 00000000..013fbb58 --- /dev/null +++ b/tests/api_resources/test_background_tasks.py @@ -0,0 +1,100 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openlayer import Openlayer, AsyncOpenlayer +from tests.utils import assert_matches_type +from openlayer.types import BackgroundTaskRetrieveResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestBackgroundTasks: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Openlayer) -> None: + background_task = client.background_tasks.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(BackgroundTaskRetrieveResponse, background_task, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Openlayer) -> None: + response = client.background_tasks.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + background_task = response.parse() + assert_matches_type(BackgroundTaskRetrieveResponse, background_task, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Openlayer) -> None: + with client.background_tasks.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + background_task = response.parse() + assert_matches_type(BackgroundTaskRetrieveResponse, background_task, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Openlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `task_id` but received ''"): + client.background_tasks.with_raw_response.retrieve( + "", + ) + + +class TestAsyncBackgroundTasks: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenlayer) -> None: + background_task = await async_client.background_tasks.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(BackgroundTaskRetrieveResponse, background_task, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenlayer) -> None: + response = await async_client.background_tasks.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + background_task = await response.parse() + assert_matches_type(BackgroundTaskRetrieveResponse, background_task, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenlayer) -> None: + async with async_client.background_tasks.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + background_task = await response.parse() + assert_matches_type(BackgroundTaskRetrieveResponse, background_task, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenlayer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `task_id` but received ''"): + await async_client.background_tasks.with_raw_response.retrieve( + "", + )