From 45d2046100d706806077c0357e0eb6d262d0a53e Mon Sep 17 00:00:00 2001 From: Vinicius Mello Date: Wed, 9 Sep 2026 21:13:14 -0300 Subject: [PATCH 1/3] ci(stlc): back-sync pushes staging main as the automation identity Staging main is getting a pull-request rule whose only bypass is the automation's repository role. The back-sync job pushed with GITHUB_TOKEN, which cannot be a bypass actor (GitHub Actions is not an org-owned app), so push with PRODUCTION_REPO_TOKEN (the service account) instead. Token pushes also trigger seal-dispatch, so the config repo re-seals immediately after a back-sync rather than at the next scheduled run. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017oDfpQ4KpmpTzYm2fZ9eaL --- .github/workflows/stlc-sync.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 From c22edc11dbbd5fbbb8128b94d370d74607744ff5 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Mon, 21 Sep 2026 05:07:39 +0000 Subject: [PATCH 2/3] docs(closes OPEN-9410): expose the governance framework endpoints Stainless-Generated-From: 1f0448fc16d8e9f7266cdc82d477f4cdf7c9d3dc --- scripts/mock | 2 +- src/openlayer/types/inference_pipeline_retrieve_response.py | 2 +- src/openlayer/types/inference_pipeline_update_response.py | 2 +- src/openlayer/types/project_create_response.py | 2 +- src/openlayer/types/project_list_response.py | 2 +- src/openlayer/types/project_update_response.py | 2 +- .../types/projects/inference_pipeline_create_response.py | 2 +- .../types/projects/inference_pipeline_list_response.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/mock b/scripts/mock index 099a4c9c..4e11791c 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/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( From cee38ac20e53320117d5fe3168b44bccaa908fe4 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Mon, 21 Sep 2026 16:18:11 +0000 Subject: [PATCH 3/3] feat(governance): add the governance write endpoints and generate governance SDK methods Stainless-Generated-From: 41f13ad6c9d6776832bb12c66945609f826bf66a --- .stats.yml | 2 +- api.md | 149 +- scripts/mock | 2 +- src/openlayer/_client.py | 85 +- src/openlayer/resources/__init__.py | 28 + src/openlayer/resources/background_tasks.py | 172 +++ .../resources/governance/__init__.py | 89 ++ .../governance/frameworks/__init__.py | 61 + .../governance/frameworks/documents.py | 302 ++++ .../governance/frameworks/frameworks.py | 1327 +++++++++++++++++ .../governance/frameworks/sections.py | 253 ++++ .../governance/frameworks/subsections.py | 245 +++ .../resources/governance/governance.py | 230 +++ .../resources/governance/rule_results.py | 785 ++++++++++ .../resources/governance/rule_stats.py | 205 +++ .../resources/governance/rule_tags.py | 205 +++ src/openlayer/resources/governance/rules.py | 885 +++++++++++ .../resources/storage/presigned_url.py | 111 +- src/openlayer/types/__init__.py | 1 + .../background_task_retrieve_response.py | 41 + src/openlayer/types/governance/__init__.py | 43 + .../governance/framework_create_params.py | 67 + .../governance/framework_create_response.py | 195 +++ .../governance/framework_export_params.py | 15 + .../governance/framework_export_response.py | 20 + .../types/governance/framework_list_params.py | 55 + ...ramework_list_project_rule_stats_params.py | 37 + ...mework_list_project_rule_stats_response.py | 117 ++ .../framework_list_projects_params.py | 17 + .../framework_list_projects_response.py | 113 ++ .../governance/framework_list_response.py | 200 +++ .../governance/framework_list_rules_params.py | 17 + .../framework_list_rules_response.py | 242 +++ .../governance/framework_retrieve_response.py | 195 +++ .../governance/framework_update_params.py | 84 ++ .../governance/framework_update_response.py | 195 +++ .../types/governance/frameworks/__init__.py | 11 + .../frameworks/document_list_params.py | 17 + .../frameworks/document_list_response.py | 217 +++ .../frameworks/document_retrieve_response.py | 206 +++ .../frameworks/section_list_rules_params.py | 31 + .../frameworks/section_list_rules_response.py | 242 +++ .../subsection_list_rules_params.py | 28 + .../subsection_list_rules_response.py | 242 +++ .../types/governance/rule_create_params.py | 66 + .../types/governance/rule_create_response.py | 237 +++ .../types/governance/rule_list_params.py | 71 + .../types/governance/rule_list_response.py | 242 +++ .../rule_result_create_evidence_params.py | 30 + .../rule_result_create_evidence_response.py | 42 + .../rule_result_list_evidence_params.py | 17 + .../rule_result_list_evidence_response.py | 46 + .../governance/rule_result_list_params.py | 44 + .../governance/rule_result_list_response.py | 82 + .../rule_result_retrieve_response.py | 78 + .../governance/rule_result_update_params.py | 41 + .../governance/rule_result_update_response.py | 78 + .../governance/rule_retrieve_response.py | 237 +++ .../governance/rule_stat_retrieve_params.py | 17 + .../governance/rule_stat_retrieve_response.py | 211 +++ .../types/governance/rule_tag_list_params.py | 17 + .../governance/rule_tag_list_response.py | 40 + .../types/governance/rule_update_params.py | 39 + .../types/governance/rule_update_response.py | 237 +++ src/openlayer/types/storage/__init__.py | 2 + .../storage/presigned_url_retrieve_params.py | 14 + .../presigned_url_retrieve_response.py | 10 + tests/api_resources/governance/__init__.py | 1 + .../governance/frameworks/__init__.py | 1 + .../governance/frameworks/test_documents.py | 214 +++ .../governance/frameworks/test_sections.py | 148 ++ .../governance/frameworks/test_subsections.py | 146 ++ .../governance/test_frameworks.py | 851 +++++++++++ .../governance/test_rule_results.py | 532 +++++++ .../governance/test_rule_stats.py | 118 ++ .../governance/test_rule_tags.py | 118 ++ tests/api_resources/governance/test_rules.py | 543 +++++++ .../storage/test_presigned_url.py | 67 +- tests/api_resources/test_background_tasks.py | 100 ++ 79 files changed, 12515 insertions(+), 6 deletions(-) create mode 100644 src/openlayer/resources/background_tasks.py create mode 100644 src/openlayer/resources/governance/__init__.py create mode 100644 src/openlayer/resources/governance/frameworks/__init__.py create mode 100644 src/openlayer/resources/governance/frameworks/documents.py create mode 100644 src/openlayer/resources/governance/frameworks/frameworks.py create mode 100644 src/openlayer/resources/governance/frameworks/sections.py create mode 100644 src/openlayer/resources/governance/frameworks/subsections.py create mode 100644 src/openlayer/resources/governance/governance.py create mode 100644 src/openlayer/resources/governance/rule_results.py create mode 100644 src/openlayer/resources/governance/rule_stats.py create mode 100644 src/openlayer/resources/governance/rule_tags.py create mode 100644 src/openlayer/resources/governance/rules.py create mode 100644 src/openlayer/types/background_task_retrieve_response.py create mode 100644 src/openlayer/types/governance/__init__.py create mode 100644 src/openlayer/types/governance/framework_create_params.py create mode 100644 src/openlayer/types/governance/framework_create_response.py create mode 100644 src/openlayer/types/governance/framework_export_params.py create mode 100644 src/openlayer/types/governance/framework_export_response.py create mode 100644 src/openlayer/types/governance/framework_list_params.py create mode 100644 src/openlayer/types/governance/framework_list_project_rule_stats_params.py create mode 100644 src/openlayer/types/governance/framework_list_project_rule_stats_response.py create mode 100644 src/openlayer/types/governance/framework_list_projects_params.py create mode 100644 src/openlayer/types/governance/framework_list_projects_response.py create mode 100644 src/openlayer/types/governance/framework_list_response.py create mode 100644 src/openlayer/types/governance/framework_list_rules_params.py create mode 100644 src/openlayer/types/governance/framework_list_rules_response.py create mode 100644 src/openlayer/types/governance/framework_retrieve_response.py create mode 100644 src/openlayer/types/governance/framework_update_params.py create mode 100644 src/openlayer/types/governance/framework_update_response.py create mode 100644 src/openlayer/types/governance/frameworks/__init__.py create mode 100644 src/openlayer/types/governance/frameworks/document_list_params.py create mode 100644 src/openlayer/types/governance/frameworks/document_list_response.py create mode 100644 src/openlayer/types/governance/frameworks/document_retrieve_response.py create mode 100644 src/openlayer/types/governance/frameworks/section_list_rules_params.py create mode 100644 src/openlayer/types/governance/frameworks/section_list_rules_response.py create mode 100644 src/openlayer/types/governance/frameworks/subsection_list_rules_params.py create mode 100644 src/openlayer/types/governance/frameworks/subsection_list_rules_response.py create mode 100644 src/openlayer/types/governance/rule_create_params.py create mode 100644 src/openlayer/types/governance/rule_create_response.py create mode 100644 src/openlayer/types/governance/rule_list_params.py create mode 100644 src/openlayer/types/governance/rule_list_response.py create mode 100644 src/openlayer/types/governance/rule_result_create_evidence_params.py create mode 100644 src/openlayer/types/governance/rule_result_create_evidence_response.py create mode 100644 src/openlayer/types/governance/rule_result_list_evidence_params.py create mode 100644 src/openlayer/types/governance/rule_result_list_evidence_response.py create mode 100644 src/openlayer/types/governance/rule_result_list_params.py create mode 100644 src/openlayer/types/governance/rule_result_list_response.py create mode 100644 src/openlayer/types/governance/rule_result_retrieve_response.py create mode 100644 src/openlayer/types/governance/rule_result_update_params.py create mode 100644 src/openlayer/types/governance/rule_result_update_response.py create mode 100644 src/openlayer/types/governance/rule_retrieve_response.py create mode 100644 src/openlayer/types/governance/rule_stat_retrieve_params.py create mode 100644 src/openlayer/types/governance/rule_stat_retrieve_response.py create mode 100644 src/openlayer/types/governance/rule_tag_list_params.py create mode 100644 src/openlayer/types/governance/rule_tag_list_response.py create mode 100644 src/openlayer/types/governance/rule_update_params.py create mode 100644 src/openlayer/types/governance/rule_update_response.py create mode 100644 src/openlayer/types/storage/presigned_url_retrieve_params.py create mode 100644 src/openlayer/types/storage/presigned_url_retrieve_response.py create mode 100644 tests/api_resources/governance/__init__.py create mode 100644 tests/api_resources/governance/frameworks/__init__.py create mode 100644 tests/api_resources/governance/frameworks/test_documents.py create mode 100644 tests/api_resources/governance/frameworks/test_sections.py create mode 100644 tests/api_resources/governance/frameworks/test_subsections.py create mode 100644 tests/api_resources/governance/test_frameworks.py create mode 100644 tests/api_resources/governance/test_rule_results.py create mode 100644 tests/api_resources/governance/test_rule_stats.py create mode 100644 tests/api_resources/governance/test_rule_tags.py create mode 100644 tests/api_resources/governance/test_rules.py create mode 100644 tests/api_resources/test_background_tasks.py 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 4e11791c..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/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( + "", + )