diff --git a/.codegen.json b/.codegen.json index 07d77d1ce..ca539bb99 100644 --- a/.codegen.json +++ b/.codegen.json @@ -18,7 +18,7 @@ ".gitattributes", ".github/actions/**/*.yml", ".github/dependabot.yml", - ".github/workflows/{external-message,integration-tests,push,sync-label,tagging,test}.yml", + ".github/workflows/{push,sync-label,tagging,test}.yml", ".gitignore", ".package.json", ".readthedocs.yaml", diff --git a/.codegen/_last_sha b/.codegen/_last_sha index adf5c54fe..2bcd27342 100644 --- a/.codegen/_last_sha +++ b/.codegen/_last_sha @@ -1 +1 @@ -947b25ec08215d066e16a0201102d07eccd5f001 \ No newline at end of file +d83d919e266fa322e65e08d5312d5c4a4b6e465c \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2ebbd89c3..e9fd509cb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -64,8 +64,8 @@ ## How is this tested? ")) | .id') - echo "Previous comment IDs: $previous_comment_ids" - # Iterate over each comment ID and delete the comment - if [ ! -z "$previous_comment_ids" ]; then - echo "$previous_comment_ids" | while read -r comment_id; do - echo "Deleting comment with ID: $comment_id" - gh api "repos/${{ github.repository }}/issues/comments/$comment_id" -X DELETE - done - fi - - - name: Comment on PR - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COMMIT_SHA: ${{ github.event.pull_request.head.sha }} - run: | - gh pr comment ${{ github.event.pull_request.number }} --body \ - " - If integration tests don't run automatically, an authorized user can run them manually by following the instructions below: - - Trigger: - [go/deco-tests-run/sdk-py](https://go/deco-tests-run/sdk-py) - - Inputs: - * PR number: ${{github.event.pull_request.number}} - * Commit SHA: \`${{ env.COMMIT_SHA }}\` - - Checks will be approved automatically on success. - " diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml deleted file mode 100644 index 425ce6f05..000000000 --- a/.github/workflows/integration-tests.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: Integration Tests - -on: - - pull_request: - types: [opened, synchronize] - - merge_group: - - -jobs: - check-token: - name: Check secrets access - - runs-on: - group: databricks-deco-testing-runner-group - labels: ubuntu-latest-deco - - environment: "test-trigger-is" - outputs: - has_token: ${{ steps.set-token-status.outputs.has_token }} - steps: - - name: Check if required secrets are set - id: set-token-status - run: | - if [ -z "${{ secrets.DECO_WORKFLOW_TRIGGER_APP_ID }}" ] || [ -z "${{ secrets.DECO_TEST_APPROVAL_APP_ID }}" ]; then - echo "Required secrets are missing. User has no access to secrets." - echo "has_token=false" >> $GITHUB_OUTPUT - else - echo "All required secrets are set. User has access to secrets." - echo "has_token=true" >> $GITHUB_OUTPUT - fi - - trigger-tests: - name: Trigger Tests - - runs-on: - group: databricks-release-runner-group-emu-access - labels: linux-ubuntu-latest-emu-access - - needs: check-token - if: github.event_name == 'pull_request' && needs.check-token.outputs.has_token == 'true' - environment: "test-trigger-is" - - steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - - - name: Generate GitHub App Token for Workflow Trigger - id: generate-token - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 - with: - app-id: ${{ secrets.DECO_WORKFLOW_TRIGGER_APP_ID }} - private-key: ${{ secrets.DECO_WORKFLOW_TRIGGER_PRIVATE_KEY }} - owner: ${{ secrets.ORG_NAME }} - repositories: ${{secrets.REPO_NAME}} - - - name: Trigger Workflow in Another Repo - env: - GH_TOKEN: ${{ steps.generate-token.outputs.token }} - run: | - gh workflow run sdk-py-isolated-pr.yml -R ${{ secrets.ORG_NAME }}/${{secrets.REPO_NAME}} \ - --ref main \ - -f pull_request_number=${{ github.event.pull_request.number }} \ - -f commit_sha=${{ github.event.pull_request.head.sha }} - - # Statuses and checks apply to specific commits (by hash). - # Enforcement of required checks is done both at the PR level and the merge queue level. - # In case of multiple commits in a single PR, the hash of the squashed commit - # will not match the one for the latest (approved) commit in the PR. - # We auto approve the check for the merge queue for two reasons: - # * Queue times out due to duration of tests. - # * Avoid running integration tests twice, since it was already run at the tip of the branch before squashing. - auto-approve: - if: github.event_name == 'merge_group' - - runs-on: - group: databricks-deco-testing-runner-group - labels: ubuntu-latest-deco - - permissions: - checks: write - contents: read - - steps: - - name: Auto-approve Check for Merge Queue - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 - with: - script: | - await github.rest.checks.create({ - owner: context.repo.owner, - repo: context.repo.repo, - name: 'Integration Tests', - head_sha: context.sha, - status: 'completed', - conclusion: 'success', - output: { - title: 'Integration Tests', - summary: 'Auto-approved for merge queue (tests already passed on PR)' - } - }); diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d0b8931d..bd6473598 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Version changelog +## Release v0.141.0 (2026-09-23) + +### API Changes +* Add `databricks.sdk.service.mason` package. +* Add [w.mason](https://databricks-sdk-py.readthedocs.io/en/latest/workspace/mason/mason.html) workspace-level service. +* Add `service_credential` field for `databricks.sdk.service.catalog.ModelProviderServiceConfigGeminiEnterpriseProviderDirectConfig`. +* Add `secret_reference` field for `databricks.sdk.service.catalog.ModelProviderServiceConfigProviderSecret`. + ## Release v0.140.0 (2026-09-19) ### API Changes diff --git a/Makefile b/Makefile index 1ad83b659..c32cb1049 100644 --- a/Makefile +++ b/Makefile @@ -17,13 +17,7 @@ lint: uv run ruff format --check databricks tests test: - uv run pytest -m 'not integration and not benchmark' --cov=databricks --cov-report html tests - -integration: - uv run pytest -n auto -m 'integration and not benchmark' --reruns 4 --dist loadgroup --cov=databricks --cov-report html tests - -benchmark: - uv run pytest -m 'benchmark' tests + uv run pytest --cov=databricks --cov-report html tests coverage: test open htmlcov/index.html diff --git a/databricks/sdk/__init__.py b/databricks/sdk/__init__.py index 39883474f..5e670ff81 100644 --- a/databricks/sdk/__init__.py +++ b/databricks/sdk/__init__.py @@ -31,6 +31,7 @@ from databricks.sdk.service import jobs as pkg_jobs from databricks.sdk.service import knowledgeassistants as pkg_knowledgeassistants from databricks.sdk.service import marketplace as pkg_marketplace +from databricks.sdk.service import mason as pkg_mason from databricks.sdk.service import ml as pkg_ml from databricks.sdk.service import networking as pkg_networking from databricks.sdk.service import oauth2 as pkg_oauth2 @@ -153,6 +154,7 @@ from databricks.sdk.service.settings import LlmProxyPartnerPoweredEnforceAPI from databricks.sdk.service.settings import LlmProxyPartnerPoweredWorkspaceAPI from databricks.sdk.service.billing import LogDeliveryAPI +from databricks.sdk.service.mason import MasonAPI from databricks.sdk.service.ml import MaterializedFeaturesAPI from databricks.sdk.service.catalog import AccountMetastoreAssignmentsAPI from databricks.sdk.service.catalog import MetastoresAPI @@ -426,6 +428,7 @@ def __init__( self._lakeview = pkg_dashboards.LakeviewAPI(self._api_client) self._lakeview_embedded = pkg_dashboards.LakeviewEmbeddedAPI(self._api_client) self._libraries = pkg_compute.LibrariesAPI(self._api_client) + self._mason = pkg_mason.MasonAPI(self._api_client) self._materialized_features = pkg_ml.MaterializedFeaturesAPI(self._api_client) self._metastores = pkg_catalog.MetastoresAPI(self._api_client) self._model_registry = pkg_ml.ModelRegistryAPI(self._api_client) @@ -837,6 +840,11 @@ def libraries(self) -> pkg_compute.LibrariesAPI: """The Libraries API allows you to install and uninstall libraries and get the status of libraries on a cluster.""" return self._libraries + @property + def mason(self) -> pkg_mason.MasonAPI: + """APIs for managing agent memory and durable session state.""" + return self._mason + @property def materialized_features(self) -> pkg_ml.MaterializedFeaturesAPI: """Materialized Features are columns in tables and views that can be directly used as features to train and serve ML models.""" diff --git a/databricks/sdk/service/aisearch.py b/databricks/sdk/service/aisearch.py index 7c2ae59ed..001a97ffa 100644 --- a/databricks/sdk/service/aisearch.py +++ b/databricks/sdk/service/aisearch.py @@ -1620,7 +1620,8 @@ def query_index( :param filters_json: str (optional) JSON string describing query filters (e.g. ``{"id >": 5}``). :param max_results: int (optional) - Maximum number of results to return (the legacy ``num_results``). Defaults to 10. + Maximum number of results to return (the legacy ``num_results``). Defaults to 10. Prefer + ``page_size``; when both are set, ``page_size`` takes precedence. :param query_columns: List[str] (optional) Text columns to search for ``query_text``. When empty, all text columns are searched. :param query_text: str (optional) diff --git a/databricks/sdk/service/apps.py b/databricks/sdk/service/apps.py index 1397914e0..5bf07fb60 100644 --- a/databricks/sdk/service/apps.py +++ b/databricks/sdk/service/apps.py @@ -50,10 +50,12 @@ class App: budget_policy_id: Optional[str] = None compute_max_instances: Optional[int] = None - """Maximum number of app instances. Must be set together with ``compute_min_instances``.""" + """Maximum number of app instances the app is configured to run. Must be set together with + ``compute_min_instances``.""" compute_min_instances: Optional[int] = None - """Minimum number of app instances. Must be set together with ``compute_max_instances``.""" + """Minimum number of app instances the app is configured to run. Must be set together with + ``compute_max_instances``.""" compute_size: Optional[ComputeSize] = None @@ -1677,10 +1679,12 @@ class AppUpdate: budget_policy_id: Optional[str] = None compute_max_instances: Optional[int] = None - """Maximum number of app instances. Must be set together with ``compute_min_instances``.""" + """Maximum number of app instances the app is configured to run. Must be set together with + ``compute_min_instances``.""" compute_min_instances: Optional[int] = None - """Minimum number of app instances. Must be set together with ``compute_max_instances``.""" + """Minimum number of app instances the app is configured to run. Must be set together with + ``compute_max_instances``.""" compute_size: Optional[ComputeSize] = None @@ -1831,7 +1835,7 @@ class ApplicationStatus: """Application status message""" running_instances: Optional[int] = None - """The number of running instances of this application.""" + """The number of app instances whose application process is running.""" state: Optional[ApplicationState] = None """State of the application.""" @@ -1887,7 +1891,7 @@ class ComputeState(Enum): @dataclass class ComputeStatus: active_instances: Optional[int] = None - """The number of compute instances used and billed for this application.""" + """The number of active compute instances currently used and billed for this application.""" message: Optional[str] = None """Compute status message""" diff --git a/databricks/sdk/service/bundledeployments.py b/databricks/sdk/service/bundledeployments.py index 5892a6f05..8fb0568f4 100644 --- a/databricks/sdk/service/bundledeployments.py +++ b/databricks/sdk/service/bundledeployments.py @@ -1107,11 +1107,9 @@ def complete_version( :param name: str The name of the version to complete. Format: deployments/{deployment_id}/versions/{version_id} :param completion_reason: :class:`VersionComplete` - The reason for completing the version. Must be a terminal reason: VERSION_COMPLETE_SUCCESS, - VERSION_COMPLETE_FAILURE, or VERSION_COMPLETE_FORCE_ABORT. + The reason for completing the version. Must be VERSION_COMPLETE_SUCCESS or VERSION_COMPLETE_FAILURE. :param force: bool (optional) - If true, force-completes the version even if the caller is not the original creator. The - completion_reason must be VERSION_COMPLETE_FORCE_ABORT when force is true. + If true, force-completes the version even if the caller is not the original creator. :returns: :class:`Version` """ diff --git a/databricks/sdk/service/catalog.py b/databricks/sdk/service/catalog.py index c45e6ef0c..6e94550cc 100644 --- a/databricks/sdk/service/catalog.py +++ b/databricks/sdk/service/catalog.py @@ -8133,6 +8133,13 @@ class ModelProviderServiceConfigGeminiEnterpriseProviderDirectConfig: region: Optional[str] = None """GCP region of the Gemini Enterprise endpoint (e.g., ``us-central1``). Required on Create.""" + service_credential: Optional[ModelProviderServiceConfigServiceCredential] = None + """Reference to a Unity Catalog service credential authorizing Gemini Enterprise requests. On + Create, supply ``service_credential.name`` as ``credentials/{name}``; required when using + service-credential authentication and mutually exclusive with ``api_key``. The credential is + referenced by name; its value is not carried here. On read, the resolved ``id`` and + ``is_deleted`` are also populated. Supported only on GCP-hosted workspaces.""" + def as_dict(self) -> dict: """Serializes the ModelProviderServiceConfigGeminiEnterpriseProviderDirectConfig into a dictionary suitable for use as a JSON request body.""" body = {} @@ -8142,6 +8149,8 @@ def as_dict(self) -> dict: body["project_id"] = self.project_id if self.region is not None: body["region"] = self.region + if self.service_credential: + body["service_credential"] = self.service_credential.as_dict() return body def as_shallow_dict(self) -> dict: @@ -8153,6 +8162,8 @@ def as_shallow_dict(self) -> dict: body["project_id"] = self.project_id if self.region is not None: body["region"] = self.region + if self.service_credential: + body["service_credential"] = self.service_credential return body @classmethod @@ -8162,6 +8173,7 @@ def from_dict(cls, d: Dict[str, Any]) -> ModelProviderServiceConfigGeminiEnterpr api_key=_from_dict(d, "api_key", ModelProviderServiceConfigProviderSecret), project_id=d.get("project_id", None), region=d.get("region", None), + service_credential=_from_dict(d, "service_credential", ModelProviderServiceConfigServiceCredential), ) @@ -8387,11 +8399,19 @@ class ModelProviderServiceConfigProviderSecret: responses omit ``plaintext``; the enclosing secret object remains present to indicate that a secret is configured.""" + secret_reference: Optional[ModelProviderServiceConfigSecretReference] = None + """Reference to a customer-owned UC Secret that carries this secret value. The value is read at + invoke time under the model provider service owner's access and is never copied onto the model + provider service, so rotating the UC Secret takes effect with no change to the model provider + service. On Create, supply ``secret_reference.name`` as ``secrets/{catalog}.{schema}.{secret}``.""" + def as_dict(self) -> dict: """Serializes the ModelProviderServiceConfigProviderSecret into a dictionary suitable for use as a JSON request body.""" body = {} if self.plaintext is not None: body["plaintext"] = self.plaintext + if self.secret_reference: + body["secret_reference"] = self.secret_reference.as_dict() return body def as_shallow_dict(self) -> dict: @@ -8399,12 +8419,47 @@ def as_shallow_dict(self) -> dict: body = {} if self.plaintext is not None: body["plaintext"] = self.plaintext + if self.secret_reference: + body["secret_reference"] = self.secret_reference return body @classmethod def from_dict(cls, d: Dict[str, Any]) -> ModelProviderServiceConfigProviderSecret: """Deserializes the ModelProviderServiceConfigProviderSecret from a dictionary.""" - return cls(plaintext=d.get("plaintext", None)) + return cls( + plaintext=d.get("plaintext", None), + secret_reference=_from_dict(d, "secret_reference", ModelProviderServiceConfigSecretReference), + ) + + +@dataclass +class ModelProviderServiceConfigSecretReference: + """Reference to a customer-owned UC Secret backing a secret-bearing provider field, in the + ``ProviderSecret.secret_reference`` arm.""" + + name: str + """Resource name of the bound UC Secret, in the form ``secrets/{catalog}.{schema}.{secret}``. On + Create the caller supplies the name here. On read it reflects the secret's current name at read + time.""" + + def as_dict(self) -> dict: + """Serializes the ModelProviderServiceConfigSecretReference into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.name is not None: + body["name"] = self.name + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ModelProviderServiceConfigSecretReference into a shallow dictionary of its immediate attributes.""" + body = {} + if self.name is not None: + body["name"] = self.name + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ModelProviderServiceConfigSecretReference: + """Deserializes the ModelProviderServiceConfigSecretReference from a dictionary.""" + return cls(name=d.get("name", None)) @dataclass diff --git a/databricks/sdk/service/cleanrooms.py b/databricks/sdk/service/cleanrooms.py index edfc0ca7d..016301f83 100644 --- a/databricks/sdk/service/cleanrooms.py +++ b/databricks/sdk/service/cleanrooms.py @@ -211,11 +211,11 @@ class CleanRoomAsset: view: Optional[CleanRoomAssetView] = None """View details available to all collaborators of the clean room. Present if and only if - **asset_type** is **VIEW**""" + **asset_type** is **VIEW** or **METRIC_VIEW**""" view_local_details: Optional[CleanRoomAssetViewLocalDetails] = None """Local details for a view that are only available to its owner. Present if and only if - **asset_type** is **VIEW**""" + **asset_type** is **VIEW** or **METRIC_VIEW**""" volume_local_details: Optional[CleanRoomAssetVolumeLocalDetails] = None """Local details for a volume that are only available to its owner. Present if and only if @@ -385,8 +385,8 @@ class CleanRoomAssetJarAnalysis: """Optional description of the jar analysis shown to all collaborators.""" environment_version: Optional[str] = None - """The serverless environment version used to execute the JAR analysis (e.g. "4"). Defaults to - "4-scala-preview" if not specified.""" + """The serverless environment version used to execute the JAR analysis (e.g. "4"). If not + specified, uses the service-configured JAR analysis default.""" etag: Optional[str] = None """Server generated etag that represents the jar analysis version.""" diff --git a/databricks/sdk/service/mason.py b/databricks/sdk/service/mason.py new file mode 100644 index 000000000..0b8d1b941 --- /dev/null +++ b/databricks/sdk/service/mason.py @@ -0,0 +1,1812 @@ +# Code generated from OpenAPI specs by Databricks SDK Generator. DO NOT EDIT. +# ruff: noqa: F811, F841 +# F401 is intentionally NOT covered: `make fmt` uses `ruff check --fix-only` +# to strip the fat-import header below; ignoring F401 would defeat that. + +from __future__ import annotations +from dataclasses import dataclass +from enum import Enum +from typing import Dict, List, Any, Iterator, Optional + +from google.protobuf.timestamp_pb2 import Timestamp + +import logging + +from databricks.sdk.service._internal import ( + _enum, + _from_dict, + _int64, + _repeated_dict, + _timestamp, +) +from databricks.sdk.common.types.fieldmask import FieldMask + + +_LOG = logging.getLogger("databricks.sdk") + + +# all definitions in this file are in alphabetical order + + +@dataclass +class AppendSessionItemsResponse: + """Response containing appended items.""" + + session_items: Optional[List[SessionItem]] = None + """Persisted session items with service-assigned fields.""" + + def as_dict(self) -> dict: + """Serializes the AppendSessionItemsResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.session_items: + body["session_items"] = [v.as_dict() for v in self.session_items] + return body + + def as_shallow_dict(self) -> dict: + """Serializes the AppendSessionItemsResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.session_items: + body["session_items"] = self.session_items + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> AppendSessionItemsResponse: + """Deserializes the AppendSessionItemsResponse from a dictionary.""" + return cls(session_items=_repeated_dict(d, "session_items", SessionItem)) + + +@dataclass +class ClearSessionItemsResponse: + """Response from clearing items from a session.""" + + def as_dict(self) -> dict: + """Serializes the ClearSessionItemsResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ClearSessionItemsResponse into a shallow dictionary of its immediate attributes.""" + body = {} + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ClearSessionItemsResponse: + """Deserializes the ClearSessionItemsResponse from a dictionary.""" + return cls() + + +@dataclass +class ExtractMemoriesResponse: + """Result of a single-session memory extraction.""" + + entries: Optional[List[ManagedMemoryEntry]] = None + """The memory entries written by this extraction.""" + + name: Optional[str] = None + """Correlation identifier for this extraction, for logging and tracing. Not a fetchable resource.""" + + def as_dict(self) -> dict: + """Serializes the ExtractMemoriesResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.entries: + body["entries"] = [v.as_dict() for v in self.entries] + if self.name is not None: + body["name"] = self.name + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ExtractMemoriesResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.entries: + body["entries"] = self.entries + if self.name is not None: + body["name"] = self.name + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ExtractMemoriesResponse: + """Deserializes the ExtractMemoriesResponse from a dictionary.""" + return cls(entries=_repeated_dict(d, "entries", ManagedMemoryEntry), name=d.get("name", None)) + + +@dataclass +class ForkSessionResponse: + """Response from forking a session.""" + + session: Optional[Session] = None + """The newly-created independent top-level session.""" + + def as_dict(self) -> dict: + """Serializes the ForkSessionResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.session: + body["session"] = self.session.as_dict() + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ForkSessionResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.session: + body["session"] = self.session + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ForkSessionResponse: + """Deserializes the ForkSessionResponse from a dictionary.""" + return cls(session=_from_dict(d, "session", Session)) + + +@dataclass +class ListManagedMemoryEntriesResponse: + """Response containing managed memory entries.""" + + managed_memory_entries: Optional[List[ManagedMemoryEntry]] = None + """Managed memory entries matching the request and its read mask.""" + + next_page_token: Optional[str] = None + """Opaque pagination token. This field is omitted when there are no more results.""" + + def as_dict(self) -> dict: + """Serializes the ListManagedMemoryEntriesResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.managed_memory_entries: + body["managed_memory_entries"] = [v.as_dict() for v in self.managed_memory_entries] + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ListManagedMemoryEntriesResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.managed_memory_entries: + body["managed_memory_entries"] = self.managed_memory_entries + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ListManagedMemoryEntriesResponse: + """Deserializes the ListManagedMemoryEntriesResponse from a dictionary.""" + return cls( + managed_memory_entries=_repeated_dict(d, "managed_memory_entries", ManagedMemoryEntry), + next_page_token=d.get("next_page_token", None), + ) + + +@dataclass +class ListManagedMemoryStoresResponse: + """Response containing managed memory stores in the caller's workspace.""" + + managed_memory_stores: Optional[List[ManagedMemoryStore]] = None + """Managed memory stores in the caller's workspace.""" + + next_page_token: Optional[str] = None + """Opaque pagination token. This field is omitted when there are no more results.""" + + def as_dict(self) -> dict: + """Serializes the ListManagedMemoryStoresResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.managed_memory_stores: + body["managed_memory_stores"] = [v.as_dict() for v in self.managed_memory_stores] + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ListManagedMemoryStoresResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.managed_memory_stores: + body["managed_memory_stores"] = self.managed_memory_stores + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ListManagedMemoryStoresResponse: + """Deserializes the ListManagedMemoryStoresResponse from a dictionary.""" + return cls( + managed_memory_stores=_repeated_dict(d, "managed_memory_stores", ManagedMemoryStore), + next_page_token=d.get("next_page_token", None), + ) + + +@dataclass +class ListSessionItemsResponse: + """Response containing a page of session items.""" + + next_page_token: Optional[str] = None + """Token to retrieve the next page.""" + + session_items: Optional[List[SessionItem]] = None + """Session items in the requested page.""" + + def as_dict(self) -> dict: + """Serializes the ListSessionItemsResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + if self.session_items: + body["session_items"] = [v.as_dict() for v in self.session_items] + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ListSessionItemsResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + if self.session_items: + body["session_items"] = self.session_items + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ListSessionItemsResponse: + """Deserializes the ListSessionItemsResponse from a dictionary.""" + return cls( + next_page_token=d.get("next_page_token", None), + session_items=_repeated_dict(d, "session_items", SessionItem), + ) + + +@dataclass +class ListSessionStoresResponse: + """Response containing a page of session stores.""" + + next_page_token: Optional[str] = None + """Token to retrieve the next page.""" + + session_stores: Optional[List[SessionStore]] = None + """Session stores in the requested page.""" + + def as_dict(self) -> dict: + """Serializes the ListSessionStoresResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + if self.session_stores: + body["session_stores"] = [v.as_dict() for v in self.session_stores] + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ListSessionStoresResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + if self.session_stores: + body["session_stores"] = self.session_stores + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ListSessionStoresResponse: + """Deserializes the ListSessionStoresResponse from a dictionary.""" + return cls( + next_page_token=d.get("next_page_token", None), + session_stores=_repeated_dict(d, "session_stores", SessionStore), + ) + + +@dataclass +class ListSessionsResponse: + """Response containing a page of sessions.""" + + next_page_token: Optional[str] = None + """Token to retrieve the next page.""" + + sessions: Optional[List[Session]] = None + """Sessions in the requested page.""" + + def as_dict(self) -> dict: + """Serializes the ListSessionsResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + if self.sessions: + body["sessions"] = [v.as_dict() for v in self.sessions] + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ListSessionsResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + if self.sessions: + body["sessions"] = self.sessions + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ListSessionsResponse: + """Deserializes the ListSessionsResponse from a dictionary.""" + return cls(next_page_token=d.get("next_page_token", None), sessions=_repeated_dict(d, "sessions", Session)) + + +@dataclass +class ManagedMemoryEntry: + """A workspace-scoped entry in a managed memory store.""" + + actor_id: str + """Customer-provided identifier for the actor whose memory this entry represents.""" + + path: str + """Absolute, case-sensitive path identifying the entry within its actor and optional session. Paths + must begin with ``/`` and must not contain empty, ``.`` or ``..`` segments.""" + + content: Optional[str] = None + """Optional free-form memory content.""" + + create_time: Optional[Timestamp] = None + """Time when the entry was created.""" + + description: Optional[str] = None + """Human-readable description of the memory entry.""" + + name: Optional[str] = None + """Resource name in the form + ``memory-stores/{managed_memory_store_id}/entries/{managed_memory_entry_id}``.""" + + session_id: Optional[str] = None + """Optional identifier for the session associated with this memory entry. When omitted, the entry + applies across the actor's sessions.""" + + source_type: Optional[ManagedMemoryEntrySourceType] = None + """Which writer created this entry. Caller sets this on Create; immutable after creation.""" + + update_time: Optional[Timestamp] = None + """Time when the entry was last updated.""" + + def as_dict(self) -> dict: + """Serializes the ManagedMemoryEntry into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.actor_id is not None: + body["actor_id"] = self.actor_id + if self.content is not None: + body["content"] = self.content + if self.create_time is not None: + body["create_time"] = self.create_time.ToJsonString() + if self.description is not None: + body["description"] = self.description + if self.name is not None: + body["name"] = self.name + if self.path is not None: + body["path"] = self.path + if self.session_id is not None: + body["session_id"] = self.session_id + if self.source_type is not None: + body["source_type"] = self.source_type.value + if self.update_time is not None: + body["update_time"] = self.update_time.ToJsonString() + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ManagedMemoryEntry into a shallow dictionary of its immediate attributes.""" + body = {} + if self.actor_id is not None: + body["actor_id"] = self.actor_id + if self.content is not None: + body["content"] = self.content + if self.create_time is not None: + body["create_time"] = self.create_time + if self.description is not None: + body["description"] = self.description + if self.name is not None: + body["name"] = self.name + if self.path is not None: + body["path"] = self.path + if self.session_id is not None: + body["session_id"] = self.session_id + if self.source_type is not None: + body["source_type"] = self.source_type + if self.update_time is not None: + body["update_time"] = self.update_time + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ManagedMemoryEntry: + """Deserializes the ManagedMemoryEntry from a dictionary.""" + return cls( + actor_id=d.get("actor_id", None), + content=d.get("content", None), + create_time=_timestamp(d, "create_time"), + description=d.get("description", None), + name=d.get("name", None), + path=d.get("path", None), + session_id=d.get("session_id", None), + source_type=_enum(d, "source_type", ManagedMemoryEntrySourceType), + update_time=_timestamp(d, "update_time"), + ) + + +@dataclass +class ManagedMemoryEntrySearchResult: + """One relevance-ranked managed memory search result.""" + + managed_memory_entry: Optional[ManagedMemoryEntry] = None + """Managed memory entry matching the query.""" + + score: Optional[float] = None + """Relevance score for the result. Higher scores are more relevant.""" + + def as_dict(self) -> dict: + """Serializes the ManagedMemoryEntrySearchResult into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.managed_memory_entry: + body["managed_memory_entry"] = self.managed_memory_entry.as_dict() + if self.score is not None: + body["score"] = self.score + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ManagedMemoryEntrySearchResult into a shallow dictionary of its immediate attributes.""" + body = {} + if self.managed_memory_entry: + body["managed_memory_entry"] = self.managed_memory_entry + if self.score is not None: + body["score"] = self.score + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ManagedMemoryEntrySearchResult: + """Deserializes the ManagedMemoryEntrySearchResult from a dictionary.""" + return cls( + managed_memory_entry=_from_dict(d, "managed_memory_entry", ManagedMemoryEntry), score=d.get("score", None) + ) + + +class ManagedMemoryEntrySourceType(Enum): + """Identifies the source that created a managed memory entry.""" + + MANAGED_MEMORY_ENTRY_SOURCE_TYPE_AGENT = "MANAGED_MEMORY_ENTRY_SOURCE_TYPE_AGENT" + MANAGED_MEMORY_ENTRY_SOURCE_TYPE_DREAMER = "MANAGED_MEMORY_ENTRY_SOURCE_TYPE_DREAMER" + + +@dataclass +class ManagedMemoryStore: + """A workspace-scoped managed memory store backed by service-managed storage.""" + + create_time: Optional[Timestamp] = None + """Time when the store was created.""" + + creator_user_id: Optional[str] = None + """Workspace-local user ID of the authenticated principal that created the store. This is immutable + server-set attribution and does not grant access; authorization is evaluated from the + authenticated request context.""" + + description: Optional[str] = None + """Human-readable description of the memory store.""" + + display_name: Optional[str] = None + """Deprecated compatibility alias for the caller-provided managed memory store ID. Canonical + clients provide the ID through ``CreateMemoryStoreRequest.managed_memory_store_id`` and use + ``name`` as the resource identifier.""" + + name: Optional[str] = None + """Resource name in the form ``memory-stores/{managed_memory_store_id}``.""" + + owner_user_id: Optional[str] = None + """Deprecated alias for ``creator_user_id``. This identifies the original creator, not a + transferable owner. Use ``creator_user_id`` instead.""" + + storage_backend: Optional[StorageBackend] = None + """Service-managed storage backing this memory store.""" + + update_time: Optional[Timestamp] = None + """Time when the store was last updated.""" + + workspace_id: Optional[int] = None + """Workspace that owns the memory store.""" + + def as_dict(self) -> dict: + """Serializes the ManagedMemoryStore into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.create_time is not None: + body["create_time"] = self.create_time.ToJsonString() + if self.creator_user_id is not None: + body["creator_user_id"] = self.creator_user_id + if self.description is not None: + body["description"] = self.description + if self.display_name is not None: + body["display_name"] = self.display_name + if self.name is not None: + body["name"] = self.name + if self.owner_user_id is not None: + body["owner_user_id"] = self.owner_user_id + if self.storage_backend: + body["storage_backend"] = self.storage_backend.as_dict() + if self.update_time is not None: + body["update_time"] = self.update_time.ToJsonString() + if self.workspace_id is not None: + body["workspace_id"] = self.workspace_id + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ManagedMemoryStore into a shallow dictionary of its immediate attributes.""" + body = {} + if self.create_time is not None: + body["create_time"] = self.create_time + if self.creator_user_id is not None: + body["creator_user_id"] = self.creator_user_id + if self.description is not None: + body["description"] = self.description + if self.display_name is not None: + body["display_name"] = self.display_name + if self.name is not None: + body["name"] = self.name + if self.owner_user_id is not None: + body["owner_user_id"] = self.owner_user_id + if self.storage_backend: + body["storage_backend"] = self.storage_backend + if self.update_time is not None: + body["update_time"] = self.update_time + if self.workspace_id is not None: + body["workspace_id"] = self.workspace_id + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ManagedMemoryStore: + """Deserializes the ManagedMemoryStore from a dictionary.""" + return cls( + create_time=_timestamp(d, "create_time"), + creator_user_id=d.get("creator_user_id", None), + description=d.get("description", None), + display_name=d.get("display_name", None), + name=d.get("name", None), + owner_user_id=d.get("owner_user_id", None), + storage_backend=_from_dict(d, "storage_backend", StorageBackend), + update_time=_timestamp(d, "update_time"), + workspace_id=_int64(d, "workspace_id"), + ) + + +@dataclass +class PopSessionItemResponse: + """Response containing the popped item.""" + + item: Optional[SessionItem] = None + """Removed item, if any.""" + + def as_dict(self) -> dict: + """Serializes the PopSessionItemResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.item: + body["item"] = self.item.as_dict() + return body + + def as_shallow_dict(self) -> dict: + """Serializes the PopSessionItemResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.item: + body["item"] = self.item + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> PopSessionItemResponse: + """Deserializes the PopSessionItemResponse from a dictionary.""" + return cls(item=_from_dict(d, "item", SessionItem)) + + +@dataclass +class SearchManagedMemoryEntriesResponse: + """Response containing managed memory entries ranked by relevance.""" + + managed_memory_entries: Optional[List[ManagedMemoryEntry]] = None + """Deprecated compatibility alias for clients migrating to ``results``. This contains the same + entries in the same order, but omits their relevance scores.""" + + next_page_token: Optional[str] = None + """Opaque pagination token. Search currently returns an unpaginated ranked top-N result set, so the + server does not populate this field.""" + + results: Optional[List[ManagedMemoryEntrySearchResult]] = None + """Canonical matching entries and relevance scores, ordered most relevant first.""" + + def as_dict(self) -> dict: + """Serializes the SearchManagedMemoryEntriesResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.managed_memory_entries: + body["managed_memory_entries"] = [v.as_dict() for v in self.managed_memory_entries] + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + if self.results: + body["results"] = [v.as_dict() for v in self.results] + return body + + def as_shallow_dict(self) -> dict: + """Serializes the SearchManagedMemoryEntriesResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.managed_memory_entries: + body["managed_memory_entries"] = self.managed_memory_entries + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + if self.results: + body["results"] = self.results + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> SearchManagedMemoryEntriesResponse: + """Deserializes the SearchManagedMemoryEntriesResponse from a dictionary.""" + return cls( + managed_memory_entries=_repeated_dict(d, "managed_memory_entries", ManagedMemoryEntry), + next_page_token=d.get("next_page_token", None), + results=_repeated_dict(d, "results", ManagedMemoryEntrySearchResult), + ) + + +@dataclass +class Session: + """A durable logical interaction stored within a Session Store.""" + + actor_id: str + """Opaque caller-provided identifier for the application actor associated with the session. + + This is application data and has no Databricks authentication or authorization semantics. Use + the same value as the Managed Memory Entry ``actor_id`` when storing memories associated with + this actor. Every session must set it. A child session must use the same value as its parent.""" + + create_time: Optional[Timestamp] = None + """Time when the session was created.""" + + last_activity_time: Optional[Timestamp] = None + """Time when the session's item history was last mutated.""" + + metadata: Optional[Dict[str, str]] = None + """Mutable caller-defined string labels.""" + + name: Optional[str] = None + """Resource name in the form ``session-stores/{session_store_id}/sessions/{session_id}``.""" + + parent_session_id: Optional[str] = None + """Immediate parent session ID. Set only at creation for child sessions, immutable thereafter, and + restricted to the same store.""" + + root_session_id: Optional[str] = None + """Top-level session ID in the spawn tree. This equals ``session_id`` for a root or fork and is + inherited transitively by child sessions.""" + + session_id: Optional[str] = None + """Unique session ID. The service generates a UUID unless the caller supplies + ``CreateSessionRequest.session_id``.""" + + update_time: Optional[Timestamp] = None + """Time when session resource fields last changed.""" + + def as_dict(self) -> dict: + """Serializes the Session into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.actor_id is not None: + body["actor_id"] = self.actor_id + if self.create_time is not None: + body["create_time"] = self.create_time.ToJsonString() + if self.last_activity_time is not None: + body["last_activity_time"] = self.last_activity_time.ToJsonString() + if self.metadata: + body["metadata"] = self.metadata + if self.name is not None: + body["name"] = self.name + if self.parent_session_id is not None: + body["parent_session_id"] = self.parent_session_id + if self.root_session_id is not None: + body["root_session_id"] = self.root_session_id + if self.session_id is not None: + body["session_id"] = self.session_id + if self.update_time is not None: + body["update_time"] = self.update_time.ToJsonString() + return body + + def as_shallow_dict(self) -> dict: + """Serializes the Session into a shallow dictionary of its immediate attributes.""" + body = {} + if self.actor_id is not None: + body["actor_id"] = self.actor_id + if self.create_time is not None: + body["create_time"] = self.create_time + if self.last_activity_time is not None: + body["last_activity_time"] = self.last_activity_time + if self.metadata: + body["metadata"] = self.metadata + if self.name is not None: + body["name"] = self.name + if self.parent_session_id is not None: + body["parent_session_id"] = self.parent_session_id + if self.root_session_id is not None: + body["root_session_id"] = self.root_session_id + if self.session_id is not None: + body["session_id"] = self.session_id + if self.update_time is not None: + body["update_time"] = self.update_time + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> Session: + """Deserializes the Session from a dictionary.""" + return cls( + actor_id=d.get("actor_id", None), + create_time=_timestamp(d, "create_time"), + last_activity_time=_timestamp(d, "last_activity_time"), + metadata=d.get("metadata", None), + name=d.get("name", None), + parent_session_id=d.get("parent_session_id", None), + root_session_id=d.get("root_session_id", None), + session_id=d.get("session_id", None), + update_time=_timestamp(d, "update_time"), + ) + + +@dataclass +class SessionItem: + """A transcript entry in a session's history.""" + + data: any + """Complete SDK-native, JSON-compatible item. The service stores and returns this value without + interpreting provider-specific fields such as ``type``, ``role``, or ``content``.""" + + create_time: Optional[Timestamp] = None + """Server-assigned time when the append commits. Values are nondecreasing within a session. Item + listing orders by this timestamp; equal timestamps are resolved by committed append order.""" + + item_id: Optional[str] = None + """Stable service-generated item ID.""" + + def as_dict(self) -> dict: + """Serializes the SessionItem into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.create_time is not None: + body["create_time"] = self.create_time.ToJsonString() + if self.data: + body["data"] = self.data + if self.item_id is not None: + body["item_id"] = self.item_id + return body + + def as_shallow_dict(self) -> dict: + """Serializes the SessionItem into a shallow dictionary of its immediate attributes.""" + body = {} + if self.create_time is not None: + body["create_time"] = self.create_time + if self.data: + body["data"] = self.data + if self.item_id is not None: + body["item_id"] = self.item_id + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> SessionItem: + """Deserializes the SessionItem from a dictionary.""" + return cls(create_time=_timestamp(d, "create_time"), data=d.get("data", None), item_id=d.get("item_id", None)) + + +@dataclass +class SessionStore: + """A workspace-scoped session store.""" + + create_time: Optional[Timestamp] = None + """Time when the store was created.""" + + creator_user_id: Optional[str] = None + """Workspace-local user ID of the authenticated principal that created the store. This is immutable + server-set attribution and does not grant access; authorization is evaluated from the + authenticated request context.""" + + description: Optional[str] = None + """Human-readable description of the session store.""" + + metadata: Optional[Dict[str, str]] = None + """Mutable caller-defined string labels.""" + + name: Optional[str] = None + """Resource name in the form ``session-stores/{session_store_id}``.""" + + update_time: Optional[Timestamp] = None + """Time when the store was last updated.""" + + def as_dict(self) -> dict: + """Serializes the SessionStore into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.create_time is not None: + body["create_time"] = self.create_time.ToJsonString() + if self.creator_user_id is not None: + body["creator_user_id"] = self.creator_user_id + if self.description is not None: + body["description"] = self.description + if self.metadata: + body["metadata"] = self.metadata + if self.name is not None: + body["name"] = self.name + if self.update_time is not None: + body["update_time"] = self.update_time.ToJsonString() + return body + + def as_shallow_dict(self) -> dict: + """Serializes the SessionStore into a shallow dictionary of its immediate attributes.""" + body = {} + if self.create_time is not None: + body["create_time"] = self.create_time + if self.creator_user_id is not None: + body["creator_user_id"] = self.creator_user_id + if self.description is not None: + body["description"] = self.description + if self.metadata: + body["metadata"] = self.metadata + if self.name is not None: + body["name"] = self.name + if self.update_time is not None: + body["update_time"] = self.update_time + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> SessionStore: + """Deserializes the SessionStore from a dictionary.""" + return cls( + create_time=_timestamp(d, "create_time"), + creator_user_id=d.get("creator_user_id", None), + description=d.get("description", None), + metadata=d.get("metadata", None), + name=d.get("name", None), + update_time=_timestamp(d, "update_time"), + ) + + +@dataclass +class StorageBackend: + """Service-managed storage backing a managed memory store.""" + + backend_id: Optional[str] = None + """Backend-specific identifier. For Lakebase, this is the project ID.""" + + backend_type: Optional[StorageBackendType] = None + """Type of the storage backend.""" + + def as_dict(self) -> dict: + """Serializes the StorageBackend into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.backend_id is not None: + body["backend_id"] = self.backend_id + if self.backend_type is not None: + body["backend_type"] = self.backend_type.value + return body + + def as_shallow_dict(self) -> dict: + """Serializes the StorageBackend into a shallow dictionary of its immediate attributes.""" + body = {} + if self.backend_id is not None: + body["backend_id"] = self.backend_id + if self.backend_type is not None: + body["backend_type"] = self.backend_type + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> StorageBackend: + """Deserializes the StorageBackend from a dictionary.""" + return cls(backend_id=d.get("backend_id", None), backend_type=_enum(d, "backend_type", StorageBackendType)) + + +class StorageBackendType(Enum): + """Type of service-managed storage backing a managed memory store.""" + + STORAGE_BACKEND_TYPE_LAKEBASE = "STORAGE_BACKEND_TYPE_LAKEBASE" + + +class MasonAPI: + """APIs for managing agent memory and durable session state. This interface is under active development and + may change.""" + + def __init__(self, api_client): + self._api = api_client + + def append_session_items(self, parent: str, items: List[SessionItem]) -> AppendSessionItemsResponse: + """Appends items to a session. + + :param parent: str + Resource name of the containing session, in the form + ``session-stores/{session_store_id}/sessions/{session_id}``. + :param items: List[:class:`SessionItem`] + Items to append atomically in request order. Concurrent append requests are serialized into one + committed order without exposing a numeric sequence in the public contract. + + :returns: :class:`AppendSessionItemsResponse` + """ + + body = {} + if items is not None: + body["items"] = [v.as_dict() for v in items] + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("POST", f"/api/2.0/agents/{parent}/items:append", body=body, headers=headers) + return AppendSessionItemsResponse.from_dict(res) + + def clear_session_items(self, parent: str) -> ClearSessionItemsResponse: + """Clears all items from a session. + + :param parent: str + Resource name of the containing session, in the form + ``session-stores/{session_store_id}/sessions/{session_id}``. + + :returns: :class:`ClearSessionItemsResponse` + """ + + body = {} + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("POST", f"/api/2.0/agents/{parent}/items:clear", body=body, headers=headers) + return ClearSessionItemsResponse.from_dict(res) + + def create_memory( + self, parent: str, managed_memory_entry: ManagedMemoryEntry, *, managed_memory_entry_id: Optional[str] = None + ) -> ManagedMemoryEntry: + """Creates a managed memory entry using exclusive-create semantics. Callers may choose the entry ID; the + service generates one when it is omitted. Returns ``ALREADY_EXISTS`` when an entry with the same + actor, session, and path already exists. Omitted ``session_id`` is its own uniqueness key: two + omitted-session entries with the same actor and path conflict, but an omitted-session entry does not + conflict with a session-scoped entry at the same actor and path. + + :param parent: str + Managed memory store that will contain the entry, in the form + ``memory-stores/{managed_memory_store_id}``. + :param managed_memory_entry: :class:`ManagedMemoryEntry` + The managed memory entry to create. + :param managed_memory_entry_id: str (optional) + Optional caller-selected managed memory entry ID. The service generates an ID when omitted. + + :returns: :class:`ManagedMemoryEntry` + """ + + body = managed_memory_entry.as_dict() + query = {} + if managed_memory_entry_id is not None: + query["managed_memory_entry_id"] = managed_memory_entry_id + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("POST", f"/api/2.0/agents/{parent}/entries", query=query, body=body, headers=headers) + return ManagedMemoryEntry.from_dict(res) + + def create_memory_store( + self, managed_memory_store: ManagedMemoryStore, managed_memory_store_id: str + ) -> ManagedMemoryStore: + """Creates a managed memory store in the caller's workspace. + + :param managed_memory_store: :class:`ManagedMemoryStore` + The managed memory store to create. + :param managed_memory_store_id: str + Caller-provided, workspace-unique managed memory store ID. It must be 3-56 characters, begin with a + lowercase letter, contain only lowercase letters, digits, and hyphens, and end with a letter or + digit. + + :returns: :class:`ManagedMemoryStore` + """ + + body = managed_memory_store.as_dict() + query = {} + if managed_memory_store_id is not None: + query["managed_memory_store_id"] = managed_memory_store_id + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("POST", "/api/2.0/agents/memory-stores", query=query, body=body, headers=headers) + return ManagedMemoryStore.from_dict(res) + + def create_session(self, parent: str, session: Session, *, session_id: Optional[str] = None) -> Session: + """Creates a session within a session store. + + :param parent: str + Resource name of the containing session store, in the form ``session-stores/{session_store_id}``. + :param session: :class:`Session` + The session to create. ``actor_id`` is required. A session with ``parent_session_id`` is a child and + must use its parent's ``actor_id``. Independent forks are created only through ``ForkSession``. + :param session_id: str (optional) + Optional caller-selected session ID. The service generates a UUID when this field is omitted. The ID + must be unique; a collision returns ``ALREADY_EXISTS``. + + :returns: :class:`Session` + """ + + body = session.as_dict() + query = {} + if session_id is not None: + query["session_id"] = session_id + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("POST", f"/api/2.0/agents/{parent}/sessions", query=query, body=body, headers=headers) + return Session.from_dict(res) + + def create_session_store(self, session_store: SessionStore, session_store_id: str) -> SessionStore: + """Creates a session store. + + :param session_store: :class:`SessionStore` + The session store to create. + :param session_store_id: str + Caller-provided, workspace-unique session store ID. It must be 3-55 characters, begin with a + lowercase letter, and contain only lowercase letters, digits, and hyphens. + + :returns: :class:`SessionStore` + """ + + body = session_store.as_dict() + query = {} + if session_store_id is not None: + query["session_store_id"] = session_store_id + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("POST", "/api/2.0/agents/session-stores", query=query, body=body, headers=headers) + return SessionStore.from_dict(res) + + def delete_memory(self, name: str): + """Deletes a managed memory entry by resource name. Returns ``NOT_FOUND`` when the entry does not exist + in the caller's workspace. + + :param name: str + Resource name in the form + ``memory-stores/{managed_memory_store_id}/entries/{managed_memory_entry_id}``. + + + """ + + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + self._api.do("DELETE", f"/api/2.0/agents/{name}", headers=headers) + + def delete_memory_store(self, name: str): + """Deletes a managed memory store by resource name. Returns ``NOT_FOUND`` when the store does not exist + in the caller's workspace. + + :param name: str + Resource name in the form ``memory-stores/{managed_memory_store_id}``. + + + """ + + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + self._api.do("DELETE", f"/api/2.0/agents/{name}", headers=headers) + + def delete_session(self, name: str): + """Deletes a session, its items, and any descendant sessions recursively. Independently retained memory + is not deleted. + + :param name: str + Resource name in the form ``session-stores/{session_store_id}/sessions/{session_id}``. + + + """ + + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + self._api.do("DELETE", f"/api/2.0/agents/{name}", headers=headers) + + def delete_session_store(self, name: str): + """Deletes a session store, its sessions and items, and its service-managed storage. Memory entries + retained by a separate Memory Store are not deleted. + + :param name: str + Resource name in the form ``session-stores/{session_store_id}``. + + + """ + + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + self._api.do("DELETE", f"/api/2.0/agents/{name}", headers=headers) + + def extract_memories( + self, + session_store: str, + session_id: str, + memory_store: str, + *, + dry_run: Optional[bool] = None, + instructions: Optional[str] = None, + ) -> ExtractMemoriesResponse: + """Synchronously extracts memories from a single session into the given memory store, returning the + entries that were written. + + :param session_store: str + Session store containing the session, in the form ``session-stores/{session_store_id}``. + :param session_id: str + Identifier of the session whose transcript is distilled into memories. + :param memory_store: str + Managed memory store the extracted entries are written to, in the form + ``memory-stores/{managed_memory_store_id}``. + :param dry_run: bool (optional) + When true, extract and return the entries without writing them to the memory store. Defaults to + false, which persists the extracted entries and returns them. + :param instructions: str (optional) + Instructions steering what is extracted from the session. + + :returns: :class:`ExtractMemoriesResponse` + """ + + body = {} + if dry_run is not None: + body["dry_run"] = dry_run + if instructions is not None: + body["instructions"] = instructions + if memory_store is not None: + body["memory_store"] = memory_store + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do( + "POST", f"/api/2.0/agents/{session_store}/sessions/{session_id}/extractions", body=body, headers=headers + ) + return ExtractMemoriesResponse.from_dict(res) + + def fork_session( + self, + parent: str, + source_session_id: str, + actor_id: str, + *, + metadata: Optional[Dict[str, str]] = None, + session_id: Optional[str] = None, + up_to_item_id: Optional[str] = None, + ) -> ForkSessionResponse: + """Forks a session into an independent top-level copy. + + :param parent: str + Resource name of the containing session store, in the form ``session-stores/{session_store_id}``. + :param source_session_id: str + ID of the session to copy. + :param actor_id: str + Opaque caller-provided identifier for the application actor associated with the forked session. + :param metadata: Dict[str,str] (optional) + Optional metadata for the fork. + :param session_id: str (optional) + Optional unique ID for the forked session. A collision returns ``ALREADY_EXISTS``. + :param up_to_item_id: str (optional) + Optional last item ID to copy through, inclusively. When omitted, the fork atomically copies all + items committed before the fork operation begins. + + :returns: :class:`ForkSessionResponse` + """ + + body = {} + if actor_id is not None: + body["actor_id"] = actor_id + if metadata is not None: + body["metadata"] = metadata + if session_id is not None: + body["session_id"] = session_id + if source_session_id is not None: + body["source_session_id"] = source_session_id + if up_to_item_id is not None: + body["up_to_item_id"] = up_to_item_id + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("POST", f"/api/2.0/agents/{parent}/sessions:fork", body=body, headers=headers) + return ForkSessionResponse.from_dict(res) + + def get_memory(self, name: str, *, read_mask: Optional[FieldMask] = None) -> ManagedMemoryEntry: + """Retrieves a managed memory entry, including its content, by resource name. Returns ``NOT_FOUND`` when + the entry does not exist in the caller's workspace. + + :param name: str + Resource name in the form + ``memory-stores/{managed_memory_store_id}/entries/{managed_memory_entry_id}``. + :param read_mask: FieldMask (optional) + Fields to return, using proto field names such as ``content`` (not ``contents``). An omitted or + empty mask returns the full entry, including ``content``; a non-empty mask returns only the + requested fields. + + :returns: :class:`ManagedMemoryEntry` + """ + + query = {} + if read_mask is not None: + query["read_mask"] = read_mask.ToJsonString() + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("GET", f"/api/2.0/agents/{name}", query=query, headers=headers) + return ManagedMemoryEntry.from_dict(res) + + def get_memory_store(self, name: str) -> ManagedMemoryStore: + """Retrieves a managed memory store by resource name. Returns ``NOT_FOUND`` when the store does not exist + in the caller's workspace. + + :param name: str + Resource name in the form ``memory-stores/{managed_memory_store_id}``. + + :returns: :class:`ManagedMemoryStore` + """ + + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("GET", f"/api/2.0/agents/{name}", headers=headers) + return ManagedMemoryStore.from_dict(res) + + def get_session(self, name: str) -> Session: + """Gets a session by resource name. + + :param name: str + Resource name in the form ``session-stores/{session_store_id}/sessions/{session_id}``. + + :returns: :class:`Session` + """ + + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("GET", f"/api/2.0/agents/{name}", headers=headers) + return Session.from_dict(res) + + def get_session_store(self, name: str) -> SessionStore: + """Gets a session store by resource name. + + :param name: str + Resource name in the form ``session-stores/{session_store_id}``. + + :returns: :class:`SessionStore` + """ + + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("GET", f"/api/2.0/agents/{name}", headers=headers) + return SessionStore.from_dict(res) + + def list_memories( + self, + parent: str, + actor_id: str, + *, + page_size: Optional[int] = None, + page_token: Optional[str] = None, + path_prefix: Optional[str] = None, + read_mask: Optional[FieldMask] = None, + session_id: Optional[str] = None, + ) -> Iterator[ManagedMemoryEntry]: + """Lists managed memory entries for one actor. Optional ``session_id`` and ``path_prefix`` further + restrict the actor partition; ``read_mask`` selects fields in each returned entry. + + :param parent: str + Managed memory store whose entries are listed, in the form + ``memory-stores/{managed_memory_store_id}``. + :param actor_id: str + Customer-provided identifier for the actor whose entries are listed. + :param page_size: int (optional) + Maximum number of entries to return. The service may return fewer entries than requested. Defaults + to 10; must be between 1 and 100. + :param page_token: str (optional) + Opaque pagination token from a previous ListManagedMemoryEntries response. + :param path_prefix: str (optional) + Optional path prefix used to restrict entries within the actor partition. + :param read_mask: FieldMask (optional) + Fields to return in each entry, using proto field names such as ``content`` (not ``contents``). An + omitted or empty mask returns each full entry, including ``content``; a non-empty mask returns only + the requested fields. + :param session_id: str (optional) + Optional session identifier. When set, only entries with this exact ``session_id`` are returned. + Omitted-session (cross-session) entries are not included. + + :returns: Iterator over :class:`ManagedMemoryEntry` + """ + + query = {} + if actor_id is not None: + query["actor_id"] = actor_id + if page_size is not None: + query["page_size"] = page_size + if page_token is not None: + query["page_token"] = page_token + if path_prefix is not None: + query["path_prefix"] = path_prefix + if read_mask is not None: + query["read_mask"] = read_mask.ToJsonString() + if session_id is not None: + query["session_id"] = session_id + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + while True: + json = self._api.do("GET", f"/api/2.0/agents/{parent}/entries", query=query, headers=headers) + if "managed_memory_entries" in json: + for v in json["managed_memory_entries"]: + yield ManagedMemoryEntry.from_dict(v) + if "next_page_token" not in json or not json["next_page_token"]: + return + query["page_token"] = json["next_page_token"] + + def list_memory_stores( + self, *, page_size: Optional[int] = None, page_token: Optional[str] = None + ) -> Iterator[ManagedMemoryStore]: + """Lists managed memory stores in the caller's workspace. + + :param page_size: int (optional) + Maximum number of stores to return. The service may return fewer stores than requested. Defaults to + 10; must be between 1 and 100. + :param page_token: str (optional) + Opaque pagination token from a previous ListManagedMemoryStores response. + + :returns: Iterator over :class:`ManagedMemoryStore` + """ + + query = {} + if page_size is not None: + query["page_size"] = page_size + if page_token is not None: + query["page_token"] = page_token + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + while True: + json = self._api.do("GET", "/api/2.0/agents/memory-stores", query=query, headers=headers) + if "managed_memory_stores" in json: + for v in json["managed_memory_stores"]: + yield ManagedMemoryStore.from_dict(v) + if "next_page_token" not in json or not json["next_page_token"]: + return + query["page_token"] = json["next_page_token"] + + def list_session_items( + self, + parent: str, + *, + order_by: Optional[str] = None, + page_size: Optional[int] = None, + page_token: Optional[str] = None, + ) -> Iterator[SessionItem]: + """Lists items in a session. + + :param parent: str + Resource name of the containing session, in the form + ``session-stores/{session_store_id}/sessions/{session_id}``. + :param order_by: str (optional) + Sort order. Supported values are ``create_time asc`` and ``create_time desc``. The default is + ``create_time desc``, which returns the most recently appended items first. Equal timestamps are + resolved by committed append order in the requested direction. + :param page_size: int (optional) + Maximum number of items to return. Defaults to 10; must be between 1 and 100. + :param page_token: str (optional) + Token returned by a previous list request. + + :returns: Iterator over :class:`SessionItem` + """ + + query = {} + if order_by is not None: + query["order_by"] = order_by + if page_size is not None: + query["page_size"] = page_size + if page_token is not None: + query["page_token"] = page_token + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + while True: + json = self._api.do("GET", f"/api/2.0/agents/{parent}/items", query=query, headers=headers) + if "session_items" in json: + for v in json["session_items"]: + yield SessionItem.from_dict(v) + if "next_page_token" not in json or not json["next_page_token"]: + return + query["page_token"] = json["next_page_token"] + + def list_session_stores( + self, *, page_size: Optional[int] = None, page_token: Optional[str] = None + ) -> Iterator[SessionStore]: + """Lists session stores. + + :param page_size: int (optional) + Maximum number of session stores to return. Defaults to 10; must be between 1 and 100. + :param page_token: str (optional) + Token returned by a previous list request. + + :returns: Iterator over :class:`SessionStore` + """ + + query = {} + if page_size is not None: + query["page_size"] = page_size + if page_token is not None: + query["page_token"] = page_token + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + while True: + json = self._api.do("GET", "/api/2.0/agents/session-stores", query=query, headers=headers) + if "session_stores" in json: + for v in json["session_stores"]: + yield SessionStore.from_dict(v) + if "next_page_token" not in json or not json["next_page_token"]: + return + query["page_token"] = json["next_page_token"] + + def list_sessions( + self, + parent: str, + *, + filter: Optional[str] = None, + order_by: Optional[str] = None, + page_size: Optional[int] = None, + page_token: Optional[str] = None, + ) -> Iterator[Session]: + """Lists sessions within a session store. + + :param parent: str + Resource name of the containing session store, in the form ``session-stores/{session_store_id}``. + :param filter: str (optional) + Filter expression. Supported fields include ``actor_id`` and ``metadata``; for example, ``actor_id = + "support-customer-123"``. + :param order_by: str (optional) + Sort order. Defaults to ``last_activity_time desc``. Page-token continuation is exactly-once when + ordering by ``create_time`` (immutable); ordering by ``last_activity_time`` is best-effort, because + that value changes as a session gains activity, so a session updated between page requests may be + repeated or skipped. To enumerate every session exactly once, order by ``create_time``. + :param page_size: int (optional) + Maximum number of sessions to return. Defaults to 10; must be between 1 and 100. + :param page_token: str (optional) + Token returned by a previous list request. + + :returns: Iterator over :class:`Session` + """ + + query = {} + if filter is not None: + query["filter"] = filter + if order_by is not None: + query["order_by"] = order_by + if page_size is not None: + query["page_size"] = page_size + if page_token is not None: + query["page_token"] = page_token + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + while True: + json = self._api.do("GET", f"/api/2.0/agents/{parent}/sessions", query=query, headers=headers) + if "sessions" in json: + for v in json["sessions"]: + yield Session.from_dict(v) + if "next_page_token" not in json or not json["next_page_token"]: + return + query["page_token"] = json["next_page_token"] + + def pop_session_item(self, parent: str) -> PopSessionItemResponse: + """Pops the newest item from a session. + + :param parent: str + Resource name of the containing session, in the form + ``session-stores/{session_store_id}/sessions/{session_id}``. + + :returns: :class:`PopSessionItemResponse` + """ + + body = {} + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("POST", f"/api/2.0/agents/{parent}/items:pop", body=body, headers=headers) + return PopSessionItemResponse.from_dict(res) + + def search_memories( + self, + parent: str, + actor_id: str, + query: str, + *, + limit: Optional[int] = None, + page_size: Optional[int] = None, + page_token: Optional[str] = None, + path_prefix: Optional[str] = None, + read_mask: Optional[FieldMask] = None, + session_id: Optional[str] = None, + ) -> Iterator[ManagedMemoryEntrySearchResult]: + """Searches managed memory entries by text query for one actor. Returns matching entries and scores + ranked by relevance; ``read_mask`` selects fields in each returned entry. + + :param parent: str + Managed memory store whose entries are searched, in the form + ``memory-stores/{managed_memory_store_id}``. + :param actor_id: str + Customer-provided identifier for the actor whose entries are searched. + :param query: str + Free-form search query. + :param limit: int (optional) + Deprecated alias for ``page_size``. When both fields are set, their values must match. + :param page_size: int (optional) + Maximum number of relevance-ranked entries to return. Defaults to 10 and must be between 1 and 100. + :param page_token: str (optional) + Reserved for pagination compatibility. The server currently ignores this field because Search + returns a ranked top-N result set. + :param path_prefix: str (optional) + Optional absolute, case-sensitive path prefix used to restrict searched entries within the actor + partition. The prefix must begin with ``/`` and must not contain empty, ``.`` or ``..`` segments. + :param read_mask: FieldMask (optional) + Fields to return in each matching entry, using proto field names such as ``content`` (not + ``contents``). An omitted or empty mask returns each full entry, including ``content``; a non-empty + mask returns only the requested fields. Search scores are always returned. + + The field mask must be a single string, with multiple fields separated by commas (no spaces). The + field path is relative to the resource object, using a dot (``.``) to navigate sub-fields (e.g., + ``author.given_name``). Specification of elements in sequence or map fields is not allowed, as only + the entire collection field can be specified. Field names must exactly match the resource field + names. + :param session_id: str (optional) + Optional session identifier. When set, only entries with this exact ``session_id`` are searched. + Omitted-session (cross-session) entries are not included. + + :returns: Iterator over :class:`ManagedMemoryEntrySearchResult` + """ + + body = {} + if actor_id is not None: + body["actor_id"] = actor_id + if limit is not None: + body["limit"] = limit + if page_size is not None: + body["page_size"] = page_size + if page_token is not None: + body["page_token"] = page_token + if path_prefix is not None: + body["path_prefix"] = path_prefix + if query is not None: + body["query"] = query + if read_mask is not None: + body["read_mask"] = read_mask.ToJsonString() + if session_id is not None: + body["session_id"] = session_id + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + while True: + json = self._api.do("POST", f"/api/2.0/agents/{parent}/entries:search", body=body, headers=headers) + if "results" in json: + for v in json["results"]: + yield ManagedMemoryEntrySearchResult.from_dict(v) + if "next_page_token" not in json or not json["next_page_token"]: + return + body["page_token"] = json["next_page_token"] + + def update_memory( + self, name: str, managed_memory_entry: ManagedMemoryEntry, update_mask: FieldMask + ) -> ManagedMemoryEntry: + """Updates selected mutable fields on a managed memory entry. Identity fields are immutable. + + :param name: str + Resource name in the form + ``memory-stores/{managed_memory_store_id}/entries/{managed_memory_entry_id}``. + :param managed_memory_entry: :class:`ManagedMemoryEntry` + The managed memory entry to update. + :param update_mask: FieldMask + Fields to update. Only ``content`` and ``description`` may be updated. + + :returns: :class:`ManagedMemoryEntry` + """ + + body = managed_memory_entry.as_dict() + query = {} + if update_mask is not None: + query["update_mask"] = update_mask.ToJsonString() + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("PATCH", f"/api/2.0/agents/{name}", query=query, body=body, headers=headers) + return ManagedMemoryEntry.from_dict(res) + + def update_memory_store( + self, name: str, managed_memory_store: ManagedMemoryStore, update_mask: FieldMask + ) -> ManagedMemoryStore: + """Updates a managed memory store's description. + + :param name: str + Resource name in the form ``memory-stores/{managed_memory_store_id}``. + :param managed_memory_store: :class:`ManagedMemoryStore` + The managed memory store to update. ``name`` is taken from the URL. + :param update_mask: FieldMask + Only ``description`` may be updated. + + :returns: :class:`ManagedMemoryStore` + """ + + body = managed_memory_store.as_dict() + query = {} + if update_mask is not None: + query["update_mask"] = update_mask.ToJsonString() + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("PATCH", f"/api/2.0/agents/{name}", query=query, body=body, headers=headers) + return ManagedMemoryStore.from_dict(res) + + def update_session(self, name: str, session: Session, update_mask: FieldMask) -> Session: + """Updates a session's mutable fields. + + :param name: str + Resource name in the form ``session-stores/{session_store_id}/sessions/{session_id}``. + :param session: :class:`Session` + Session to update. + :param update_mask: FieldMask + Fields to update. Only ``metadata`` is mutable; any other path returns ``INVALID_PARAMETER_VALUE``. + + :returns: :class:`Session` + """ + + body = session.as_dict() + query = {} + if update_mask is not None: + query["update_mask"] = update_mask.ToJsonString() + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("PATCH", f"/api/2.0/agents/{name}", query=query, body=body, headers=headers) + return Session.from_dict(res) + + def update_session_store(self, name: str, session_store: SessionStore, update_mask: FieldMask) -> SessionStore: + """Updates a session store's description and metadata. + + :param name: str + Resource name in the form ``session-stores/{session_store_id}``. + :param session_store: :class:`SessionStore` + Session store to update. + :param update_mask: FieldMask + Fields to update. Only ``description`` and ``metadata`` are mutable; any other path returns + ``INVALID_PARAMETER_VALUE``. + + :returns: :class:`SessionStore` + """ + + body = session_store.as_dict() + query = {} + if update_mask is not None: + query["update_mask"] = update_mask.ToJsonString() + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Workspace-Id"] = cfg.workspace_id + + res = self._api.do("PATCH", f"/api/2.0/agents/{name}", query=query, body=body, headers=headers) + return SessionStore.from_dict(res) diff --git a/databricks/sdk/service/ml.py b/databricks/sdk/service/ml.py index 2e1b3c5fb..a46743708 100644 --- a/databricks/sdk/service/ml.py +++ b/databricks/sdk/service/ml.py @@ -10805,7 +10805,10 @@ def backfill_features( :param feature_full_names: List[str] Full names of the features to backfill. :param backfill_ranges: List[:class:`BackfillRange`] - Output ranges to backfill. + Output ranges to backfill. TODO[FS-1372]: audit_mode=INCLUDE is intentionally omitted. The + annotation redactor cannot serialize google.protobuf.Timestamp leaves (start_time/end_time), so + annotating this field does not surface the ranges in audit logs. See + FeatureStoreEventDefinitions.BackfillFeatures. :param budget_policy_id: str (optional) The budget policy ID, in UUID format, used to attribute the serverless compute cost of this backfill. If not specified, a default budget policy may be applied. diff --git a/databricks/sdk/service/pipelines.py b/databricks/sdk/service/pipelines.py index 0b93dbd5c..4c74c0a68 100644 --- a/databricks/sdk/service/pipelines.py +++ b/databricks/sdk/service/pipelines.py @@ -145,7 +145,7 @@ def from_dict(cls, d: Dict[str, Any]) -> AutoFullRefreshPolicy: @dataclass class AvroTransformerOptions: parse_mode: Optional[ParseMode] = None - """(Optional) Parse mode for Avro data. Valid values: FAILFAST, PERMISSIVE. Defaults to FAILFAST.""" + """(Optional) Parse mode for Avro data. Valid values: FAILFAST, PERMISSIVE. Defaults to PERMISSIVE.""" schema: Optional[str] = None """Inline Avro JSON schema string.""" @@ -4196,7 +4196,7 @@ class ProtobufTransformerOptions: parse_mode: Optional[ParseMode] = None """(Optional) Parse mode for Protobuf data. Valid values: FAILFAST, PERMISSIVE. Defaults to - FAILFAST.""" + PERMISSIVE.""" recursive_fields_max_depth: Optional[int] = None """(Optional) Maximum expansion depth for recursive protobuf fields. Spark SQL does not natively diff --git a/databricks/sdk/service/sql.py b/databricks/sdk/service/sql.py index 3f86ae65b..efe542b98 100644 --- a/databricks/sdk/service/sql.py +++ b/databricks/sdk/service/sql.py @@ -6155,12 +6155,10 @@ class SpotInstancePolicy(Enum): The breakdown of how the EndpointSpotInstancePolicy converts to per cloud configurations is: - +-------+--------------------------------------+--------------------------------+ | Cloud | - COST_OPTIMIZED | RELIABILITY_OPTIMIZED | - +-------+--------------------------------------+--------------------------------+ | AWS | On - Demand Driver with Spot Executors | On Demand Driver and Executors | | AZURE | On Demand Driver - and Executors | On Demand Driver and Executors | - +-------+--------------------------------------+--------------------------------+""" + - AWS, COST_OPTIMIZED: On Demand Driver with Spot Executors. + - AWS, RELIABILITY_OPTIMIZED: On Demand Driver and Executors. + - AZURE, COST_OPTIMIZED: On Demand Driver and Executors. + - AZURE, RELIABILITY_OPTIMIZED: On Demand Driver and Executors.""" COST_OPTIMIZED = "COST_OPTIMIZED" POLICY_UNSPECIFIED = "POLICY_UNSPECIFIED" diff --git a/databricks/sdk/service/tags.py b/databricks/sdk/service/tags.py index 012cd7c80..e2fb75bf3 100644 --- a/databricks/sdk/service/tags.py +++ b/databricks/sdk/service/tags.py @@ -91,7 +91,7 @@ def from_dict(cls, d: Dict[str, Any]) -> ListTagPoliciesResponse: class TagAssignment: entity_type: str """The type of entity to which the tag is assigned. Allowed values are apps, dashboards, - designerfiles, geniespaces, notebooks""" + designer-files, geniespaces, notebooks""" entity_id: str """The identifier of the entity to which the tag is assigned. For apps, the entity_id is the app @@ -419,8 +419,8 @@ def delete_tag_assignment(self, entity_type: str, entity_id: str, tag_key: str): """Delete a tag assignment :param entity_type: str - The type of entity to which the tag is assigned. Allowed values are apps, dashboards, designerfiles, - geniespaces, notebooks + The type of entity to which the tag is assigned. Allowed values are apps, dashboards, + designer-files, geniespaces, notebooks :param entity_id: str The identifier of the entity to which the tag is assigned. For apps, the entity_id is the app name :param tag_key: str @@ -445,8 +445,8 @@ def get_tag_assignment(self, entity_type: str, entity_id: str, tag_key: str) -> """Get a tag assignment :param entity_type: str - The type of entity to which the tag is assigned. Allowed values are apps, dashboards, designerfiles, - geniespaces, notebooks + The type of entity to which the tag is assigned. Allowed values are apps, dashboards, + designer-files, geniespaces, notebooks :param entity_id: str The identifier of the entity to which the tag is assigned. For apps, the entity_id is the app name :param tag_key: str @@ -474,8 +474,8 @@ def list_tag_assignments( """List the tag assignments for an entity :param entity_type: str - The type of entity to which the tag is assigned. Allowed values are apps, dashboards, designerfiles, - geniespaces, notebooks + The type of entity to which the tag is assigned. Allowed values are apps, dashboards, + designer-files, geniespaces, notebooks :param entity_id: str The identifier of the entity to which the tag is assigned. For apps, the entity_id is the app name :param page_size: int (optional) @@ -516,8 +516,8 @@ def update_tag_assignment( """Update a tag assignment :param entity_type: str - The type of entity to which the tag is assigned. Allowed values are apps, dashboards, designerfiles, - geniespaces, notebooks + The type of entity to which the tag is assigned. Allowed values are apps, dashboards, + designer-files, geniespaces, notebooks :param entity_id: str The identifier of the entity to which the tag is assigned. For apps, the entity_id is the app name :param tag_key: str diff --git a/databricks/sdk/version.py b/databricks/sdk/version.py index 120dcc32b..873491885 100644 --- a/databricks/sdk/version.py +++ b/databricks/sdk/version.py @@ -1 +1 @@ -__version__ = "0.140.0" +__version__ = "0.141.0" diff --git a/docs/dbdataclasses/catalog.rst b/docs/dbdataclasses/catalog.rst index 53486b808..b06f291f4 100644 --- a/docs/dbdataclasses/catalog.rst +++ b/docs/dbdataclasses/catalog.rst @@ -1247,6 +1247,10 @@ These dataclasses are used in the SDK to represent API requests and responses fo :members: :undoc-members: +.. autoclass:: ModelProviderServiceConfigSecretReference + :members: + :undoc-members: + .. autoclass:: ModelProviderServiceConfigServiceCredential :members: :undoc-members: diff --git a/docs/dbdataclasses/index.rst b/docs/dbdataclasses/index.rst index e2586bc34..93f3189fb 100644 --- a/docs/dbdataclasses/index.rst +++ b/docs/dbdataclasses/index.rst @@ -27,6 +27,7 @@ Dataclasses jobs knowledgeassistants marketplace + mason ml networking oauth2 diff --git a/docs/dbdataclasses/mason.rst b/docs/dbdataclasses/mason.rst new file mode 100644 index 000000000..3dd4a46c8 --- /dev/null +++ b/docs/dbdataclasses/mason.rst @@ -0,0 +1,94 @@ +Mason +===== + +These dataclasses are used in the SDK to represent API requests and responses for services in the ``databricks.sdk.service.mason`` module. + +.. py:currentmodule:: databricks.sdk.service.mason +.. autoclass:: AppendSessionItemsResponse + :members: + :undoc-members: + +.. autoclass:: ClearSessionItemsResponse + :members: + :undoc-members: + +.. autoclass:: ExtractMemoriesResponse + :members: + :undoc-members: + +.. autoclass:: ForkSessionResponse + :members: + :undoc-members: + +.. autoclass:: ListManagedMemoryEntriesResponse + :members: + :undoc-members: + +.. autoclass:: ListManagedMemoryStoresResponse + :members: + :undoc-members: + +.. autoclass:: ListSessionItemsResponse + :members: + :undoc-members: + +.. autoclass:: ListSessionStoresResponse + :members: + :undoc-members: + +.. autoclass:: ListSessionsResponse + :members: + :undoc-members: + +.. autoclass:: ManagedMemoryEntry + :members: + :undoc-members: + +.. autoclass:: ManagedMemoryEntrySearchResult + :members: + :undoc-members: + +.. py:class:: ManagedMemoryEntrySourceType + + Identifies the source that created a managed memory entry. + + .. py:attribute:: MANAGED_MEMORY_ENTRY_SOURCE_TYPE_AGENT + :value: "MANAGED_MEMORY_ENTRY_SOURCE_TYPE_AGENT" + + .. py:attribute:: MANAGED_MEMORY_ENTRY_SOURCE_TYPE_DREAMER + :value: "MANAGED_MEMORY_ENTRY_SOURCE_TYPE_DREAMER" + +.. autoclass:: ManagedMemoryStore + :members: + :undoc-members: + +.. autoclass:: PopSessionItemResponse + :members: + :undoc-members: + +.. autoclass:: SearchManagedMemoryEntriesResponse + :members: + :undoc-members: + +.. autoclass:: Session + :members: + :undoc-members: + +.. autoclass:: SessionItem + :members: + :undoc-members: + +.. autoclass:: SessionStore + :members: + :undoc-members: + +.. autoclass:: StorageBackend + :members: + :undoc-members: + +.. py:class:: StorageBackendType + + Type of service-managed storage backing a managed memory store. + + .. py:attribute:: STORAGE_BACKEND_TYPE_LAKEBASE + :value: "STORAGE_BACKEND_TYPE_LAKEBASE" diff --git a/docs/dbdataclasses/sql.rst b/docs/dbdataclasses/sql.rst index cf9f58e92..a726ad7f0 100644 --- a/docs/dbdataclasses/sql.rst +++ b/docs/dbdataclasses/sql.rst @@ -1020,7 +1020,7 @@ These dataclasses are used in the SDK to represent API requests and responses fo EndpointSpotInstancePolicy configures whether the endpoint should use spot instances. The breakdown of how the EndpointSpotInstancePolicy converts to per cloud configurations is: - +-------+--------------------------------------+--------------------------------+ | Cloud | COST_OPTIMIZED | RELIABILITY_OPTIMIZED | +-------+--------------------------------------+--------------------------------+ | AWS | On Demand Driver with Spot Executors | On Demand Driver and Executors | | AZURE | On Demand Driver and Executors | On Demand Driver and Executors | +-------+--------------------------------------+--------------------------------+ + - AWS, COST_OPTIMIZED: On Demand Driver with Spot Executors. - AWS, RELIABILITY_OPTIMIZED: On Demand Driver and Executors. - AZURE, COST_OPTIMIZED: On Demand Driver and Executors. - AZURE, RELIABILITY_OPTIMIZED: On Demand Driver and Executors. .. py:attribute:: COST_OPTIMIZED :value: "COST_OPTIMIZED" diff --git a/docs/packages.py b/docs/packages.py index de9259a9a..9b292809c 100644 --- a/docs/packages.py +++ b/docs/packages.py @@ -156,6 +156,11 @@ class Package: Package("jobs", "", ""), Package("knowledgeassistants", "Knowledge Assistants", "Manage Knowledge Assistants and related resources."), Package("marketplace", "", ""), + Package( + "mason", + "Mason", + "APIs for managing agent memory and durable session state. This interface is under active development and may change.", + ), Package("ml", "", ""), Package("networking", "Networking Endpoints", "These APIs manage endpoint configurations for this account."), Package("oauth2", "", ""), diff --git a/docs/workspace/aisearch/ai_search.rst b/docs/workspace/aisearch/ai_search.rst index 47d29cc76..ce9f3e8a8 100644 --- a/docs/workspace/aisearch/ai_search.rst +++ b/docs/workspace/aisearch/ai_search.rst @@ -136,7 +136,8 @@ :param filters_json: str (optional) JSON string describing query filters (e.g. ``{"id >": 5}``). :param max_results: int (optional) - Maximum number of results to return (the legacy ``num_results``). Defaults to 10. + Maximum number of results to return (the legacy ``num_results``). Defaults to 10. Prefer + ``page_size``; when both are set, ``page_size`` takes precedence. :param query_columns: List[str] (optional) Text columns to search for ``query_text``. When empty, all text columns are searched. :param query_text: str (optional) diff --git a/docs/workspace/bundledeployments/bundle_deployments.rst b/docs/workspace/bundledeployments/bundle_deployments.rst index ae10c5566..3c2355451 100644 --- a/docs/workspace/bundledeployments/bundle_deployments.rst +++ b/docs/workspace/bundledeployments/bundle_deployments.rst @@ -20,11 +20,9 @@ :param name: str The name of the version to complete. Format: deployments/{deployment_id}/versions/{version_id} :param completion_reason: :class:`VersionComplete` - The reason for completing the version. Must be a terminal reason: VERSION_COMPLETE_SUCCESS, - VERSION_COMPLETE_FAILURE, or VERSION_COMPLETE_FORCE_ABORT. + The reason for completing the version. Must be VERSION_COMPLETE_SUCCESS or VERSION_COMPLETE_FAILURE. :param force: bool (optional) - If true, force-completes the version even if the caller is not the original creator. The - completion_reason must be VERSION_COMPLETE_FORCE_ABORT when force is true. + If true, force-completes the version even if the caller is not the original creator. :returns: :class:`Version` diff --git a/docs/workspace/index.rst b/docs/workspace/index.rst index e39d2db90..eb2050dad 100644 --- a/docs/workspace/index.rst +++ b/docs/workspace/index.rst @@ -27,6 +27,7 @@ These APIs are available from WorkspaceClient jobs/index knowledgeassistants/index marketplace/index + mason/index ml/index oauth2/index pipelines/index diff --git a/docs/workspace/mason/index.rst b/docs/workspace/mason/index.rst new file mode 100644 index 000000000..698f84f9a --- /dev/null +++ b/docs/workspace/mason/index.rst @@ -0,0 +1,10 @@ + +Mason +===== + +APIs for managing agent memory and durable session state. This interface is under active development and may change. + +.. toctree:: + :maxdepth: 1 + + mason \ No newline at end of file diff --git a/docs/workspace/mason/mason.rst b/docs/workspace/mason/mason.rst new file mode 100644 index 000000000..9a5350ef2 --- /dev/null +++ b/docs/workspace/mason/mason.rst @@ -0,0 +1,431 @@ +``w.mason``: Mason.v1 +===================== +.. currentmodule:: databricks.sdk.service.mason + +.. py:class:: MasonAPI + + APIs for managing agent memory and durable session state. This interface is under active development and + may change. + + .. py:method:: append_session_items(parent: str, items: List[SessionItem]) -> AppendSessionItemsResponse + + Appends items to a session. + + :param parent: str + Resource name of the containing session, in the form + ``session-stores/{session_store_id}/sessions/{session_id}``. + :param items: List[:class:`SessionItem`] + Items to append atomically in request order. Concurrent append requests are serialized into one + committed order without exposing a numeric sequence in the public contract. + + :returns: :class:`AppendSessionItemsResponse` + + + .. py:method:: clear_session_items(parent: str) -> ClearSessionItemsResponse + + Clears all items from a session. + + :param parent: str + Resource name of the containing session, in the form + ``session-stores/{session_store_id}/sessions/{session_id}``. + + :returns: :class:`ClearSessionItemsResponse` + + + .. py:method:: create_memory(parent: str, managed_memory_entry: ManagedMemoryEntry [, managed_memory_entry_id: Optional[str]]) -> ManagedMemoryEntry + + Creates a managed memory entry using exclusive-create semantics. Callers may choose the entry ID; the + service generates one when it is omitted. Returns ``ALREADY_EXISTS`` when an entry with the same + actor, session, and path already exists. Omitted ``session_id`` is its own uniqueness key: two + omitted-session entries with the same actor and path conflict, but an omitted-session entry does not + conflict with a session-scoped entry at the same actor and path. + + :param parent: str + Managed memory store that will contain the entry, in the form + ``memory-stores/{managed_memory_store_id}``. + :param managed_memory_entry: :class:`ManagedMemoryEntry` + The managed memory entry to create. + :param managed_memory_entry_id: str (optional) + Optional caller-selected managed memory entry ID. The service generates an ID when omitted. + + :returns: :class:`ManagedMemoryEntry` + + + .. py:method:: create_memory_store(managed_memory_store: ManagedMemoryStore, managed_memory_store_id: str) -> ManagedMemoryStore + + Creates a managed memory store in the caller's workspace. + + :param managed_memory_store: :class:`ManagedMemoryStore` + The managed memory store to create. + :param managed_memory_store_id: str + Caller-provided, workspace-unique managed memory store ID. It must be 3-56 characters, begin with a + lowercase letter, contain only lowercase letters, digits, and hyphens, and end with a letter or + digit. + + :returns: :class:`ManagedMemoryStore` + + + .. py:method:: create_session(parent: str, session: Session [, session_id: Optional[str]]) -> Session + + Creates a session within a session store. + + :param parent: str + Resource name of the containing session store, in the form ``session-stores/{session_store_id}``. + :param session: :class:`Session` + The session to create. ``actor_id`` is required. A session with ``parent_session_id`` is a child and + must use its parent's ``actor_id``. Independent forks are created only through ``ForkSession``. + :param session_id: str (optional) + Optional caller-selected session ID. The service generates a UUID when this field is omitted. The ID + must be unique; a collision returns ``ALREADY_EXISTS``. + + :returns: :class:`Session` + + + .. py:method:: create_session_store(session_store: SessionStore, session_store_id: str) -> SessionStore + + Creates a session store. + + :param session_store: :class:`SessionStore` + The session store to create. + :param session_store_id: str + Caller-provided, workspace-unique session store ID. It must be 3-55 characters, begin with a + lowercase letter, and contain only lowercase letters, digits, and hyphens. + + :returns: :class:`SessionStore` + + + .. py:method:: delete_memory(name: str) + + Deletes a managed memory entry by resource name. Returns ``NOT_FOUND`` when the entry does not exist + in the caller's workspace. + + :param name: str + Resource name in the form + ``memory-stores/{managed_memory_store_id}/entries/{managed_memory_entry_id}``. + + + + + .. py:method:: delete_memory_store(name: str) + + Deletes a managed memory store by resource name. Returns ``NOT_FOUND`` when the store does not exist + in the caller's workspace. + + :param name: str + Resource name in the form ``memory-stores/{managed_memory_store_id}``. + + + + + .. py:method:: delete_session(name: str) + + Deletes a session, its items, and any descendant sessions recursively. Independently retained memory + is not deleted. + + :param name: str + Resource name in the form ``session-stores/{session_store_id}/sessions/{session_id}``. + + + + + .. py:method:: delete_session_store(name: str) + + Deletes a session store, its sessions and items, and its service-managed storage. Memory entries + retained by a separate Memory Store are not deleted. + + :param name: str + Resource name in the form ``session-stores/{session_store_id}``. + + + + + .. py:method:: extract_memories(session_store: str, session_id: str, memory_store: str [, dry_run: Optional[bool], instructions: Optional[str]]) -> ExtractMemoriesResponse + + Synchronously extracts memories from a single session into the given memory store, returning the + entries that were written. + + :param session_store: str + Session store containing the session, in the form ``session-stores/{session_store_id}``. + :param session_id: str + Identifier of the session whose transcript is distilled into memories. + :param memory_store: str + Managed memory store the extracted entries are written to, in the form + ``memory-stores/{managed_memory_store_id}``. + :param dry_run: bool (optional) + When true, extract and return the entries without writing them to the memory store. Defaults to + false, which persists the extracted entries and returns them. + :param instructions: str (optional) + Instructions steering what is extracted from the session. + + :returns: :class:`ExtractMemoriesResponse` + + + .. py:method:: fork_session(parent: str, source_session_id: str, actor_id: str [, metadata: Optional[Dict[str, str]], session_id: Optional[str], up_to_item_id: Optional[str]]) -> ForkSessionResponse + + Forks a session into an independent top-level copy. + + :param parent: str + Resource name of the containing session store, in the form ``session-stores/{session_store_id}``. + :param source_session_id: str + ID of the session to copy. + :param actor_id: str + Opaque caller-provided identifier for the application actor associated with the forked session. + :param metadata: Dict[str,str] (optional) + Optional metadata for the fork. + :param session_id: str (optional) + Optional unique ID for the forked session. A collision returns ``ALREADY_EXISTS``. + :param up_to_item_id: str (optional) + Optional last item ID to copy through, inclusively. When omitted, the fork atomically copies all + items committed before the fork operation begins. + + :returns: :class:`ForkSessionResponse` + + + .. py:method:: get_memory(name: str [, read_mask: Optional[FieldMask]]) -> ManagedMemoryEntry + + Retrieves a managed memory entry, including its content, by resource name. Returns ``NOT_FOUND`` when + the entry does not exist in the caller's workspace. + + :param name: str + Resource name in the form + ``memory-stores/{managed_memory_store_id}/entries/{managed_memory_entry_id}``. + :param read_mask: FieldMask (optional) + Fields to return, using proto field names such as ``content`` (not ``contents``). An omitted or + empty mask returns the full entry, including ``content``; a non-empty mask returns only the + requested fields. + + :returns: :class:`ManagedMemoryEntry` + + + .. py:method:: get_memory_store(name: str) -> ManagedMemoryStore + + Retrieves a managed memory store by resource name. Returns ``NOT_FOUND`` when the store does not exist + in the caller's workspace. + + :param name: str + Resource name in the form ``memory-stores/{managed_memory_store_id}``. + + :returns: :class:`ManagedMemoryStore` + + + .. py:method:: get_session(name: str) -> Session + + Gets a session by resource name. + + :param name: str + Resource name in the form ``session-stores/{session_store_id}/sessions/{session_id}``. + + :returns: :class:`Session` + + + .. py:method:: get_session_store(name: str) -> SessionStore + + Gets a session store by resource name. + + :param name: str + Resource name in the form ``session-stores/{session_store_id}``. + + :returns: :class:`SessionStore` + + + .. py:method:: list_memories(parent: str, actor_id: str [, page_size: Optional[int], page_token: Optional[str], path_prefix: Optional[str], read_mask: Optional[FieldMask], session_id: Optional[str]]) -> Iterator[ManagedMemoryEntry] + + Lists managed memory entries for one actor. Optional ``session_id`` and ``path_prefix`` further + restrict the actor partition; ``read_mask`` selects fields in each returned entry. + + :param parent: str + Managed memory store whose entries are listed, in the form + ``memory-stores/{managed_memory_store_id}``. + :param actor_id: str + Customer-provided identifier for the actor whose entries are listed. + :param page_size: int (optional) + Maximum number of entries to return. The service may return fewer entries than requested. Defaults + to 10; must be between 1 and 100. + :param page_token: str (optional) + Opaque pagination token from a previous ListManagedMemoryEntries response. + :param path_prefix: str (optional) + Optional path prefix used to restrict entries within the actor partition. + :param read_mask: FieldMask (optional) + Fields to return in each entry, using proto field names such as ``content`` (not ``contents``). An + omitted or empty mask returns each full entry, including ``content``; a non-empty mask returns only + the requested fields. + :param session_id: str (optional) + Optional session identifier. When set, only entries with this exact ``session_id`` are returned. + Omitted-session (cross-session) entries are not included. + + :returns: Iterator over :class:`ManagedMemoryEntry` + + + .. py:method:: list_memory_stores( [, page_size: Optional[int], page_token: Optional[str]]) -> Iterator[ManagedMemoryStore] + + Lists managed memory stores in the caller's workspace. + + :param page_size: int (optional) + Maximum number of stores to return. The service may return fewer stores than requested. Defaults to + 10; must be between 1 and 100. + :param page_token: str (optional) + Opaque pagination token from a previous ListManagedMemoryStores response. + + :returns: Iterator over :class:`ManagedMemoryStore` + + + .. py:method:: list_session_items(parent: str [, order_by: Optional[str], page_size: Optional[int], page_token: Optional[str]]) -> Iterator[SessionItem] + + Lists items in a session. + + :param parent: str + Resource name of the containing session, in the form + ``session-stores/{session_store_id}/sessions/{session_id}``. + :param order_by: str (optional) + Sort order. Supported values are ``create_time asc`` and ``create_time desc``. The default is + ``create_time desc``, which returns the most recently appended items first. Equal timestamps are + resolved by committed append order in the requested direction. + :param page_size: int (optional) + Maximum number of items to return. Defaults to 10; must be between 1 and 100. + :param page_token: str (optional) + Token returned by a previous list request. + + :returns: Iterator over :class:`SessionItem` + + + .. py:method:: list_session_stores( [, page_size: Optional[int], page_token: Optional[str]]) -> Iterator[SessionStore] + + Lists session stores. + + :param page_size: int (optional) + Maximum number of session stores to return. Defaults to 10; must be between 1 and 100. + :param page_token: str (optional) + Token returned by a previous list request. + + :returns: Iterator over :class:`SessionStore` + + + .. py:method:: list_sessions(parent: str [, filter: Optional[str], order_by: Optional[str], page_size: Optional[int], page_token: Optional[str]]) -> Iterator[Session] + + Lists sessions within a session store. + + :param parent: str + Resource name of the containing session store, in the form ``session-stores/{session_store_id}``. + :param filter: str (optional) + Filter expression. Supported fields include ``actor_id`` and ``metadata``; for example, ``actor_id = + "support-customer-123"``. + :param order_by: str (optional) + Sort order. Defaults to ``last_activity_time desc``. Page-token continuation is exactly-once when + ordering by ``create_time`` (immutable); ordering by ``last_activity_time`` is best-effort, because + that value changes as a session gains activity, so a session updated between page requests may be + repeated or skipped. To enumerate every session exactly once, order by ``create_time``. + :param page_size: int (optional) + Maximum number of sessions to return. Defaults to 10; must be between 1 and 100. + :param page_token: str (optional) + Token returned by a previous list request. + + :returns: Iterator over :class:`Session` + + + .. py:method:: pop_session_item(parent: str) -> PopSessionItemResponse + + Pops the newest item from a session. + + :param parent: str + Resource name of the containing session, in the form + ``session-stores/{session_store_id}/sessions/{session_id}``. + + :returns: :class:`PopSessionItemResponse` + + + .. py:method:: search_memories(parent: str, actor_id: str, query: str [, limit: Optional[int], page_size: Optional[int], page_token: Optional[str], path_prefix: Optional[str], read_mask: Optional[FieldMask], session_id: Optional[str]]) -> Iterator[ManagedMemoryEntrySearchResult] + + Searches managed memory entries by text query for one actor. Returns matching entries and scores + ranked by relevance; ``read_mask`` selects fields in each returned entry. + + :param parent: str + Managed memory store whose entries are searched, in the form + ``memory-stores/{managed_memory_store_id}``. + :param actor_id: str + Customer-provided identifier for the actor whose entries are searched. + :param query: str + Free-form search query. + :param limit: int (optional) + Deprecated alias for ``page_size``. When both fields are set, their values must match. + :param page_size: int (optional) + Maximum number of relevance-ranked entries to return. Defaults to 10 and must be between 1 and 100. + :param page_token: str (optional) + Reserved for pagination compatibility. The server currently ignores this field because Search + returns a ranked top-N result set. + :param path_prefix: str (optional) + Optional absolute, case-sensitive path prefix used to restrict searched entries within the actor + partition. The prefix must begin with ``/`` and must not contain empty, ``.`` or ``..`` segments. + :param read_mask: FieldMask (optional) + Fields to return in each matching entry, using proto field names such as ``content`` (not + ``contents``). An omitted or empty mask returns each full entry, including ``content``; a non-empty + mask returns only the requested fields. Search scores are always returned. + + The field mask must be a single string, with multiple fields separated by commas (no spaces). The + field path is relative to the resource object, using a dot (``.``) to navigate sub-fields (e.g., + ``author.given_name``). Specification of elements in sequence or map fields is not allowed, as only + the entire collection field can be specified. Field names must exactly match the resource field + names. + :param session_id: str (optional) + Optional session identifier. When set, only entries with this exact ``session_id`` are searched. + Omitted-session (cross-session) entries are not included. + + :returns: Iterator over :class:`ManagedMemoryEntrySearchResult` + + + .. py:method:: update_memory(name: str, managed_memory_entry: ManagedMemoryEntry, update_mask: FieldMask) -> ManagedMemoryEntry + + Updates selected mutable fields on a managed memory entry. Identity fields are immutable. + + :param name: str + Resource name in the form + ``memory-stores/{managed_memory_store_id}/entries/{managed_memory_entry_id}``. + :param managed_memory_entry: :class:`ManagedMemoryEntry` + The managed memory entry to update. + :param update_mask: FieldMask + Fields to update. Only ``content`` and ``description`` may be updated. + + :returns: :class:`ManagedMemoryEntry` + + + .. py:method:: update_memory_store(name: str, managed_memory_store: ManagedMemoryStore, update_mask: FieldMask) -> ManagedMemoryStore + + Updates a managed memory store's description. + + :param name: str + Resource name in the form ``memory-stores/{managed_memory_store_id}``. + :param managed_memory_store: :class:`ManagedMemoryStore` + The managed memory store to update. ``name`` is taken from the URL. + :param update_mask: FieldMask + Only ``description`` may be updated. + + :returns: :class:`ManagedMemoryStore` + + + .. py:method:: update_session(name: str, session: Session, update_mask: FieldMask) -> Session + + Updates a session's mutable fields. + + :param name: str + Resource name in the form ``session-stores/{session_store_id}/sessions/{session_id}``. + :param session: :class:`Session` + Session to update. + :param update_mask: FieldMask + Fields to update. Only ``metadata`` is mutable; any other path returns ``INVALID_PARAMETER_VALUE``. + + :returns: :class:`Session` + + + .. py:method:: update_session_store(name: str, session_store: SessionStore, update_mask: FieldMask) -> SessionStore + + Updates a session store's description and metadata. + + :param name: str + Resource name in the form ``session-stores/{session_store_id}``. + :param session_store: :class:`SessionStore` + Session store to update. + :param update_mask: FieldMask + Fields to update. Only ``description`` and ``metadata`` are mutable; any other path returns + ``INVALID_PARAMETER_VALUE``. + + :returns: :class:`SessionStore` + \ No newline at end of file diff --git a/docs/workspace/ml/feature_engineering.rst b/docs/workspace/ml/feature_engineering.rst index 89027f188..d1b9a58ee 100644 --- a/docs/workspace/ml/feature_engineering.rst +++ b/docs/workspace/ml/feature_engineering.rst @@ -13,7 +13,10 @@ :param feature_full_names: List[str] Full names of the features to backfill. :param backfill_ranges: List[:class:`BackfillRange`] - Output ranges to backfill. + Output ranges to backfill. TODO[FS-1372]: audit_mode=INCLUDE is intentionally omitted. The + annotation redactor cannot serialize google.protobuf.Timestamp leaves (start_time/end_time), so + annotating this field does not surface the ranges in audit logs. See + FeatureStoreEventDefinitions.BackfillFeatures. :param budget_policy_id: str (optional) The budget policy ID, in UUID format, used to attribute the serverless compute cost of this backfill. If not specified, a default budget policy may be applied. diff --git a/docs/workspace/tags/workspace_entity_tag_assignments.rst b/docs/workspace/tags/workspace_entity_tag_assignments.rst index e91770687..cf735ec8b 100644 --- a/docs/workspace/tags/workspace_entity_tag_assignments.rst +++ b/docs/workspace/tags/workspace_entity_tag_assignments.rst @@ -20,8 +20,8 @@ Delete a tag assignment :param entity_type: str - The type of entity to which the tag is assigned. Allowed values are apps, dashboards, designerfiles, - geniespaces, notebooks + The type of entity to which the tag is assigned. Allowed values are apps, dashboards, + designer-files, geniespaces, notebooks :param entity_id: str The identifier of the entity to which the tag is assigned. For apps, the entity_id is the app name :param tag_key: str @@ -35,8 +35,8 @@ Get a tag assignment :param entity_type: str - The type of entity to which the tag is assigned. Allowed values are apps, dashboards, designerfiles, - geniespaces, notebooks + The type of entity to which the tag is assigned. Allowed values are apps, dashboards, + designer-files, geniespaces, notebooks :param entity_id: str The identifier of the entity to which the tag is assigned. For apps, the entity_id is the app name :param tag_key: str @@ -50,8 +50,8 @@ List the tag assignments for an entity :param entity_type: str - The type of entity to which the tag is assigned. Allowed values are apps, dashboards, designerfiles, - geniespaces, notebooks + The type of entity to which the tag is assigned. Allowed values are apps, dashboards, + designer-files, geniespaces, notebooks :param entity_id: str The identifier of the entity to which the tag is assigned. For apps, the entity_id is the app name :param page_size: int (optional) @@ -67,8 +67,8 @@ Update a tag assignment :param entity_type: str - The type of entity to which the tag is assigned. Allowed values are apps, dashboards, designerfiles, - geniespaces, notebooks + The type of entity to which the tag is assigned. Allowed values are apps, dashboards, + designer-files, geniespaces, notebooks :param entity_id: str The identifier of the entity to which the tag is assigned. For apps, the entity_id is the app name :param tag_key: str diff --git a/pyproject.toml b/pyproject.toml index 93c2024a5..edcbe610f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,13 +48,10 @@ dev = [ "ipywidgets", "requests-mock", "pyfakefs", - "databricks-connect", - "pytest-rerunfailures", "openai", "langchain-openai", "httpx", "grpcio<2", # the sandbox extra's dep; also needed to run the gRPC transport unit tests - "build", # some integration tests depend on the databricks-sdk-py wheel "check-manifest", ] notebook = [ diff --git a/tests/conftest.py b/tests/conftest.py index 7025c4395..0cc5b1c47 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,9 +10,6 @@ from .clock import FakeClock -# Side-effect import: ruff would otherwise strip this as unused. -from .integration.conftest import restorable_env # noqa: F401 - @pytest.fixture(autouse=True) def stub_host_metadata(mocker): diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py deleted file mode 100644 index 82f6d943e..000000000 --- a/tests/integration/conftest.py +++ /dev/null @@ -1,247 +0,0 @@ -import json -import logging -import os -import pathlib -import string -import sys - -import pytest - -from databricks.sdk import AccountClient, FilesAPI, FilesExt, WorkspaceClient -from databricks.sdk.config import ConfigAttribute -from databricks.sdk.core import Config -from databricks.sdk.environments import Cloud -from databricks.sdk.service.catalog import VolumeType - - -@pytest.fixture(autouse=True) -def stub_host_metadata(): - """Override root conftest stub — integration tests hit real endpoints.""" - - -def pytest_addoption(parser): - # make logging sensible and readable. - parser.addini( - "log_format", - "...", - "string", - "%(asctime)s [%(name)s][%(levelname)s] %(message)s", - ) - parser.addini("log_date_format", "...", "string", "%H:%M") - - -def pytest_configure(config): - # disable urllib3, as it adds more noise - logger = logging.getLogger("urllib3.connectionpool") - logger.propagate = False - - config.addinivalue_line( - "markers", - "integration: marks tests as those requiring a real Databricks backend", - ) - config.addinivalue_line( - "markers", - "benchmark: marks tests as benchmarks which should not be run by default", - ) - - -def pytest_collection_modifyitems(items): - # safer to refer to fixture fns instead of strings - client_fixtures = [x.__name__ for x in [a, w, ucws, ucacct, unified_config, isolated_env]] - for item in items: - current_fixtures = getattr(item, "fixturenames", ()) - for requires_client in client_fixtures: - if requires_client in current_fixtures: - item.add_marker("integration") - - -@pytest.fixture(scope="session") -def random(): - import random - - def inner(k=16) -> str: - charset = string.ascii_uppercase + string.ascii_lowercase + string.digits - return "".join(random.choices(charset, k=int(k))) - - return inner - - -@pytest.fixture(scope="session") -def a(env_or_skip) -> AccountClient: - _load_debug_env_if_runs_from_ide("account") - env_or_skip("CLOUD_ENV") - account_client = AccountClient() - if env_or_skip("TEST_ENVIRONMENT_TYPE") not in ["ACCOUNT", "UC_ACCOUNT"]: - pytest.skip("not Databricks Account environment") - return account_client - - -@pytest.fixture(scope="session") -def ucacct(env_or_skip) -> AccountClient: - _load_debug_env_if_runs_from_ide("ucacct") - env_or_skip("CLOUD_ENV") - account_client = AccountClient() - if env_or_skip("TEST_ENVIRONMENT_TYPE") not in ["UC_ACCOUNT"]: - pytest.skip("not Databricks UC Account environment") - if "TEST_METASTORE_ID" not in os.environ: - pytest.skip("not in Unity Catalog Workspace test env") - return account_client - - -@pytest.fixture(scope="session") -def unified_config(env_or_skip) -> Config: - _load_debug_env_if_runs_from_ide("account") - env_or_skip("CLOUD_ENV") - config = Config() - config.host = env_or_skip("UNIFIED_HOST") - config.workspace_id = env_or_skip("TEST_WORKSPACE_ID") - - config._fix_host_if_needed() - return config - - -@pytest.fixture(scope="session") -def w(env_or_skip) -> WorkspaceClient: - _load_debug_env_if_runs_from_ide("workspace") - env_or_skip("CLOUD_ENV") - if env_or_skip("TEST_ENVIRONMENT_TYPE") not in ["WORKSPACE", "UC_WORKSPACE"]: - pytest.skip("not Databricks Workspace environment") - return WorkspaceClient() - - -@pytest.fixture(scope="session") -def ucws(env_or_skip) -> WorkspaceClient: - _load_debug_env_if_runs_from_ide("ucws") - env_or_skip("CLOUD_ENV") - if env_or_skip("TEST_ENVIRONMENT_TYPE") not in ["UC_WORKSPACE"]: - pytest.skip("not Databricks UC Workspace environment") - if "TEST_METASTORE_ID" not in os.environ: - pytest.skip("not Databricks UC Workspace environment") - return WorkspaceClient() - - -@pytest.fixture -def isolated_env(monkeypatch): - """Fixture for tests that need to construct a client with explicit parameters - only, without Config picking up env vars automatically. - - Usage: - def test_something(isolated_env): - env = isolated_env("workspace") # loads debug-env key when running from IDE - host = env("UNIFIED_HOST") - - The first call takes a debug-env key (used only when running from an IDE). - Returns a callable that looks up vars from the original environment snapshot - (skipping the test if missing). All Config-related env vars are removed so - the client only sees explicitly passed parameters.""" - - def init(debug_env_key: str): - _load_debug_env_if_runs_from_ide(debug_env_key) - original_env = os.environ.copy() - - for attr in vars(Config).values(): - if isinstance(attr, ConfigAttribute) and attr.env: - monkeypatch.delenv(attr.env, raising=False) - - def get(var: str) -> str: - if var not in original_env: - pytest.skip(f"Environment variable {var} is missing") - return original_env[var] - - return get - - return init - - -@pytest.fixture(scope="session") -def env_or_skip(): - def inner(var: str) -> str: - if var not in os.environ: - pytest.skip(f"Environment variable {var} is missing") - return os.environ[var] - - return inner - - -@pytest.fixture(scope="session") -def skip_aws_uc_workspace(): - if ( - os.getenv("CLOUD_PROVIDER") == "AWS" - and os.getenv("CLOUD_ENV") == "ucws" - and os.getenv("TEST_ENVIRONMENT_TYPE") == "UC_WORKSPACE" - ): - pytest.skip("Slow test causes automated releases to fail") - - -@pytest.fixture(scope="session") -def schema(ucws, random): - schema = ucws.schemas.create("dbfs-" + random(), "main") - yield schema - ucws.schemas.delete(schema.full_name) - - -@pytest.fixture(scope="session") -def volume(ucws, schema): - volume = ucws.volumes.create("main", schema.name, "dbfs-test", VolumeType.MANAGED) - yield "/Volumes/" + volume.full_name.replace(".", "/") - ucws.volumes.delete(volume.full_name) - - -@pytest.fixture(scope="session", params=[False, True]) -def files_api(request, ucws) -> FilesAPI: - if request.param: - # ensure new Files API client is used for files of any size - ucws.config.multipart_upload_min_stream_size = 0 - # enable new Files API client - return FilesExt(ucws.api_client, ucws.config) - else: - # use the default client - return ucws.files - - -@pytest.fixture() -def workspace_dir(w, random): - directory = f"/Users/{w.current_user.me().user_name}/dir-{random(12)}" - w.workspace.mkdirs(directory) - yield directory - w.workspace.delete(directory, recursive=True) - - -def _load_debug_env_if_runs_from_ide(key) -> bool: - if not _is_in_debug(): - return False - conf_file = pathlib.Path.home() / ".databricks/debug-env.json" - with conf_file.open("r") as f: - conf = json.load(f) - if key not in conf: - raise KeyError(f"{key} not found in ~/.databricks/debug-env.json") - for k, v in conf[key].items(): - os.environ[k] = v - return True - - -def _is_in_debug() -> bool: - return os.path.basename(sys.argv[0]) in [ - "_jb_pytest_runner.py", - "testlauncher.py", - ] - - -def _is_cloud(cloud: Cloud) -> bool: - """Check if the CLOUD_PROVIDER environment variable matches the specified cloud provider.""" - cloud_provider = os.getenv("CLOUD_PROVIDER", "").upper() - cloud_upper = cloud.value.upper() - return cloud_provider == cloud_upper - - -@pytest.fixture(scope="function") -def restorable_env(): - import os - - current_env = os.environ.copy() - yield - for k, v in os.environ.items(): - if k not in current_env: - del os.environ[k] - elif v != current_env[k]: - os.environ[k] = current_env[k] diff --git a/tests/integration/test_auth.py b/tests/integration/test_auth.py deleted file mode 100644 index 9ee4cdb36..000000000 --- a/tests/integration/test_auth.py +++ /dev/null @@ -1,527 +0,0 @@ -import base64 -import io -import json -import re -import shutil -import subprocess -import sys -import typing -import urllib.parse -from contextlib import ExitStack -from functools import partial -from pathlib import Path - -import pytest - -from databricks.sdk import AccountClient, WorkspaceClient -from databricks.sdk.config import Config -from databricks.sdk.errors import NotFound, PermissionDenied -from databricks.sdk.service import iam, oauth2 -from databricks.sdk.service.compute import ClusterSpec, DataSecurityMode, Library, ResultType, SparkVersion -from databricks.sdk.service.jobs import NotebookTask, Task, ViewType -from databricks.sdk.service.workspace import ImportFormat, Language - - -@pytest.fixture -def fresh_wheel_file(tmp_path) -> Path: - this_file = Path(__file__) - project_root = this_file.parent.parent.parent.absolute() - build_root = tmp_path / "databricks-sdk-py" - shutil.copytree(project_root, build_root) - try: - completed_process = subprocess.run( - [sys.executable, "-m", "build"], - capture_output=True, - cwd=build_root, - ) - if completed_process.returncode != 0: - raise RuntimeError(completed_process.stderr) - - from databricks.sdk.version import __version__ - - filename = f"databricks_sdk-{__version__}-py3-none-any.whl" - wheel_file = build_root / "dist" / filename - - return wheel_file - except subprocess.CalledProcessError as e: - raise RuntimeError(e.stderr) - - -@pytest.mark.parametrize("mode", [DataSecurityMode.SINGLE_USER, DataSecurityMode.USER_ISOLATION]) -def test_runtime_auth_from_interactive_on_uc(skip_aws_uc_workspace, ucws, fresh_wheel_file, env_or_skip, random, mode): - instance_pool_id = env_or_skip("TEST_INSTANCE_POOL_ID") - latest = ucws.clusters.select_spark_version(latest=True) - - my_user = ucws.current_user.me().user_name - - workspace_location = f"/Users/{my_user}/wheels/{random(10)}" - ucws.workspace.mkdirs(workspace_location) - - wsfs_wheel = f"{workspace_location}/{fresh_wheel_file.name}" - with fresh_wheel_file.open("rb") as f: - ucws.workspace.upload(wsfs_wheel, f, format=ImportFormat.AUTO) - - from databricks.sdk.service.compute import Language - - interactive_cluster = ucws.clusters.create( - cluster_name=f"native-auth-on-{mode.name}", - spark_version=latest, - instance_pool_id=instance_pool_id, - autotermination_minutes=10, - num_workers=1, - data_security_mode=mode, - ).result() - ctx = ucws.command_execution.create(cluster_id=interactive_cluster.cluster_id, language=Language.PYTHON).result() - run = partial( - ucws.command_execution.execute, - cluster_id=interactive_cluster.cluster_id, - context_id=ctx.id, - language=Language.PYTHON, - ) - try: - res = run(command=f"%pip install /Workspace{wsfs_wheel}\ndbutils.library.restartPython()").result() - results = res.results - if results.result_type != ResultType.TEXT: - msg = f"({mode}) unexpected result type: {results.result_type}: {results.summary}\n{results.cause}" - raise RuntimeError(msg) - - res = run( - command="\n".join( - [ - "from databricks.sdk import WorkspaceClient", - "w = WorkspaceClient()", - "me = w.current_user.me()", - "print(me.user_name)", - ] - ) - ).result() - assert res.results.result_type == ResultType.TEXT, f"unexpected result type: {res.results.result_type}" - - assert my_user == res.results.data, f"unexpected user: {res.results.data}" - finally: - ucws.clusters.permanent_delete(interactive_cluster.cluster_id) - - -def _get_lts_versions(w) -> typing.List[SparkVersion]: - v = w.clusters.spark_versions() - lts_runtimes = [ - x - for x in v.versions - if "LTS" in x.name and "-ml" not in x.key and "-photon" not in x.key and "-aarch64" not in x.key - ] - return lts_runtimes - - -def test_runtime_auth_from_jobs_volumes( - skip_aws_uc_workspace, ucws, files_api, fresh_wheel_file, env_or_skip, random, volume -): - dbr_versions = [v for v in _get_lts_versions(ucws) if int(v.key.split(".")[0]) >= 15] - - volume_wheel = f"{volume}/tmp/wheels/{random(10)}/{fresh_wheel_file.name}" - with fresh_wheel_file.open("rb") as f: - files_api.upload(volume_wheel, f) - - lib = Library(whl=volume_wheel) - return _test_runtime_auth_from_jobs_inner(ucws, env_or_skip, random, dbr_versions, lib) - - -def test_runtime_auth_from_jobs_dbfs(skip_aws_uc_workspace, w, fresh_wheel_file, env_or_skip, random): - # Library installation from DBFS is not supported past DBR 14.3. - # DBR < 13 ships Python < 3.10 which is below our requires-python. - dbr_versions = [v for v in _get_lts_versions(w) if 13 <= int(v.key.split(".")[0]) < 15] - - dbfs_wheel = f"/tmp/wheels/{random(10)}/{fresh_wheel_file.name}" - with fresh_wheel_file.open("rb") as f: - w.dbfs.upload(dbfs_wheel, f) - - lib = Library(whl=f"dbfs:{dbfs_wheel}") - return _test_runtime_auth_from_jobs_inner(w, env_or_skip, random, dbr_versions, lib) - - -def _test_runtime_auth_from_jobs_inner(w, env_or_skip, random, dbr_versions, library): - instance_pool_id = env_or_skip("TEST_INSTANCE_POOL_ID") - - my_name = w.current_user.me().user_name - notebook_path = f"/Users/{my_name}/notebook-native-auth" - notebook_content = io.BytesIO( - b""" -from databricks.sdk import WorkspaceClient -w = WorkspaceClient() -me = w.current_user.me() -print(me.user_name)""" - ) - - from databricks.sdk.service.workspace import Language - - w.workspace.upload( - notebook_path, - notebook_content, - language=Language.PYTHON, - overwrite=True, - ) - - tasks = [] - for v in dbr_versions: - t = Task( - task_key=f"test_{v.key.replace('.', '_')}", - notebook_task=NotebookTask(notebook_path=notebook_path), - new_cluster=ClusterSpec( - spark_version=v.key, - num_workers=1, - instance_pool_id=instance_pool_id, - # GCP uses "custom" data security mode by default, which does not support UC. - data_security_mode=DataSecurityMode.SINGLE_USER, - ), - libraries=[library], - ) - tasks.append(t) - - waiter = w.jobs.submit(run_name=f"Runtime Native Auth {random(10)}", tasks=tasks) - run = waiter.result() - for task_key, output in _task_outputs(w, run).items(): - assert my_name in output, f"{task_key} does not work with notebook native auth" - - -def _task_outputs(w, run): - notebook_model_re = re.compile(r"var __DATABRICKS_NOTEBOOK_MODEL = '(.*)';", re.MULTILINE) - - task_outputs = {} - for task_run in run.tasks: - output = "" - run_output = w.jobs.export_run(task_run.run_id) - for view in run_output.views: - if view.type != ViewType.NOTEBOOK: - continue - for b64 in notebook_model_re.findall(view.content): - url_encoded: bytes = base64.b64decode(b64) - json_encoded = urllib.parse.unquote(url_encoded.decode("utf-8")) - notebook_model = json.loads(json_encoded) - for command in notebook_model["commands"]: - results_data = command["results"]["data"] - if isinstance(results_data, str): - output += results_data - else: - for data in results_data: - output += data["data"] - task_outputs[task_run.task_key] = output - return task_outputs - - -def test_wif_account(ucacct, env_or_skip, random): - sp = ucacct.service_principals.create( - active=True, - display_name="py-sdk-test-" + random(), - roles=[iam.ComplexValue(value="account_admin")], - ) - - ucacct.service_principal_federation_policy.create( - policy=oauth2.FederationPolicy( - oidc_policy=oauth2.OidcFederationPolicy( - issuer="https://token.actions.githubusercontent.com", - audiences=["https://github.com/databricks-eng"], - subject="repo:databricks-eng/eng-dev-ecosystem:environment:integration-tests", - ) - ), - service_principal_id=sp.id, - ) - - ac = AccountClient( - host=ucacct.config.host, - account_id=ucacct.config.account_id, - client_id=sp.application_id, - auth_type="github-oidc", - token_audience="https://github.com/databricks-eng", - ) - - groups = ac.groups.list() - - next(groups) - - -def test_wif_workspace(ucacct, env_or_skip, random): - workspace_id = env_or_skip("TEST_WORKSPACE_ID") - workspace_url = env_or_skip("TEST_WORKSPACE_URL") - - sp = ucacct.service_principals.create( - active=True, - display_name="py-sdk-test-" + random(), - ) - - ucacct.service_principal_federation_policy.create( - policy=oauth2.FederationPolicy( - oidc_policy=oauth2.OidcFederationPolicy( - issuer="https://token.actions.githubusercontent.com", - audiences=["https://github.com/databricks-eng"], - subject="repo:databricks-eng/eng-dev-ecosystem:environment:integration-tests", - ) - ), - service_principal_id=sp.id, - ) - - ucacct.workspace_assignment.update( - workspace_id=workspace_id, - principal_id=sp.id, - permissions=[iam.WorkspacePermission.ADMIN], - ) - - ws = WorkspaceClient( - host=workspace_url, - client_id=sp.application_id, - auth_type="github-oidc", - token_audience="https://github.com/databricks-eng", - ) - - ws.current_user.me() - - -def _ignore_not_found(action): - try: - action() - except NotFound: - pass - - -def test_wif_workspace_group_role_isolation(ucacct, env_or_skip, random): - """Verifies group-role WIF can read a resource that normal WIF credentials cannot.""" - # Use the GitHub Actions OIDC environment and an account administrator to arrange the test. - env_or_skip("ACTIONS_ID_TOKEN_REQUEST_URL") - workspace_id = int(env_or_skip("TEST_WORKSPACE_ID")) - workspace_url = env_or_skip("TEST_WORKSPACE_URL") - audience = "https://github.com/databricks-eng" - - # Use administrator credentials to create the workspace resource and set its permissions. - workspace_admin = WorkspaceClient(host=workspace_url) - - with ExitStack() as cleanup: - # Create the service principal whose normal and role-based WIF access will be compared. - service_principal = ucacct.service_principals_v2.create( - active=True, - display_name="py-sdk-wif-role-sp-" + random(), - ) - cleanup.callback( - _ignore_not_found, - lambda: ucacct.service_principals_v2.delete(service_principal.id), - ) - service_principal_id = int(service_principal.id) - - # Give the service principal basic workspace access without granting notebook access. - ucacct.workspace_assignment.update( - workspace_id, - service_principal_id, - permissions=[iam.WorkspacePermission.USER], - ) - cleanup.callback( - _ignore_not_found, - lambda: ucacct.workspace_assignment.delete(workspace_id, service_principal_id), - ) - - # Create the group that represents the temporary workspace role. - group = ucacct.groups_v2.create(display_name="py-sdk-wif-role-group-" + random()) - cleanup.callback( - _ignore_not_found, - lambda: ucacct.groups_v2.delete(group.id), - ) - group_id = int(group.id) - - # Assign the group to the workspace so that it can receive workspace permissions. - ucacct.workspace_assignment.update( - workspace_id, - group_id, - permissions=[iam.WorkspacePermission.USER], - ) - cleanup.callback( - _ignore_not_found, - lambda: ucacct.workspace_assignment.delete(workspace_id, group_id), - ) - - # Allow the service principal to assume the group role. - rule_set_name = f"accounts/{ucacct.config.account_id}/groups/{group.id}/ruleSets/default" - rule_set = ucacct.access_control.get_rule_set(rule_set_name, "") - ucacct.access_control.update_rule_set( - rule_set_name, - iam.RuleSetUpdateRequest( - name=rule_set_name, - etag=rule_set.etag, - grant_rules=[ - *(rule_set.grant_rules or []), - iam.GrantRule( - principals=[f"servicePrincipals/{service_principal.application_id}"], - role="roles/group.assumer", - ), - ], - ), - ) - - # Trust this repository's GitHub OIDC identity to authenticate as the service principal. - policy = ucacct.service_principal_federation_policy.create( - service_principal_id, - oauth2.FederationPolicy( - oidc_policy=oauth2.OidcFederationPolicy( - issuer="https://token.actions.githubusercontent.com", - audiences=[audience], - subject="repo:databricks-eng/eng-dev-ecosystem:environment:integration-tests", - ) - ), - ) - cleanup.callback( - _ignore_not_found, - lambda: ucacct.service_principal_federation_policy.delete( - service_principal_id, - policy.policy_id or policy.uid, - ), - ) - - # Create a private notebook that distinguishes normal access from role access. - workspace_admin_user = workspace_admin.current_user.me().user_name - notebook_directory = f"/Users/{workspace_admin_user}/.sdk/notebooks/py-sdk-wif-role-{random()}" - notebook_path = f"{notebook_directory}/notebook" - workspace_admin.workspace.mkdirs(notebook_directory) - cleanup.callback( - _ignore_not_found, - lambda: workspace_admin.workspace.delete(notebook_directory, recursive=True), - ) - workspace_admin.workspace.upload( - notebook_path, - b"print(1)", - format=ImportFormat.SOURCE, - language=Language.PYTHON, - overwrite=True, - ) - notebook = workspace_admin.workspace.get_status(notebook_path) - - # Grant only the group role permission to read the notebook. - workspace_admin.permissions.update( - "notebooks", - str(notebook.object_id), - access_control_list=[ - iam.AccessControlRequest( - group_name=group.display_name, - permission_level=iam.PermissionLevel.CAN_READ, - ) - ], - ) - - # Authenticate with the group role and verify that its notebook permission is usable. - role_client = WorkspaceClient( - host=workspace_url, - client_id=service_principal.application_id, - group_id=group.id, - auth_type="github-oidc", - token_audience=audience, - ) - role_client.workspace.get_status(notebook_path) - - # Authenticate normally as the same service principal and verify that access is denied. - normal_client = WorkspaceClient( - host=workspace_url, - client_id=service_principal.application_id, - auth_type="github-oidc", - token_audience=audience, - ) - with pytest.raises((PermissionDenied, NotFound)): - normal_client.workspace.get_status(notebook_path) - - -def test_workspace_config_resolves_account_and_workspace_id(w, env_or_skip): - """Test that Config resolves account_id and workspace_id from host metadata.""" - env_or_skip("CLOUD_ENV") - - config = Config() - - assert config.account_id, "expected account_id to be resolved from host metadata" - assert config.workspace_id, "expected workspace_id to be resolved from host metadata" - - -def test_workspace_oauth_m2m_auth(w, env_or_skip): - env_or_skip("CLOUD_ENV") - - # Get environment variables - host = env_or_skip("DATABRICKS_HOST") - client_id = env_or_skip("TEST_DATABRICKS_CLIENT_ID") - client_secret = env_or_skip("TEST_DATABRICKS_CLIENT_SECRET") - - # Create workspace client with OAuth M2M authentication - ws = WorkspaceClient( - host=host, - client_id=client_id, - client_secret=client_secret, - auth_type="oauth-m2m", - ) - - # Call the "me" API - me = ws.current_user.me() - - # Verify we got a valid response - assert me.user_name, "expected non-empty user_name" - - -def test_workspace_azure_client_secret_auth(w, env_or_skip): - env_or_skip("CLOUD_ENV") - - host = env_or_skip("DATABRICKS_HOST") - azure_client_id = env_or_skip("ARM_CLIENT_ID") - azure_client_secret = env_or_skip("ARM_CLIENT_SECRET") - azure_tenant_id = env_or_skip("ARM_TENANT_ID") - - # Create workspace client with Azure client secret authentication - ws = WorkspaceClient( - host=host, - azure_client_id=azure_client_id, - azure_client_secret=azure_client_secret, - azure_tenant_id=azure_tenant_id, - auth_type="azure-client-secret", - ) - - # Call the "me" API - me = ws.current_user.me() - - # Verify we got a valid response - assert me.user_name, "expected non-empty user_name" - - -def test_account_oauth_m2m_auth(a, env_or_skip): - env_or_skip("CLOUD_ENV") - - # Get environment variables - host = env_or_skip("DATABRICKS_HOST") - account_id = env_or_skip("DATABRICKS_ACCOUNT_ID") - client_id = env_or_skip("TEST_DATABRICKS_CLIENT_ID") - client_secret = env_or_skip("TEST_DATABRICKS_CLIENT_SECRET") - - # Create account client with OAuth M2M authentication - ac = AccountClient( - host=host, - account_id=account_id, - client_id=client_id, - client_secret=client_secret, - auth_type="oauth-m2m", - ) - - # List service principals to verify authentication works - sps = ac.service_principals.list() - next(sps) - - -def test_account_azure_client_secret_auth(a, env_or_skip): - env_or_skip("CLOUD_ENV") - - # Get environment variables - host = env_or_skip("DATABRICKS_HOST") - account_id = env_or_skip("DATABRICKS_ACCOUNT_ID") - azure_client_id = env_or_skip("ARM_CLIENT_ID") - azure_client_secret = env_or_skip("ARM_CLIENT_SECRET") - azure_tenant_id = env_or_skip("ARM_TENANT_ID") - - # Create account client with Azure client secret authentication - ac = AccountClient( - host=host, - account_id=account_id, - azure_client_id=azure_client_id, - azure_client_secret=azure_client_secret, - azure_tenant_id=azure_tenant_id, - auth_type="azure-client-secret", - ) - - # List service principals to verify authentication works - sps = ac.service_principals.list() - next(sps) diff --git a/tests/integration/test_client.py b/tests/integration/test_client.py deleted file mode 100644 index 4e13d6854..000000000 --- a/tests/integration/test_client.py +++ /dev/null @@ -1,19 +0,0 @@ -def test_get_workspace_client(ucacct, env_or_skip): - # Need to switch to ucacct - workspace_id = env_or_skip("TEST_WORKSPACE_ID") - ws = ucacct.workspaces.get(workspace_id) - w = ucacct.get_workspace_client(ws) - assert w.current_user.me().active - - -def test_get_workspace_id(ucws, env_or_skip): - ws_id = int(env_or_skip("THIS_WORKSPACE_ID")) - assert ucws.get_workspace_id() == ws_id - - -def test_creating_ws_client_from_ac_client_does_not_override_config(ucacct, env_or_skip): - ws_id = env_or_skip("TEST_WORKSPACE_ID") - ws = ucacct.workspaces.get(ws_id) - w = ucacct.get_workspace_client(ws) - me = w.current_user.me() - assert me.user_name is not None diff --git a/tests/integration/test_clusters.py b/tests/integration/test_clusters.py deleted file mode 100644 index 506012740..000000000 --- a/tests/integration/test_clusters.py +++ /dev/null @@ -1,44 +0,0 @@ -import logging -from datetime import timedelta - -import pytest - -from databricks.sdk.core import DatabricksError -from databricks.sdk.service.compute import EventType - - -def test_smallest_node_type(w): - node_type_id = w.clusters.select_node_type(local_disk=True) - assert node_type_id is not None - - -def test_latest_runtime(w): - spark_version = w.clusters.select_spark_version(long_term_support=True) - assert spark_version is not None - - -def test_cluster_events(w, env_or_skip): - cluster_id = env_or_skip("TEST_DEFAULT_CLUSTER_ID") - count = 0 - for e in w.clusters.events(cluster_id, event_types=[EventType.STARTING, EventType.TERMINATING]): - count += 1 - assert count > 0 - - -def test_create_cluster(skip_aws_uc_workspace, w, env_or_skip, random): - info = w.clusters.create( - cluster_name=f"databricks-sdk-py-{random(8)}", - spark_version=w.clusters.select_spark_version(long_term_support=True), - instance_pool_id=env_or_skip("TEST_INSTANCE_POOL_ID"), - autotermination_minutes=10, - num_workers=1, - ).result(timeout=timedelta(minutes=20)) - logging.info(f"Created: {info}") - - -def test_error_unmarshall(w, random): - with pytest.raises(DatabricksError) as exc_info: - w.clusters.get("123__non_existing__") - err = exc_info.value - assert "Cluster 123__non_existing__ does not exist" in str(err) - assert "INVALID_PARAMETER_VALUE" == err.error_code diff --git a/tests/integration/test_commands.py b/tests/integration/test_commands.py deleted file mode 100644 index e60302818..000000000 --- a/tests/integration/test_commands.py +++ /dev/null @@ -1,11 +0,0 @@ -import pytest - -from databricks.sdk.core import DatabricksError - - -def test_error_unmarshall(w, random): - with pytest.raises(DatabricksError) as exc_info: - w.command_execution.execute(cluster_id="__non_existing__") - err = exc_info.value - assert "requirement failed: missing contextId" in str(err) - assert err.error_code is None diff --git a/tests/integration/test_data_plane.py b/tests/integration/test_data_plane.py deleted file mode 100644 index 338366667..000000000 --- a/tests/integration/test_data_plane.py +++ /dev/null @@ -1,22 +0,0 @@ -from databricks.sdk.data_plane import DataPlaneTokenSource - - -def test_data_plane_token_source(ucws, env_or_skip): - endpoint = env_or_skip("SERVING_ENDPOINT_NAME") - serving_endpoint = ucws.serving_endpoints.get(endpoint) - assert serving_endpoint.data_plane_info is not None - assert serving_endpoint.data_plane_info.query_info is not None - - info = serving_endpoint.data_plane_info.query_info - - ts = DataPlaneTokenSource(ucws.config.host, ucws._config.oauth_token) - dp_token = ts.token(info.endpoint_url, info.authorization_details) - - assert dp_token.valid - - -def test_model_serving_data_plane(ucws, env_or_skip): - endpoint = env_or_skip("SERVING_ENDPOINT_NAME") - serving_endpoints = ucws.serving_endpoints_data_plane - response = serving_endpoints.query(name=endpoint, dataframe_records=[{"col": 1.0}]) - assert response is not None diff --git a/tests/integration/test_dbconnect.py b/tests/integration/test_dbconnect.py deleted file mode 100644 index ecc82855b..000000000 --- a/tests/integration/test_dbconnect.py +++ /dev/null @@ -1,73 +0,0 @@ -import pytest - -DBCONNECT_DBR_CLIENT = { - "13.3": "13.3.3", - "14.3": "14.3.1", -} - - -def reload_modules(name: str): - """ - Reloads the specified module. This is useful when testing Databricks Connect, since both - the `databricks.connect` and `databricks.sdk.runtime` modules are stateful, and we need - to reload these modules to reset the state cache between test runs. - """ - - import importlib - import sys - - v = sys.modules.get(name) - if v is None: - return - try: - print(f"Reloading {name}") - importlib.reload(v) - except Exception as e: - print(f"Failed to reload {name}: {e}") - - -@pytest.fixture(params=list(DBCONNECT_DBR_CLIENT.keys())) -def setup_dbconnect_test(request, env_or_skip, restorable_env): - dbr = request.param - assert ( - dbr in DBCONNECT_DBR_CLIENT - ), f"Unsupported Databricks Runtime version {dbr}. Please update DBCONNECT_DBR_CLIENT." - - import os - - os.environ["DATABRICKS_CLUSTER_ID"] = env_or_skip(f"TEST_DBR_{dbr.replace('.', '_')}_DBCONNECT_CLUSTER_ID") - - import subprocess - import sys - - lib = f"databricks-connect=={DBCONNECT_DBR_CLIENT[dbr]}" - subprocess.check_call([sys.executable, "-m", "pip", "install", lib]) - - yield - - subprocess.check_call([sys.executable, "-m", "pip", "uninstall", "-y", "databricks-connect"]) - - -@pytest.mark.xdist_group(name="databricks-connect") -def test_dbconnect_initialisation(w, setup_dbconnect_test): - reload_modules("databricks.connect") - from databricks.connect import DatabricksSession - - spark = DatabricksSession.builder.getOrCreate() - assert spark.sql("SELECT 1").collect()[0][0] == 1 - - -@pytest.mark.xdist_group(name="databricks-connect") -def test_dbconnect_runtime_import(w, setup_dbconnect_test): - reload_modules("databricks.sdk.runtime") - from databricks.sdk.runtime import spark - - assert spark.sql("SELECT 1").collect()[0][0] == 1 - - -@pytest.mark.xdist_group(name="databricks-connect") -def test_dbconnect_runtime_import_no_error_if_doesnt_exist(w): - reload_modules("databricks.sdk.runtime") - from databricks.sdk.runtime import spark - - assert spark is None diff --git a/tests/integration/test_dbutils.py b/tests/integration/test_dbutils.py deleted file mode 100644 index 6438771c3..000000000 --- a/tests/integration/test_dbutils.py +++ /dev/null @@ -1,206 +0,0 @@ -import base64 -import logging -import os - -import pytest - -from databricks.sdk.core import DatabricksError -from databricks.sdk.errors import NotFound - - -def test_rest_dbfs_ls(w, env_or_skip): - from databricks.sdk.runtime import dbutils - - x = dbutils.fs.ls("/") - - assert len(x) > 1 - - -@pytest.fixture(params=["dbfs", "volumes"]) -def fs_and_base_path(request, ucws, volume): - if request.param == "dbfs": - fs = ucws.dbutils.fs - base_path = "/tmp" - else: - fs = ucws.dbutils.fs - base_path = volume - return fs, base_path - - -def test_put(fs_and_base_path): - fs, base_path = fs_and_base_path - path = base_path + "/dbc_qa_file" - fs.put(path, "test", True) - output = fs.head(path) - assert output == "test" - - -def test_large_put(fs_and_base_path): - fs, base_path = fs_and_base_path - path = base_path + "/dbc_qa_large_file" - fs.put(path, "test" * 20000, True) - output = fs.head(path) - assert output == ("test" * 20000)[:65536] - - -def test_put_local_path(w, random, tmp_path): - to_write = random(1024 * 1024 * 2) - tmp_path = tmp_path / "tmp_file" - w.dbutils.fs.put(f"file:{tmp_path}", to_write, True) - assert w.dbutils.fs.head(f"file:{tmp_path}", 1024 * 1024 * 2) == to_write - - -def test_cp_file(fs_and_base_path, random): - fs, base_path = fs_and_base_path - path = base_path + "/dbc_qa_file-" + random() - fs.put(path, "test", True) - fs.cp(path, path + "_copy") - output = fs.head(path + "_copy") - assert output == "test" - assert len(fs.ls(path)) == 1 - - -def test_cp_dir(fs_and_base_path, random): - fs, base_path = fs_and_base_path - path = base_path + "/dbc_qa_dir-" + random() - fs.mkdirs(path) - fs.put(path + "/file1", "test1", True) - fs.put(path + "/file2", "test2", True) - fs.cp(path, path + "_copy") - output = fs.ls(path + "_copy") - assert len(output) == 2 - assert output[0].path == path + "_copy/file1" - assert output[1].path == path + "_copy/file2" - - -def test_ls_file(fs_and_base_path, random): - fs, base_path = fs_and_base_path - path = base_path + "/dbc_qa_file-" + random() - fs.put(path, "test", True) - output = fs.ls(path) - assert len(output) == 1 - assert output[0].path == path - - -def test_ls_dir(fs_and_base_path, random): - fs, base_path = fs_and_base_path - path = base_path + "/dbc_qa_dir-" + random() - fs.mkdirs(path) - fs.put(path + "/file1", "test1", True) - fs.put(path + "/file2", "test2", True) - output = fs.ls(path) - assert len(output) == 2 - assert output[0].path == path + "/file1" - assert output[1].path == path + "/file2" - - -def test_mv_file(fs_and_base_path, random): - fs, base_path = fs_and_base_path - path = base_path + "/dbc_qa_file-" + random() - fs.put(path, "test", True) - fs.mv(path, path + "_moved") - output = fs.head(path + "_moved") - assert output == "test" - with pytest.raises(NotFound): - fs.ls(path) - - -def test_mv_dir(fs_and_base_path, random): - fs, base_path = fs_and_base_path - path = base_path + "/dbc_qa_dir-" + random() - fs.mkdirs(path) - fs.put(path + "/file1", "test1", True) - fs.put(path + "/file2", "test2", True) - # DBFS can do recursive mv as a single API call, but Volumes cannot - kw = {"recurse": True} if "/Volumes" in path else {} - fs.mv(path, path + "_moved", **kw) - output = fs.ls(path + "_moved") - assert len(output) == 2 - assert output[0].path == path + "_moved/file1" - assert output[1].path == path + "_moved/file2" - with pytest.raises(NotFound): - fs.ls(path) - - -def test_mv_local_to_remote(fs_and_base_path, random, tmp_path): - fs, base_path = fs_and_base_path - path = base_path + "/dbc_qa_file-" + random() - with open(tmp_path / "test", "w") as f: - f.write("test") - fs.mv("file:" + str(tmp_path / "test"), path) - output = fs.head(path) - assert output == "test" - assert os.listdir(tmp_path) == [] - - -def test_mv_remote_to_local(fs_and_base_path, random, tmp_path): - fs, base_path = fs_and_base_path - path = base_path + "/dbc_qa_file-" + random() - fs.put(path, "test", True) - fs.mv(path, "file:" + str(tmp_path / "test")) - with open(tmp_path / "test", "r") as f: - output = f.read() - assert output == "test" - with pytest.raises(NotFound): - fs.ls(path) - - -def test_rm_file(fs_and_base_path, random): - fs, base_path = fs_and_base_path - path = base_path + "/dbc_qa_file-" + random() - fs.put(path, "test", True) - fs.rm(path) - with pytest.raises(NotFound): - fs.ls(path) - - -def test_rm_dir(fs_and_base_path, random): - fs, base_path = fs_and_base_path - path = base_path + "/dbc_qa_dir-" + random() - fs.mkdirs(path) - fs.put(path + "/file1", "test1", True) - fs.put(path + "/file2", "test2", True) - with pytest.raises(IOError): - fs.rm(path) - fs.rm(path, recurse=True) - with pytest.raises(NotFound): - fs.ls(path) - - -def test_secrets(w, random): - random_scope = f"scope-{random()}" - key_for_string = f"string-{random()}" - key_for_bytes = f"bytes-{random()}" - random_value = f"SECRET-{random()}" - - logger = logging.getLogger("foo") - logger.info(f"Before loading secret: {random_value}") - - w.secrets.create_scope(random_scope) - w.secrets.put_secret(random_scope, key_for_string, string_value=random_value) - w.secrets.put_secret( - random_scope, - key_for_bytes, - bytes_value=base64.b64encode(random_value.encode()).decode(), - ) - - from databricks.sdk.runtime import dbutils - - all_scopes = dbutils.secrets.listScopes() - assert random_scope in [scope.getName() for scope in all_scopes] - - all_secrets = {} - for secret_metadata in dbutils.secrets.list(random_scope): - key = secret_metadata.key - try: - all_secrets[key] = dbutils.secrets.get(random_scope, key) - except DatabricksError as e: - if e.error_code == "BAD_REQUEST": - pytest.skip("dbconnect is not enabled on this workspace") - raise e - - logger.info(f"After loading secret: {random_value}") - logging.getLogger("databricks.sdk").info(f"After loading secret: {random_value}") - - assert all_secrets[key_for_string] == random_value - assert all_secrets[key_for_bytes] == random_value diff --git a/tests/integration/test_deployment.py b/tests/integration/test_deployment.py deleted file mode 100644 index 61f5c2f61..000000000 --- a/tests/integration/test_deployment.py +++ /dev/null @@ -1,14 +0,0 @@ -import logging - -import pytest - -from databricks.sdk.environments import Cloud - -from .conftest import _is_cloud - - -def test_workspaces(a): - if _is_cloud(Cloud.AZURE): - pytest.skip("not available on Azure") - for w in a.workspaces.list(): - logging.info(f"Found workspace: {w.workspace_name}") diff --git a/tests/integration/test_external_browser.py b/tests/integration/test_external_browser.py deleted file mode 100644 index 883069217..000000000 --- a/tests/integration/test_external_browser.py +++ /dev/null @@ -1,46 +0,0 @@ -import pytest - -from databricks.sdk import WorkspaceClient - -from .conftest import _load_debug_env_if_runs_from_ide - - -@pytest.fixture(scope="session") -def env(env_or_skip): - if not _load_debug_env_if_runs_from_ide("workspace"): - pytest.skip("runnable only on dev machines") - return env_or_skip - - -def test_pkce_app(env): - w = WorkspaceClient( - host=env("DATABRICKS_HOST"), - client_id=env("TEST_PKCE_APP_CLIENT_ID"), - auth_type="external-browser", - ) - clusters = w.clusters.list() - for cl in clusters: - print(f" - {cl.cluster_name} is {cl.state}") - - -def test_public_app(env): - w = WorkspaceClient( - host=env("DATABRICKS_HOST"), - client_id=env("TEST_PUBLIC_APP_CLIENT_ID"), - auth_type="external-browser", - ) - clusters = w.clusters.list() - for cl in clusters: - print(f" - {cl.cluster_name} is {cl.state}") - - -def test_private_app(env): - w = WorkspaceClient( - host=env("DATABRICKS_HOST"), - client_id=env("TEST_PRIVATE_APP_CLIENT_ID"), - client_secret=env("TEST_PRIVATE_APP_CLIENT_SECRET"), - auth_type="external-browser", - ) - clusters = w.clusters.list() - for cl in clusters: - print(f" - {cl.cluster_name} is {cl.state}") diff --git a/tests/integration/test_files.py b/tests/integration/test_files.py deleted file mode 100644 index c5a20620c..000000000 --- a/tests/integration/test_files.py +++ /dev/null @@ -1,382 +0,0 @@ -import io -import logging -import pathlib -import platform -import time -from typing import Callable, List, Tuple, Union - -import pytest - -from databricks.sdk.core import DatabricksError -from databricks.sdk.service.catalog import VolumeType - - -def test_local_io(random): - if platform.system() == "Windows": - dummy_file = f"C:\\Windows\\Temp\\{random()}" - else: - dummy_file = f"/tmp/{random()}" - to_write = random(1024 * 1024 * 2.5).encode() - with open(dummy_file, "wb") as f: - written = f.write(to_write) - assert len(to_write) == written - - f = open(dummy_file, "rb") - assert f.read() == to_write - f.close() - - -def test_dbfs_io(w, random): - dummy_file = f"/tmp/{random()}" - to_write = random(1024 * 1024 * 1.5).encode() - with w.dbfs.open(dummy_file, write=True) as f: - written = f.write(to_write) - assert len(to_write) == written - - f = w.dbfs.open(dummy_file, read=True) - from_dbfs = f.read() - assert from_dbfs == to_write - f.close() - - -@pytest.fixture -def junk(w, random): - def inner(path: str, size=256) -> bytes: - to_write = random(size).encode() - with w.dbfs.open(path, write=True) as f: - written = f.write(to_write) - assert len(to_write) == written - return to_write - - return inner - - -@pytest.fixture -def ls(w): - def inner(root: str, recursive=False) -> List[str]: - return [f.path.removeprefix(root) for f in w.dbfs.list(root, recursive=recursive)] - - return inner - - -def test_recursive_listing(w, random, junk, ls): - root = f"/tmp/{random()}" - junk(f"{root}/01") - junk(f"{root}/a/02") - junk(f"{root}/a/b/03") - - assert ["/01", "/a"] == ls(root) - assert ["/01", "/a/02", "/a/b/03"] == ls(root, recursive=True) - - w.dbfs.delete(root, recursive=True) - - -def test_cp_dbfs_folder_to_folder_non_recursive(w, random, junk, ls): - root = f"/tmp/{random()}" - junk(f"{root}/01") - junk(f"{root}/a/02") - junk(f"{root}/a/b/03") - new_root = f"/tmp/{random()}" - - w.dbfs.copy(root, new_root) - - assert ["/01"] == ls(new_root, recursive=True) - - -def test_cp_dbfs_folder_to_folder_recursive(w, random, junk, ls): - root = f"/tmp/{random()}" - junk(f"{root}/01") - junk(f"{root}/a/02") - junk(f"{root}/a/b/03") - new_root = f"/tmp/{random()}" - - w.dbfs.copy(root, new_root, recursive=True, overwrite=True) - - assert ["/01", "/a/02", "/a/b/03"] == ls(new_root, recursive=True) - - -def test_cp_dbfs_folder_to_existing_folder_recursive(w, random, junk, ls): - root = f"/tmp/{random()}" - junk(f"{root}/01") - junk(f"{root}/a/02") - junk(f"{root}/a/b/03") - new_root = f"/tmp/{random()}" - - w.dbfs.mkdirs(new_root) - w.dbfs.copy(root, new_root, recursive=True, overwrite=True) - - base = root.split("/")[-1] - assert [f"/{base}/01", f"/{base}/a/02", f"/{base}/a/b/03"] == ls(new_root, recursive=True) - - -def test_cp_dbfs_file_to_non_existing_location(w, random, junk): - root = f"/tmp/{random()}" - payload = junk(f"{root}/01") - copy_destination = f"{root}/{random()}" - - w.dbfs.copy(f"{root}/01", copy_destination) - - with w.dbfs.open(copy_destination, read=True) as f: - assert f.read() == payload - - -def test_cp_dbfs_file_to_existing_folder(w, random, junk): - root = f"/tmp/{random()}" - payload = junk(f"{root}/01") - w.dbfs.mkdirs(f"{root}/02") - w.dbfs.copy(f"{root}/01", f"{root}/02") - - with w.dbfs.open(f"{root}/02/01", read=True) as f: - assert f.read() == payload - - -def test_cp_dbfs_file_to_existing_location(w, random, junk): - root = f"/tmp/{random()}" - junk(f"{root}/01") - junk(f"{root}/02") - with pytest.raises(DatabricksError) as ei: - w.dbfs.copy(f"{root}/01", f"{root}/02") - assert "A file or directory already exists" in str(ei.value) - - -def test_cp_dbfs_file_to_existing_location_with_overwrite(w, random, junk): - root = f"/tmp/{random()}" - payload = junk(f"{root}/01") - junk(f"{root}/02") - - w.dbfs.copy(f"{root}/01", f"{root}/02", overwrite=True) - - with w.dbfs.open(f"{root}/02", read=True) as f: - assert f.read() == payload - - -def test_move_within_dbfs(w, random, junk): - root = f"/tmp/{random()}" - payload = junk(f"{root}/01") - - w.dbfs.move_(f"{root}/01", f"{root}/02") - - assert w.dbfs.exists(f"{root}/01") is False - with w.dbfs.open(f"{root}/02", read=True) as f: - assert f.read() == payload - - -def test_move_from_dbfs_to_local(w, random, junk, tmp_path): - root = pathlib.Path(f"/tmp/{random()}") - payload_01 = junk(f"{root}/01") - payload_02 = junk(f"{root}/a/02") - payload_03 = junk(f"{root}/a/b/03") - - w.dbfs.move_(root, f"file:{tmp_path}", recursive=True) - - assert w.dbfs.exists(root) is False - with (tmp_path / root.name / "01").open("rb") as f: - assert f.read() == payload_01 - with (tmp_path / root.name / "a/02").open("rb") as f: - assert f.read() == payload_02 - with (tmp_path / root.name / "a/b/03").open("rb") as f: - assert f.read() == payload_03 - - -def test_dbfs_upload_download(w, random, junk, tmp_path): - root = pathlib.Path(f"/tmp/{random()}") - - f = io.BytesIO(b"some text data") - w.dbfs.upload(f"{root}/01", f) - - with w.dbfs.download(f"{root}/01") as f: - assert f.read() == b"some text data" - - -class ResourceWithCleanup: - cleanup: Callable[[], None] - - def __init__(self, cleanup): - self.cleanup = cleanup - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.cleanup() - - @staticmethod - def create_schema(w, catalog, schema): - res = w.schemas.create(catalog_name=catalog, name=schema) - return ResourceWithCleanup(lambda: w.schemas.delete(res.full_name)) - - @staticmethod - def create_volume(w, catalog, schema, volume): - res = w.volumes.create( - catalog_name=catalog, - schema_name=schema, - name=volume, - volume_type=VolumeType.MANAGED, - ) - return ResourceWithCleanup(lambda: w.volumes.delete(res.full_name)) - - -def test_files_api_upload_download(ucws, files_api, random): - w = ucws - schema = "filesit-" + random() - volume = "filesit-" + random() - with ResourceWithCleanup.create_schema(w, "main", schema): - with ResourceWithCleanup.create_volume(w, "main", schema, volume): - f = io.BytesIO(b"some text data") - target_file = f"/Volumes/main/{schema}/{volume}/filesit-with-?-and-#-{random()}.txt" - files_api.upload(target_file, f) - with files_api.download(target_file).contents as f: - assert f.read() == b"some text data" - - -def test_files_api_read_twice_from_one_download(ucws, files_api, random): - w = ucws - schema = "filesit-" + random() - volume = "filesit-" + random() - with ResourceWithCleanup.create_schema(w, "main", schema): - with ResourceWithCleanup.create_volume(w, "main", schema, volume): - f = io.BytesIO(b"some text data") - target_file = f"/Volumes/main/{schema}/{volume}/filesit-{random()}.txt" - files_api.upload(target_file, f) - - res = files_api.download(target_file).contents - - with res: - assert res.read() == b"some text data" - - with pytest.raises(ValueError): - with res: - res.read() - - -def test_files_api_delete_file(ucws, files_api, random): - w = ucws - schema = "filesit-" + random() - volume = "filesit-" + random() - with ResourceWithCleanup.create_schema(w, "main", schema): - with ResourceWithCleanup.create_volume(w, "main", schema, volume): - f = io.BytesIO(b"some text data") - target_file = f"/Volumes/main/{schema}/{volume}/filesit-{random()}.txt" - files_api.upload(target_file, f) - files_api.delete(target_file) - - -def test_files_api_get_metadata(ucws, files_api, random): - w = ucws - schema = "filesit-" + random() - volume = "filesit-" + random() - with ResourceWithCleanup.create_schema(w, "main", schema): - with ResourceWithCleanup.create_volume(w, "main", schema, volume): - f = io.BytesIO(b"some text data") - target_file = f"/Volumes/main/{schema}/{volume}/filesit-{random()}.txt" - files_api.upload(target_file, f) - m = files_api.get_metadata(target_file) - assert m.content_type == "application/octet-stream" - assert m.content_length == 14 - assert m.last_modified is not None - - -def test_files_api_create_directory(ucws, files_api, random): - w = ucws - schema = "filesit-" + random() - volume = "filesit-" + random() - with ResourceWithCleanup.create_schema(w, "main", schema): - with ResourceWithCleanup.create_volume(w, "main", schema, volume): - target_directory = f"/Volumes/main/{schema}/{volume}/filesit-{random()}/" - files_api.create_directory(target_directory) - - -def test_files_api_list_directory_contents(ucws, files_api, random): - w = ucws - schema = "filesit-" + random() - volume = "filesit-" + random() - with ResourceWithCleanup.create_schema(w, "main", schema): - with ResourceWithCleanup.create_volume(w, "main", schema, volume): - target_directory = f"/Volumes/main/{schema}/{volume}/filesit-{random()}" - files_api.upload(target_directory + "/file1.txt", io.BytesIO(b"some text data")) - files_api.upload(target_directory + "/file2.txt", io.BytesIO(b"some text data")) - files_api.upload(target_directory + "/file3.txt", io.BytesIO(b"some text data")) - - result = list(files_api.list_directory_contents(target_directory)) - assert len(result) == 3 - - -def test_files_api_delete_directory(ucws, files_api, random): - w = ucws - schema = "filesit-" + random() - volume = "filesit-" + random() - with ResourceWithCleanup.create_schema(w, "main", schema): - with ResourceWithCleanup.create_volume(w, "main", schema, volume): - target_directory = f"/Volumes/main/{schema}/{volume}/filesit-{random()}/" - files_api.create_directory(target_directory) - files_api.delete_directory(target_directory) - - -def test_files_api_get_directory_metadata(ucws, files_api, random): - w = ucws - schema = "filesit-" + random() - volume = "filesit-" + random() - with ResourceWithCleanup.create_schema(w, "main", schema): - with ResourceWithCleanup.create_volume(w, "main", schema, volume): - target_directory = f"/Volumes/main/{schema}/{volume}/filesit-{random()}/" - files_api.create_directory(target_directory) - files_api.get_directory_metadata(target_directory) - - -@pytest.mark.benchmark -def test_files_api_download_benchmark(ucws, files_api, random): - w = ucws - schema = "filesit-" + random() - volume = "filesit-" + random() - with ResourceWithCleanup.create_schema(w, "main", schema): - with ResourceWithCleanup.create_volume(w, "main", schema, volume): - # Create a 50 MB file - f = io.BytesIO(bytes(range(256)) * 200000) - target_file = f"/Volumes/main/{schema}/{volume}/filesit-benchmark-{random()}.txt" - files_api.upload(target_file, f) - - totals = {} - for chunk_size_kb in [ - 20, - 50, - 100, - 200, - 500, - 1000, - 2000, - 5000, - 10000, - 20000, - 50000, - None, - ]: - chunk_size = chunk_size_kb * 1024 if chunk_size_kb else None - total = 0 - count = 10 - for i in range(count): - start = time.time() - f = files_api.download(target_file).contents - f.set_chunk_size(chunk_size) - with f as vf: - vf.read() - end = time.time() - total += end - start - avg_time = total / count - logging.info( - "[chunk_size=%s] Average time to download: %f seconds", - str(chunk_size_kb) + "kb" if chunk_size_kb else "None", - avg_time, - ) - totals[chunk_size_kb] = avg_time - logging.info("Benchmark results:") - best: Tuple[Union[int, None], Union[float, None]] = (None, None) - for k, v in totals.items(): - if best[1] is None or v < best[1]: - best = (k, v) - logging.info( - "[chunk_size=%s] Average time to download: %f seconds", - str(k) + "kb" if k else "None", - v, - ) - min_str = str(best[0]) + "kb" if best[0] else "None" - logging.info("Fastest chunk size: %s in %f seconds", min_str, best[1]) diff --git a/tests/integration/test_iam.py b/tests/integration/test_iam.py deleted file mode 100644 index 5bec70a68..000000000 --- a/tests/integration/test_iam.py +++ /dev/null @@ -1,70 +0,0 @@ -import pytest - -from databricks.sdk import errors -from databricks.sdk.core import DatabricksError - - -def test_filtering_groups(w, random): - all = w.groups.list(filter=f"displayName eq any-{random(12)}") - found = len(list(all)) - assert found == 0 - - -def test_scim_error_unmarshall(w, random): - with pytest.raises(DatabricksError) as exc_info: - groups = w.groups.list(filter=random(12)) - next(groups) - assert isinstance(exc_info.value, errors.BadRequest) - - -def test_scim_get_user_as_dict(w): - first_user = next(w.users.list()) - user = w.users.get(first_user.id) - # should not throw - user.as_dict() - - -@pytest.mark.parametrize( - "path,call", - [ - ("/api/2.0/preview/scim/v2/Users", lambda w: w.users.list(count=10)), - ("/api/2.0/preview/scim/v2/Groups", lambda w: w.groups.list(count=4)), - ( - "/api/2.0/preview/scim/v2/ServicePrincipals", - lambda w: w.service_principals.list(count=1), - ), - ], -) -def test_workspace_users_list_pagination(w, path, call): - # Use count=1 to avoid serializing the full result set just to read totalResults. - raw = w.api_client.do("GET", path, query={"count": 1}) - total = raw["totalResults"] - all = call(w) - found = len(list(all)) - assert found == total - - -@pytest.mark.parametrize( - "path,call", - [ - ( - "/api/2.0/accounts/%s/scim/v2/Users", - lambda a: a.users.list(count=3000), - ), - ( - "/api/2.0/accounts/%s/scim/v2/Groups", - lambda a: a.groups.list(count=5), - ), - ( - "/api/2.0/accounts/%s/scim/v2/ServicePrincipals", - lambda a: a.service_principals.list(count=1000), - ), - ], -) -def test_account_users_list_pagination(a, path, call): - # Use count=1 to avoid serializing the full result set just to read totalResults. - raw = a.api_client.do("GET", path.replace("%s", a.config.account_id), query={"count": 1}) - total = raw["totalResults"] - all = call(a) - found = len(list(all)) - assert found == total diff --git a/tests/integration/test_jobs.py b/tests/integration/test_jobs.py deleted file mode 100644 index dd8e5801f..000000000 --- a/tests/integration/test_jobs.py +++ /dev/null @@ -1,99 +0,0 @@ -import datetime -import logging - - -def test_jobs(w): - found = 0 - for job in w.jobs.list(): - logging.info(f"Looking at {job.settings.name}") - found += 1 - assert found > 0 - - -def test_submitting_jobs(skip_aws_uc_workspace, w, random, env_or_skip): - from databricks.sdk.service import compute, jobs - - py_on_dbfs = f"/home/{w.current_user.me().user_name}/sample.py" - with w.dbfs.open(py_on_dbfs, write=True, overwrite=True) as f: - f.write(b'import time; time.sleep(10); print("Hello, World!")') - - waiter = w.jobs.submit( - run_name=f"py-sdk-{random(8)}", - tasks=[ - jobs.SubmitTask( - task_key="pi", - new_cluster=compute.ClusterSpec( - spark_version=w.clusters.select_spark_version(long_term_support=True), - # node_type_id=w.clusters.select_node_type(local_disk=True), - instance_pool_id=env_or_skip("TEST_INSTANCE_POOL_ID"), - num_workers=1, - ), - spark_python_task=jobs.SparkPythonTask(python_file=f"dbfs:{py_on_dbfs}"), - ) - ], - ) - - logging.info(f"starting to poll: {waiter.run_id}") - - def print_status(run: jobs.Run): - statuses = [f"{t.task_key}: {t.state.life_cycle_state}" for t in run.tasks] - logging.info(f"workflow intermediate status: {', '.join(statuses)}") - - run = waiter.result(timeout=datetime.timedelta(minutes=15), callback=print_status) - - logging.info(f"job finished: {run.run_page_url}") - - -def test_last_job_runs(w): - from collections import defaultdict - from datetime import datetime, timezone - - latest_state = {} - all_jobs = {} - durations = defaultdict(list) - - for job in w.jobs.list(): - all_jobs[job.job_id] = job - for run in w.jobs.list_runs(job_id=job.job_id, expand_tasks=False): - durations[job.job_id].append(run.run_duration) - if job.job_id not in latest_state: - latest_state[job.job_id] = run - continue - if run.end_time < latest_state[job.job_id].end_time: - continue - latest_state[job.job_id] = run - - summary = [] - for job_id, run in latest_state.items(): - summary.append( - { - "job_name": all_jobs[job_id].settings.name, - "last_status": run.state.result_state, - "last_finished": datetime.fromtimestamp(run.end_time / 1000, timezone.utc), - "average_duration": sum(durations[job_id]) / len(durations[job_id]), - } - ) - - for line in sorted(summary, key=lambda s: s["last_finished"], reverse=True): - logging.info(f"Latest: {line}") - - -def test_create_job(w): - from databricks.sdk.service import compute, jobs - - cluster = jobs.JobCluster( - job_cluster_key="cluster1", - new_cluster=compute.ClusterSpec( - num_workers=2, - spark_version=w.clusters.select_spark_version(), - node_type_id=w.clusters.select_node_type(local_disk=True), - ), - ) - - task1 = jobs.Task( - task_key="task1", - job_cluster_key="cluster1", - python_wheel_task=jobs.PythonWheelTask(entry_point="test", package_name="deepspeed"), - ) - - w.jobs.create(job_clusters=[cluster], tasks=[task1]) diff --git a/tests/integration/test_repos.py b/tests/integration/test_repos.py deleted file mode 100644 index 1682d9b87..000000000 --- a/tests/integration/test_repos.py +++ /dev/null @@ -1,6 +0,0 @@ -import logging - - -def test_repos_list(w): - for repo in w.repos.list(): - logging.info(f"Found repo: {repo}") diff --git a/tests/integration/test_settings.py b/tests/integration/test_settings.py deleted file mode 100644 index a14466815..000000000 --- a/tests/integration/test_settings.py +++ /dev/null @@ -1,7 +0,0 @@ -def test_workspace_conf(w): - w.workspace_conf.set_status({"enableResultsDownloading": "false"}) - conf = w.workspace_conf.get_status(keys="enableResultsDownloading") - assert conf["enableResultsDownloading"] == "false" - w.workspace_conf.set_status({"enableResultsDownloading": "true"}) - conf = w.workspace_conf.get_status(keys="enableResultsDownloading") - assert conf["enableResultsDownloading"] == "true" diff --git a/tests/integration/test_sql.py b/tests/integration/test_sql.py deleted file mode 100644 index 5e583bdba..000000000 --- a/tests/integration/test_sql.py +++ /dev/null @@ -1,18 +0,0 @@ -from datetime import datetime - -from databricks.sdk.service.sql import QueryFilter, TimeRange - - -def test_query_history_list_with_filter(w): - def date_to_ms(date): - return int(datetime.strptime(date, "%Y-%m-%d").timestamp() * 1000) - - filter = QueryFilter( - query_start_time_range=TimeRange( - start_time_ms=date_to_ms("2023-01-01"), - end_time_ms=date_to_ms("2023-01-02"), - ) - ) - queries = w.query_history.list(filter_by=filter) - for q in queries.res: - print(q) diff --git a/tests/integration/test_unified_profile.py b/tests/integration/test_unified_profile.py deleted file mode 100644 index 818be1b8c..000000000 --- a/tests/integration/test_unified_profile.py +++ /dev/null @@ -1,248 +0,0 @@ -from urllib.parse import urlparse - -import pytest -from requests.adapters import HTTPAdapter -from requests.structures import CaseInsensitiveDict - -from databricks.sdk import AccountClient, WorkspaceClient -from databricks.sdk.environments import Cloud - -from .conftest import _is_cloud - - -class _CaptureHeadersAdapter(HTTPAdapter): - """Snapshots request and response headers for a single matching URL path. - - Used to verify wire-level header behavior in integration tests; the request - is otherwise let through to the real transport unchanged. Headers are kept - as CaseInsensitiveDict so assertions don't depend on wire casing (HTTP/2 - servers typically lowercase header names). - """ - - def __init__(self, path, *args, **kwargs): - super().__init__(*args, **kwargs) - self.path = path - self.req_headers = None - self.resp_headers = None - - def send(self, request, **kwargs): - response = super().send(request, **kwargs) - if urlparse(request.url).path == self.path: - self.req_headers = CaseInsensitiveDict(request.headers) - self.resp_headers = CaseInsensitiveDict(response.headers) - return response - - -def test_workspace_operations(unified_config): - # GCP google-credentials auth produces a workspace-local SP that is not - # federated at the account level. Workspace APIs via the unified host - # return 401 for this SP. See test_spog_workspace_google_credentials. - if _is_cloud(Cloud.GCP): - pytest.skip("google-credentials workspace ops not supported on unified hosts (workspace-local SP)") - client = WorkspaceClient(config=unified_config) - user = client.current_user.me() - assert user is not None - - -def test_account_operations(unified_config): - client = AccountClient(config=unified_config) - groups = client.groups.list() - assert groups is not None - - -# SPOG/W — Workspace operations on unified host with explicit auth - - -# Environment: azure-prod-pat -def test_spog_workspace_pat(isolated_env): - env = isolated_env("workspace") - host = env("UNIFIED_HOST") - workspace_id = env("TEST_WORKSPACE_ID") - account_id = env("TEST_ACCOUNT_ID") - token = env("DATABRICKS_TOKEN") - ws = WorkspaceClient( - host=host, - workspace_id=workspace_id, - account_id=account_id, - token=token, - ) - me = ws.current_user.me() - assert me.user_name - - -# Environment: azure-prod-ucws -def test_spog_workspace_oauth_m2m(isolated_env): - env = isolated_env("ucws") - host = env("UNIFIED_HOST") - client_id = env("TEST_DATABRICKS_CLIENT_ID") - client_secret = env("TEST_DATABRICKS_CLIENT_SECRET") - workspace_id = env("THIS_WORKSPACE_ID") - account_id = env("TEST_ACCOUNT_ID") - ws = WorkspaceClient( - host=host, - client_id=client_id, - client_secret=client_secret, - workspace_id=workspace_id, - account_id=account_id, - auth_type="oauth-m2m", - ) - me = ws.current_user.me() - assert me.user_name - - -# Environment: azure-prod-ucws -def test_spog_workspace_azure_client_secret(isolated_env): - env = isolated_env("ucws") - host = env("UNIFIED_HOST") - workspace_id = env("THIS_WORKSPACE_ID") - account_id = env("TEST_ACCOUNT_ID") - azure_client_id = env("ARM_CLIENT_ID") - azure_client_secret = env("ARM_CLIENT_SECRET") - azure_tenant_id = env("ARM_TENANT_ID") - ws = WorkspaceClient( - host=host, - workspace_id=workspace_id, - account_id=account_id, - azure_client_id=azure_client_id, - azure_client_secret=azure_client_secret, - azure_tenant_id=azure_tenant_id, - auth_type="azure-client-secret", - ) - me = ws.current_user.me() - assert me.user_name - - -# Environment: gcp-prod-ucacct -def test_spog_workspace_google_credentials(isolated_env): - env = isolated_env("ucacct") - host = env("UNIFIED_HOST") - account_id = env("DATABRICKS_ACCOUNT_ID") - google_credentials = env("GOOGLE_CREDENTIALS") - google_service_account = env("DATABRICKS_GOOGLE_SERVICE_ACCOUNT") - workspace_id = env("TEST_WORKSPACE_ID") - - ws = WorkspaceClient( - host=host, - workspace_id=workspace_id, - account_id=account_id, - google_credentials=google_credentials, - google_service_account=google_service_account, - auth_type="google-credentials", - ) - me = ws.current_user.me() - assert me.user_name - - -# SPOG/A — Account operations on unified host with explicit auth - - -# Environment: azure-prod-acct -def test_spog_account_oauth_m2m(isolated_env): - env = isolated_env("ucacct") - host = env("UNIFIED_HOST") - account_id = env("DATABRICKS_ACCOUNT_ID") - client_id = env("TEST_DATABRICKS_CLIENT_ID") - client_secret = env("TEST_DATABRICKS_CLIENT_SECRET") - ac = AccountClient( - host=host, - account_id=account_id, - client_id=client_id, - client_secret=client_secret, - auth_type="oauth-m2m", - ) - sps = ac.service_principals.list() - next(sps) - - -# Environment: azure-prod-acct -def test_spog_account_azure_client_secret(isolated_env): - env = isolated_env("ucacct") - host = env("UNIFIED_HOST") - account_id = env("DATABRICKS_ACCOUNT_ID") - azure_client_id = env("ARM_CLIENT_ID") - azure_client_secret = env("ARM_CLIENT_SECRET") - azure_tenant_id = env("ARM_TENANT_ID") - ac = AccountClient( - host=host, - account_id=account_id, - azure_client_id=azure_client_id, - azure_client_secret=azure_client_secret, - azure_tenant_id=azure_tenant_id, - auth_type="azure-client-secret", - ) - sps = ac.service_principals.list() - next(sps) - - -# Environment: gcp-prod-ucacct -def test_spog_account_google_credentials(isolated_env): - env = isolated_env("ucacct") - host = env("UNIFIED_HOST") - account_id = env("DATABRICKS_ACCOUNT_ID") - google_credentials = env("GOOGLE_CREDENTIALS") - google_service_account = env("DATABRICKS_GOOGLE_SERVICE_ACCOUNT") - ac = AccountClient( - host=host, - account_id=account_id, - google_credentials=google_credentials, - google_service_account=google_service_account, - auth_type="google-credentials", - ) - sps = ac.service_principals.list() - next(sps) - - -# Environment: azure-prod-ucws -def test_spog_unified_host_sends_workspace_id_header(isolated_env): - """Verify wire-level header behavior of the workspace-id migration on a real unified host. - - Mirrors the Go SDK's TestAccUnifiedHostSendsWorkspaceIdHeader in - internal/unified_host_test.go. Probes the SCIM Me endpoint and inspects the - actual headers that cross the wire: - 1. SDK sends the new X-Databricks-Workspace-Id routing header on - workspace-scoped requests - 2. SDK no longer sends the legacy X-Databricks-Org-Id request header - 3. Server still echoes the legacy X-Databricks-Org-Id on the response, - since the server-side migration has not yet happened (this is why - WorkspaceClient.get_workspace_id() still reads the legacy name). - """ - env = isolated_env("ucws") - host = env("UNIFIED_HOST") - client_id = env("TEST_DATABRICKS_CLIENT_ID") - client_secret = env("TEST_DATABRICKS_CLIENT_SECRET") - workspace_id = env("THIS_WORKSPACE_ID") - account_id = env("TEST_ACCOUNT_ID") - - me_path = "/api/2.0/preview/scim/v2/Me" - - ws = WorkspaceClient( - host=host, - client_id=client_id, - client_secret=client_secret, - workspace_id=workspace_id, - account_id=account_id, - auth_type="oauth-m2m", - ) - - # Mount a capture adapter on the SDK's session for wire-level header inspection. - transport = _CaptureHeadersAdapter(me_path) - ws._api_client._api_client._session.mount("https://", transport) - - # (1) Workspace-scoped probe against the unified host must succeed. - me = ws.current_user.me() - assert me.user_name, "Me should return a non-empty user_name" - - assert transport.req_headers is not None, f"transport did not observe a request to {me_path}" - - # (2) Request side: SDK must send the new header and not the legacy one. - assert ( - transport.req_headers.get("X-Databricks-Workspace-Id") == workspace_id - ), "SDK must send X-Databricks-Workspace-Id with Config.workspace_id" - assert ( - "X-Databricks-Org-Id" not in transport.req_headers - ), "SDK must no longer send the legacy X-Databricks-Org-Id request header" - - # (3) Response side: server still echoes the legacy header name during the migration. - assert ( - "X-Databricks-Org-Id" in transport.resp_headers - ), "server is expected to still echo the legacy X-Databricks-Org-Id response header during the migration" diff --git a/tests/integration/test_workspace.py b/tests/integration/test_workspace.py deleted file mode 100644 index a3b4fd9c5..000000000 --- a/tests/integration/test_workspace.py +++ /dev/null @@ -1,98 +0,0 @@ -import io - -from databricks.sdk.service.workspace import ImportFormat, Language - - -def test_workspace_recursive_list(w, workspace_dir, random): - # create a file in the directory - file = f"{workspace_dir}/file-{random(12)}.py" - w.workspace.upload(file, io.BytesIO(b"print(1)")) - # create a subdirectory - subdirectory = f"{workspace_dir}/subdir-{random(12)}" - w.workspace.mkdirs(subdirectory) - # create a file in the subdirectory - subfile = f"{subdirectory}/subfile-{random(12)}.py" - w.workspace.upload(subfile, io.BytesIO(b"print(2)")) - # list the directory recursively - names = [] - for i in w.workspace.list(workspace_dir, recursive=True): - names.append(i.path) - assert len(names) == 2 - - -def test_workspace_upload_download_notebooks(w, random): - notebook = f"/Users/{w.current_user.me().user_name}/notebook-{random(12)}.py" - - w.workspace.upload(notebook, io.BytesIO(b"print(1)")) - with w.workspace.download(notebook) as f: - content = f.read() - assert content == b"# Databricks notebook source\nprint(1)" - - w.workspace.delete(notebook) - - -def test_workspace_unzip_notebooks(w, random): - notebook = f"/Users/{w.current_user.me().user_name}/notebook-{random(12)}.py" - - # Big notebooks can be gzipped during transfer by the API (out of our control) - # Creating some large content to trigger this behaviour - notebook_content = ("print(1)\n" * 1000).strip("\n") - - w.workspace.upload(notebook, io.BytesIO(bytes(notebook_content, "utf-8"))) - with w.workspace.download(notebook) as f: - content = f.read() - expected_content = bytes(f"# Databricks notebook source\n{notebook_content}", "utf-8") - assert content == expected_content - - w.workspace.delete(notebook) - - -def test_workspace_download_connection_closed(w, random): - notebook = f"/Users/{w.current_user.me().user_name}/notebook-{random(12)}.py" - - w.workspace.upload(notebook, io.BytesIO(b"print(1)")) - - for n in range(30): - with w.workspace.download(notebook) as f: - content = f.read() - assert content == b"# Databricks notebook source\nprint(1)" - - w.workspace.delete(notebook) - - -def test_workspace_upload_download_files(w, random): - py_file = f"/Users/{w.current_user.me().user_name}/file-{random(12)}.py" - - w.workspace.upload(py_file, io.BytesIO(b"print(1)"), format=ImportFormat.AUTO) - with w.workspace.download(py_file) as f: - content = f.read() - assert content == b"print(1)" - - w.workspace.delete(py_file) - - -def test_workspace_upload_download_txt_files(w, random): - txt_file = f"/Users/{w.current_user.me().user_name}/txt-{random(12)}.txt" - - w.workspace.upload(txt_file, io.BytesIO(b"print(1)"), format=ImportFormat.AUTO) - with w.workspace.download(txt_file) as f: - content = f.read() - assert content == b"print(1)" - - w.workspace.delete(txt_file) - - -def test_workspace_upload_download_notebooks_no_extension(w, random): - nb = f"/Users/{w.current_user.me().user_name}/notebook-{random(12)}" - - w.workspace.upload( - nb, - io.BytesIO(b"print(1)"), - format=ImportFormat.SOURCE, - language=Language.PYTHON, - ) - with w.workspace.download(nb) as f: - content = f.read() - assert content == b"# Databricks notebook source\nprint(1)" - - w.workspace.delete(nb) diff --git a/tests/test_dbutils.py b/tests/test_dbutils.py index 9792d8de5..8da294cce 100644 --- a/tests/test_dbutils.py +++ b/tests/test_dbutils.py @@ -286,10 +286,8 @@ def test_jobs_task_values_get_throws(dbutils): assert str(e) == "Must pass debugValue when calling get outside of a job context. debugValue cannot be None." -def test_dbutils_proxy_overrides(dbutils, mocker, restorable_env): - import os - - os.environ["DATABRICKS_SOURCE_FILE"] = "test_source_file" +def test_dbutils_proxy_overrides(dbutils, mocker, monkeypatch): + monkeypatch.setenv("DATABRICKS_SOURCE_FILE", "test_source_file") mocker.patch( "databricks.sdk.dbutils.RemoteDbUtils._cluster_id", return_value="test_cluster_id", diff --git a/uv.lock b/uv.lock old mode 100755 new mode 100644 index 642917e79..ae529dfd5 --- a/uv.lock +++ b/uv.lock @@ -446,56 +446,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bc/58/6b3d24e6b9bc474a2dcdee65dfd1f008867015408a271562e4b690561a4d/cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7", size = 3407605, upload-time = "2026-02-10T19:18:29.233Z" }, ] -[[package]] -name = "databricks-connect" -version = "16.1.7" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -resolution-markers = [ - "python_full_version == '3.11.*'", - "python_full_version < '3.11'", -] -dependencies = [ - { name = "databricks-sdk", marker = "python_full_version < '3.12'" }, - { name = "googleapis-common-protos", marker = "python_full_version < '3.12'" }, - { name = "grpcio", marker = "python_full_version < '3.12'" }, - { name = "grpcio-status", marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" }, marker = "python_full_version < '3.12'" }, - { name = "packaging", marker = "python_full_version < '3.12'" }, - { name = "pandas", marker = "python_full_version < '3.12'" }, - { name = "py4j", version = "0.10.9.7", source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" }, marker = "python_full_version < '3.12'" }, - { name = "pyarrow", marker = "python_full_version < '3.12'" }, - { name = "setuptools", marker = "python_full_version < '3.12'" }, - { name = "six", marker = "python_full_version < '3.12'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/5d/ae8c59de242ca6ef1631e66fdf7fc413bc0d51d2521c4e1cb90104dd62d9/databricks_connect-16.1.7-py2.py3-none-any.whl", hash = "sha256:ddc7ddb8bcfb7910db67e8c4ef5ab2f938c55cf1a1b2e066ff56d2db68b13cb5", size = 2393078, upload-time = "2025-10-28T14:25:02.265Z" }, -] - -[[package]] -name = "databricks-connect" -version = "18.1.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", -] -dependencies = [ - { name = "databricks-sdk", marker = "python_full_version >= '3.12'" }, - { name = "googleapis-common-protos", marker = "python_full_version >= '3.12'" }, - { name = "grpcio", marker = "python_full_version >= '3.12'" }, - { name = "grpcio-status", marker = "python_full_version >= '3.12'" }, - { name = "numpy", version = "2.4.3", source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "packaging", marker = "python_full_version >= '3.12'" }, - { name = "pandas", marker = "python_full_version >= '3.12'" }, - { name = "py4j", version = "0.10.9.9", source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "pyarrow", marker = "python_full_version >= '3.12'" }, - { name = "setuptools", marker = "python_full_version >= '3.12'" }, - { name = "six", marker = "python_full_version >= '3.12'" }, - { name = "zstandard", marker = "python_full_version >= '3.12'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/f8/f3b5033d5fc3db90dcfcf06c4d0fa888b7b1c8444dfd6504c8ed040c8618/databricks_connect-18.1.1-py2.py3-none-any.whl", hash = "sha256:3bbade4e200b7cd0877f526ce20cb4cc315ae96b498cf069afb122c6c45a8565", size = 2841550, upload-time = "2026-03-03T09:39:12.949Z" }, -] - [[package]] name = "databricks-sdk" source = { editable = "." } @@ -508,10 +458,7 @@ dependencies = [ [package.optional-dependencies] dev = [ - { name = "build" }, { name = "check-manifest" }, - { name = "databricks-connect", version = "16.1.7", source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" }, marker = "python_full_version < '3.12'" }, - { name = "databricks-connect", version = "18.1.1", source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" }, marker = "python_full_version >= '3.12'" }, { name = "httpx" }, { name = "ipython", version = "8.38.0", source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" }, marker = "python_full_version < '3.11'" }, { name = "ipython", version = "9.10.0", source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" }, marker = "python_full_version == '3.11.*'" }, @@ -524,7 +471,6 @@ dev = [ { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-mock" }, - { name = "pytest-rerunfailures" }, { name = "pytest-xdist" }, { name = "requests-mock" }, { name = "ruff" }, @@ -544,9 +490,7 @@ openai = [ [package.metadata] requires-dist = [ - { name = "build", marker = "extra == 'dev'" }, { name = "check-manifest", marker = "extra == 'dev'" }, - { name = "databricks-connect", marker = "extra == 'dev'" }, { name = "google-auth", specifier = "~=2.0" }, { name = "httpx", marker = "extra == 'dev'" }, { name = "httpx", marker = "extra == 'openai'" }, @@ -564,7 +508,6 @@ requires-dist = [ { name = "pytest", marker = "extra == 'dev'" }, { name = "pytest-cov", marker = "extra == 'dev'" }, { name = "pytest-mock", marker = "extra == 'dev'" }, - { name = "pytest-rerunfailures", marker = "extra == 'dev'" }, { name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.6.1,<4.0" }, { name = "requests", specifier = ">=2.28.1,<3" }, { name = "requests-mock", marker = "extra == 'dev'" }, @@ -636,93 +579,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/37/45/de64b823b639103de4b63dd193480dce99526bd36be6530c2dba85bf7817/google_auth-2.49.0-py3-none-any.whl", hash = "sha256:f893ef7307f19cf53700b7e2f61b5a6affe3aa0edf9943b13788920ab92d8d87", size = 240676, upload-time = "2026-03-06T21:52:38.304Z" }, ] -[[package]] -name = "googleapis-common-protos" -version = "1.73.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -dependencies = [ - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/99/96/a0205167fa0154f4a542fd6925bdc63d039d88dab3588b875078107e6f06/googleapis_common_protos-1.73.0.tar.gz", hash = "sha256:778d07cd4fbeff84c6f7c72102f0daf98fa2bfd3fa8bea426edc545588da0b5a", size = 147323, upload-time = "2026-03-06T21:53:09.727Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/28/23eea8acd65972bbfe295ce3666b28ac510dfcb115fac089d3edb0feb00a/googleapis_common_protos-1.73.0-py3-none-any.whl", hash = "sha256:dfdaaa2e860f242046be561e6d6cb5c5f1541ae02cfbcb034371aadb2942b4e8", size = 297578, upload-time = "2026-03-06T21:52:33.933Z" }, -] - -[[package]] -name = "grpcio" -version = "1.78.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/a8/690a085b4d1fe066130de97a87de32c45062cf2ecd218df9675add895550/grpcio-1.78.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:7cc47943d524ee0096f973e1081cb8f4f17a4615f2116882a5f1416e4cfe92b5", size = 5946986, upload-time = "2026-02-06T09:54:34.043Z" }, - { url = "https://files.pythonhosted.org/packages/c7/1b/e5213c5c0ced9d2d92778d30529ad5bb2dcfb6c48c4e2d01b1f302d33d64/grpcio-1.78.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c3f293fdc675ccba4db5a561048cca627b5e7bd1c8a6973ffedabe7d116e22e2", size = 11816533, upload-time = "2026-02-06T09:54:37.04Z" }, - { url = "https://files.pythonhosted.org/packages/18/37/1ba32dccf0a324cc5ace744c44331e300b000a924bf14840f948c559ede7/grpcio-1.78.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10a9a644b5dd5aec3b82b5b0b90d41c0fa94c85ef42cb42cf78a23291ddb5e7d", size = 6519964, upload-time = "2026-02-06T09:54:40.268Z" }, - { url = "https://files.pythonhosted.org/packages/ed/f5/c0e178721b818072f2e8b6fde13faaba942406c634009caf065121ce246b/grpcio-1.78.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4c5533d03a6cbd7f56acfc9cfb44ea64f63d29091e40e44010d34178d392d7eb", size = 7198058, upload-time = "2026-02-06T09:54:42.389Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b2/40d43c91ae9cd667edc960135f9f08e58faa1576dc95af29f66ec912985f/grpcio-1.78.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff870aebe9a93a85283837801d35cd5f8814fe2ad01e606861a7fb47c762a2b7", size = 6727212, upload-time = "2026-02-06T09:54:44.91Z" }, - { url = "https://files.pythonhosted.org/packages/ed/88/9da42eed498f0efcfcd9156e48ae63c0cde3bea398a16c99fb5198c885b6/grpcio-1.78.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:391e93548644e6b2726f1bb84ed60048d4bcc424ce5e4af0843d28ca0b754fec", size = 7300845, upload-time = "2026-02-06T09:54:47.562Z" }, - { url = "https://files.pythonhosted.org/packages/23/3f/1c66b7b1b19a8828890e37868411a6e6925df5a9030bfa87ab318f34095d/grpcio-1.78.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:df2c8f3141f7cbd112a6ebbd760290b5849cda01884554f7c67acc14e7b1758a", size = 8284605, upload-time = "2026-02-06T09:54:50.475Z" }, - { url = "https://files.pythonhosted.org/packages/94/c4/ca1bd87394f7b033e88525384b4d1e269e8424ab441ea2fba1a0c5b50986/grpcio-1.78.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd8cb8026e5f5b50498a3c4f196f57f9db344dad829ffae16b82e4fdbaea2813", size = 7726672, upload-time = "2026-02-06T09:54:53.11Z" }, - { url = "https://files.pythonhosted.org/packages/41/09/f16e487d4cc65ccaf670f6ebdd1a17566b965c74fc3d93999d3b2821e052/grpcio-1.78.0-cp310-cp310-win32.whl", hash = "sha256:f8dff3d9777e5d2703a962ee5c286c239bf0ba173877cc68dc02c17d042e29de", size = 4076715, upload-time = "2026-02-06T09:54:55.549Z" }, - { url = "https://files.pythonhosted.org/packages/2a/32/4ce60d94e242725fd3bcc5673c04502c82a8e87b21ea411a63992dc39f8f/grpcio-1.78.0-cp310-cp310-win_amd64.whl", hash = "sha256:94f95cf5d532d0e717eed4fc1810e8e6eded04621342ec54c89a7c2f14b581bf", size = 4799157, upload-time = "2026-02-06T09:54:59.838Z" }, - { url = "https://files.pythonhosted.org/packages/86/c7/d0b780a29b0837bf4ca9580904dfb275c1fc321ded7897d620af7047ec57/grpcio-1.78.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2777b783f6c13b92bd7b716667452c329eefd646bfb3f2e9dabea2e05dbd34f6", size = 5951525, upload-time = "2026-02-06T09:55:01.989Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b1/96920bf2ee61df85a9503cb6f733fe711c0ff321a5a697d791b075673281/grpcio-1.78.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:9dca934f24c732750389ce49d638069c3892ad065df86cb465b3fa3012b70c9e", size = 11830418, upload-time = "2026-02-06T09:55:04.462Z" }, - { url = "https://files.pythonhosted.org/packages/83/0c/7c1528f098aeb75a97de2bae18c530f56959fb7ad6c882db45d9884d6edc/grpcio-1.78.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:459ab414b35f4496138d0ecd735fed26f1318af5e52cb1efbc82a09f0d5aa911", size = 6524477, upload-time = "2026-02-06T09:55:07.111Z" }, - { url = "https://files.pythonhosted.org/packages/8d/52/e7c1f3688f949058e19a011c4e0dec973da3d0ae5e033909677f967ae1f4/grpcio-1.78.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:082653eecbdf290e6e3e2c276ab2c54b9e7c299e07f4221872380312d8cf395e", size = 7198266, upload-time = "2026-02-06T09:55:10.016Z" }, - { url = "https://files.pythonhosted.org/packages/e5/61/8ac32517c1e856677282c34f2e7812d6c328fa02b8f4067ab80e77fdc9c9/grpcio-1.78.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85f93781028ec63f383f6bc90db785a016319c561cc11151fbb7b34e0d012303", size = 6730552, upload-time = "2026-02-06T09:55:12.207Z" }, - { url = "https://files.pythonhosted.org/packages/bd/98/b8ee0158199250220734f620b12e4a345955ac7329cfd908d0bf0fda77f0/grpcio-1.78.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f12857d24d98441af6a1d5c87442d624411db486f7ba12550b07788f74b67b04", size = 7304296, upload-time = "2026-02-06T09:55:15.044Z" }, - { url = "https://files.pythonhosted.org/packages/bd/0f/7b72762e0d8840b58032a56fdbd02b78fc645b9fa993d71abf04edbc54f4/grpcio-1.78.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5397fff416b79e4b284959642a4e95ac4b0f1ece82c9993658e0e477d40551ec", size = 8288298, upload-time = "2026-02-06T09:55:17.276Z" }, - { url = "https://files.pythonhosted.org/packages/24/ae/ae4ce56bc5bb5caa3a486d60f5f6083ac3469228faa734362487176c15c5/grpcio-1.78.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fbe6e89c7ffb48518384068321621b2a69cab509f58e40e4399fdd378fa6d074", size = 7730953, upload-time = "2026-02-06T09:55:19.545Z" }, - { url = "https://files.pythonhosted.org/packages/b5/6e/8052e3a28eb6a820c372b2eb4b5e32d195c661e137d3eca94d534a4cfd8a/grpcio-1.78.0-cp311-cp311-win32.whl", hash = "sha256:6092beabe1966a3229f599d7088b38dfc8ffa1608b5b5cdda31e591e6500f856", size = 4076503, upload-time = "2026-02-06T09:55:21.521Z" }, - { url = "https://files.pythonhosted.org/packages/08/62/f22c98c5265dfad327251fa2f840b591b1df5f5e15d88b19c18c86965b27/grpcio-1.78.0-cp311-cp311-win_amd64.whl", hash = "sha256:1afa62af6e23f88629f2b29ec9e52ec7c65a7176c1e0a83292b93c76ca882558", size = 4799767, upload-time = "2026-02-06T09:55:24.107Z" }, - { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, - { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, - { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, - { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, - { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, - { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, - { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, - { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, - { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" }, - { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" }, - { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" }, - { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" }, - { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" }, - { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" }, - { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, - { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, - { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, - { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, - { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, - { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, - { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, - { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, - { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, - { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, - { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, -] - -[[package]] -name = "grpcio-status" -version = "1.78.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -dependencies = [ - { name = "googleapis-common-protos" }, - { name = "grpcio" }, - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8a/cd/89ce482a931b543b92cdd9b2888805518c4620e0094409acb8c81dd4610a/grpcio_status-1.78.0.tar.gz", hash = "sha256:a34cfd28101bfea84b5aa0f936b4b423019e9213882907166af6b3bddc59e189", size = 13808, upload-time = "2026-02-06T10:01:48.034Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/8a/1241ec22c41028bddd4a052ae9369267b4475265ad0ce7140974548dc3fa/grpcio_status-1.78.0-py3-none-any.whl", hash = "sha256:b492b693d4bf27b47a6c32590701724f1d3b9444b36491878fb71f6208857f34", size = 14523, upload-time = "2026-02-06T10:01:32.584Z" }, -] - [[package]] name = "h11" version = "0.16.0" @@ -1098,124 +954,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, ] -[[package]] -name = "numpy" -version = "1.26.4" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -resolution-markers = [ - "python_full_version == '3.11.*'", - "python_full_version < '3.11'", -] -sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468, upload-time = "2024-02-05T23:48:01.194Z" }, - { url = "https://files.pythonhosted.org/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411, upload-time = "2024-02-05T23:48:29.038Z" }, - { url = "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016, upload-time = "2024-02-05T23:48:54.098Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889, upload-time = "2024-02-05T23:49:25.361Z" }, - { url = "https://files.pythonhosted.org/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746, upload-time = "2024-02-05T23:49:51.983Z" }, - { url = "https://files.pythonhosted.org/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620, upload-time = "2024-02-05T23:50:22.515Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659, upload-time = "2024-02-05T23:50:35.834Z" }, - { url = "https://files.pythonhosted.org/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905, upload-time = "2024-02-05T23:51:03.701Z" }, - { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127, upload-time = "2024-02-05T23:52:15.314Z" }, - { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994, upload-time = "2024-02-05T23:52:47.569Z" }, - { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005, upload-time = "2024-02-05T23:53:15.637Z" }, - { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297, upload-time = "2024-02-05T23:53:42.16Z" }, - { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567, upload-time = "2024-02-05T23:54:11.696Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812, upload-time = "2024-02-05T23:54:26.453Z" }, - { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913, upload-time = "2024-02-05T23:54:53.933Z" }, - { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" }, - { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, - { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" }, - { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, - { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" }, - { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, - { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" }, - { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, -] - -[[package]] -name = "numpy" -version = "2.4.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", -] -sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/51/5093a2df15c4dc19da3f79d1021e891f5dcf1d9d1db6ba38891d5590f3fe/numpy-2.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:33b3bf58ee84b172c067f56aeadc7ee9ab6de69c5e800ab5b10295d54c581adb", size = 16957183, upload-time = "2026-03-09T07:55:57.774Z" }, - { url = "https://files.pythonhosted.org/packages/b5/7c/c061f3de0630941073d2598dc271ac2f6cbcf5c83c74a5870fea07488333/numpy-2.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ba7b51e71c05aa1f9bc3641463cd82308eab40ce0d5c7e1fd4038cbf9938147", size = 14968734, upload-time = "2026-03-09T07:56:00.494Z" }, - { url = "https://files.pythonhosted.org/packages/ef/27/d26c85cbcd86b26e4f125b0668e7a7c0542d19dd7d23ee12e87b550e95b5/numpy-2.4.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1988292870c7cb9d0ebb4cc96b4d447513a9644801de54606dc7aabf2b7d920", size = 5475288, upload-time = "2026-03-09T07:56:02.857Z" }, - { url = "https://files.pythonhosted.org/packages/2b/09/3c4abbc1dcd8010bf1a611d174c7aa689fc505585ec806111b4406f6f1b1/numpy-2.4.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:23b46bb6d8ecb68b58c09944483c135ae5f0e9b8d8858ece5e4ead783771d2a9", size = 6805253, upload-time = "2026-03-09T07:56:04.53Z" }, - { url = "https://files.pythonhosted.org/packages/21/bc/e7aa3f6817e40c3f517d407742337cbb8e6fc4b83ce0b55ab780c829243b/numpy-2.4.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a016db5c5dba78fa8fe9f5d80d6708f9c42ab087a739803c0ac83a43d686a470", size = 15969479, upload-time = "2026-03-09T07:56:06.638Z" }, - { url = "https://files.pythonhosted.org/packages/78/51/9f5d7a41f0b51649ddf2f2320595e15e122a40610b233d51928dd6c92353/numpy-2.4.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:715de7f82e192e8cae5a507a347d97ad17598f8e026152ca97233e3666daaa71", size = 16901035, upload-time = "2026-03-09T07:56:09.405Z" }, - { url = "https://files.pythonhosted.org/packages/64/6e/b221dd847d7181bc5ee4857bfb026182ef69499f9305eb1371cbb1aea626/numpy-2.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ddb7919366ee468342b91dea2352824c25b55814a987847b6c52003a7c97f15", size = 17325657, upload-time = "2026-03-09T07:56:12.067Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b8/8f3fd2da596e1063964b758b5e3c970aed1949a05200d7e3d46a9d46d643/numpy-2.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a315e5234d88067f2d97e1f2ef670a7569df445d55400f1e33d117418d008d52", size = 18635512, upload-time = "2026-03-09T07:56:14.629Z" }, - { url = "https://files.pythonhosted.org/packages/5c/24/2993b775c37e39d2f8ab4125b44337ab0b2ba106c100980b7c274a22bee7/numpy-2.4.3-cp311-cp311-win32.whl", hash = "sha256:2b3f8d2c4589b1a2028d2a770b0fc4d1f332fb5e01521f4de3199a896d158ddd", size = 6238100, upload-time = "2026-03-09T07:56:17.243Z" }, - { url = "https://files.pythonhosted.org/packages/76/1d/edccf27adedb754db7c4511d5eac8b83f004ae948fe2d3509e8b78097d4c/numpy-2.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:77e76d932c49a75617c6d13464e41203cd410956614d0a0e999b25e9e8d27eec", size = 12609816, upload-time = "2026-03-09T07:56:19.089Z" }, - { url = "https://files.pythonhosted.org/packages/92/82/190b99153480076c8dce85f4cfe7d53ea84444145ffa54cb58dcd460d66b/numpy-2.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:eb610595dd91560905c132c709412b512135a60f1851ccbd2c959e136431ff67", size = 10485757, upload-time = "2026-03-09T07:56:21.753Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628, upload-time = "2026-03-09T07:56:24.252Z" }, - { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872, upload-time = "2026-03-09T07:56:26.991Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489, upload-time = "2026-03-09T07:56:29.414Z" }, - { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814, upload-time = "2026-03-09T07:56:32.225Z" }, - { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601, upload-time = "2026-03-09T07:56:34.461Z" }, - { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358, upload-time = "2026-03-09T07:56:36.852Z" }, - { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135, upload-time = "2026-03-09T07:56:39.337Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816, upload-time = "2026-03-09T07:56:42.31Z" }, - { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132, upload-time = "2026-03-09T07:56:44.851Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144, upload-time = "2026-03-09T07:56:47.057Z" }, - { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364, upload-time = "2026-03-09T07:56:49.849Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" }, - { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" }, - { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" }, - { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" }, - { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" }, - { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" }, - { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" }, - { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" }, - { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" }, - { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" }, - { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" }, - { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" }, - { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" }, - { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" }, - { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" }, - { url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563, upload-time = "2026-03-09T07:57:43.817Z" }, - { url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161, upload-time = "2026-03-09T07:57:46.169Z" }, - { url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738, upload-time = "2026-03-09T07:57:48.506Z" }, - { url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618, upload-time = "2026-03-09T07:57:50.591Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676, upload-time = "2026-03-09T07:57:52.34Z" }, - { url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492, upload-time = "2026-03-09T07:57:54.91Z" }, - { url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789, upload-time = "2026-03-09T07:57:57.641Z" }, - { url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941, upload-time = "2026-03-09T07:58:00.577Z" }, - { url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503, upload-time = "2026-03-09T07:58:03.331Z" }, - { url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915, upload-time = "2026-03-09T07:58:06.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875, upload-time = "2026-03-09T07:58:08.734Z" }, - { url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225, upload-time = "2026-03-09T07:58:11.034Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769, upload-time = "2026-03-09T07:58:13.67Z" }, - { url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461, upload-time = "2026-03-09T07:58:15.912Z" }, - { url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809, upload-time = "2026-03-09T07:58:17.787Z" }, - { url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242, upload-time = "2026-03-09T07:58:20.476Z" }, - { url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660, upload-time = "2026-03-09T07:58:23.067Z" }, - { url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384, upload-time = "2026-03-09T07:58:25.839Z" }, - { url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547, upload-time = "2026-03-09T07:58:28.289Z" }, - { url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645, upload-time = "2026-03-09T07:58:30.384Z" }, - { url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454, upload-time = "2026-03-09T07:58:33.031Z" }, - { url = "https://files.pythonhosted.org/packages/64/e4/4dab9fb43c83719c29241c535d9e07be73bea4bc0c6686c5816d8e1b6689/numpy-2.4.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c6b124bfcafb9e8d3ed09130dbee44848c20b3e758b6bbf006e641778927c028", size = 16834892, upload-time = "2026-03-09T07:58:35.334Z" }, - { url = "https://files.pythonhosted.org/packages/c9/29/f8b6d4af90fed3dfda84ebc0df06c9833d38880c79ce954e5b661758aa31/numpy-2.4.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:76dbb9d4e43c16cf9aa711fcd8de1e2eeb27539dcefb60a1d5e9f12fae1d1ed8", size = 14893070, upload-time = "2026-03-09T07:58:37.7Z" }, - { url = "https://files.pythonhosted.org/packages/9a/04/a19b3c91dbec0a49269407f15d5753673a09832daed40c45e8150e6fa558/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:29363fbfa6f8ee855d7569c96ce524845e3d726d6c19b29eceec7dd555dab152", size = 5399609, upload-time = "2026-03-09T07:58:39.853Z" }, - { url = "https://files.pythonhosted.org/packages/79/34/4d73603f5420eab89ea8a67097b31364bf7c30f811d4dd84b1659c7476d9/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:bc71942c789ef415a37f0d4eab90341425a00d538cd0642445d30b41023d3395", size = 6714355, upload-time = "2026-03-09T07:58:42.365Z" }, - { url = "https://files.pythonhosted.org/packages/58/ad/1100d7229bb248394939a12a8074d485b655e8ed44207d328fdd7fcebc7b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e58765ad74dcebd3ef0208a5078fba32dc8ec3578fe84a604432950cd043d79", size = 15800434, upload-time = "2026-03-09T07:58:44.837Z" }, - { url = "https://files.pythonhosted.org/packages/0c/fd/16d710c085d28ba4feaf29ac60c936c9d662e390344f94a6beaa2ac9899b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e236dbda4e1d319d681afcbb136c0c4a8e0f1a5c58ceec2adebb547357fe857", size = 16729409, upload-time = "2026-03-09T07:58:47.972Z" }, - { url = "https://files.pythonhosted.org/packages/57/a7/b35835e278c18b85206834b3aa3abe68e77a98769c59233d1f6300284781/numpy-2.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b42639cdde6d24e732ff823a3fa5b701d8acad89c4142bc1d0bd6dc85200ba5", size = 12504685, upload-time = "2026-03-09T07:58:50.525Z" }, -] - [[package]] name = "openai" version = "2.26.0" @@ -1325,68 +1063,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] -[[package]] -name = "pandas" -version = "2.3.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.4.3", source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, - { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, - { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, - { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, - { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, - { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, - { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, - { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, - { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, - { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, - { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, - { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, - { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, - { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, - { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, - { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, - { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, - { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, - { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, - { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, - { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, - { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, - { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, - { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, - { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, - { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, - { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, - { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, - { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, - { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, - { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, - { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, - { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, - { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, - { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, - { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, - { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, -] - [[package]] name = "parso" version = "0.8.6" @@ -1462,88 +1138,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, ] -[[package]] -name = "py4j" -version = "0.10.9.7" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -resolution-markers = [ - "python_full_version == '3.11.*'", - "python_full_version < '3.11'", -] -sdist = { url = "https://files.pythonhosted.org/packages/1e/f2/b34255180c72c36ff7097f7c2cdca02abcbd89f5eebf7c7c41262a9a0637/py4j-0.10.9.7.tar.gz", hash = "sha256:0b6e5315bb3ada5cf62ac651d107bb2ebc02def3dee9d9548e3baac644ea8dbb", size = 1508234, upload-time = "2022-08-12T22:49:09.792Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/30/a58b32568f1623aaad7db22aa9eafc4c6c194b429ff35bdc55ca2726da47/py4j-0.10.9.7-py2.py3-none-any.whl", hash = "sha256:85defdfd2b2376eb3abf5ca6474b51ab7e0de341c75a02f46dc9b5976f5a5c1b", size = 200481, upload-time = "2022-08-12T22:49:07.05Z" }, -] - -[[package]] -name = "py4j" -version = "0.10.9.9" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", -] -sdist = { url = "https://files.pythonhosted.org/packages/38/31/0b210511177070c8d5d3059556194352e5753602fa64b85b7ab81ec1a009/py4j-0.10.9.9.tar.gz", hash = "sha256:f694cad19efa5bd1dee4f3e5270eb406613c974394035e5bfc4ec1aba870b879", size = 761089, upload-time = "2025-01-15T03:53:18.624Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/db/ea0203e495be491c85af87b66e37acfd3bf756fd985f87e46fc5e3bf022c/py4j-0.10.9.9-py2.py3-none-any.whl", hash = "sha256:c7c26e4158defb37b0bb124933163641a2ff6e3a3913f7811b0ddbe07ed61533", size = 203008, upload-time = "2025-01-15T03:53:15.648Z" }, -] - -[[package]] -name = "pyarrow" -version = "23.0.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/22/134986a4cc224d593c1afde5494d18ff629393d74cc2eddb176669f234a4/pyarrow-23.0.1.tar.gz", hash = "sha256:b8c5873e33440b2bc2f4a79d2b47017a89c5a24116c055625e6f2ee50523f019", size = 1167336, upload-time = "2026-02-16T10:14:12.39Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/a8/24e5dc6855f50a62936ceb004e6e9645e4219a8065f304145d7fb8a79d5d/pyarrow-23.0.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:3fab8f82571844eb3c460f90a75583801d14ca0cc32b1acc8c361650e006fd56", size = 34307390, upload-time = "2026-02-16T10:08:08.654Z" }, - { url = "https://files.pythonhosted.org/packages/bc/8e/4be5617b4aaae0287f621ad31c6036e5f63118cfca0dc57d42121ff49b51/pyarrow-23.0.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:3f91c038b95f71ddfc865f11d5876c42f343b4495535bd262c7b321b0b94507c", size = 35853761, upload-time = "2026-02-16T10:08:17.811Z" }, - { url = "https://files.pythonhosted.org/packages/2e/08/3e56a18819462210432ae37d10f5c8eed3828be1d6c751b6e6a2e93c286a/pyarrow-23.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:d0744403adabef53c985a7f8a082b502a368510c40d184df349a0a8754533258", size = 44493116, upload-time = "2026-02-16T10:08:25.792Z" }, - { url = "https://files.pythonhosted.org/packages/f8/82/c40b68001dbec8a3faa4c08cd8c200798ac732d2854537c5449dc859f55a/pyarrow-23.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c33b5bf406284fd0bba436ed6f6c3ebe8e311722b441d89397c54f871c6863a2", size = 47564532, upload-time = "2026-02-16T10:08:34.27Z" }, - { url = "https://files.pythonhosted.org/packages/20/bc/73f611989116b6f53347581b02177f9f620efdf3cd3f405d0e83cdf53a83/pyarrow-23.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ddf743e82f69dcd6dbbcb63628895d7161e04e56794ef80550ac6f3315eeb1d5", size = 48183685, upload-time = "2026-02-16T10:08:42.889Z" }, - { url = "https://files.pythonhosted.org/packages/b0/cc/6c6b3ecdae2a8c3aced99956187e8302fc954cc2cca2a37cf2111dad16ce/pyarrow-23.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e052a211c5ac9848ae15d5ec875ed0943c0221e2fcfe69eee80b604b4e703222", size = 50605582, upload-time = "2026-02-16T10:08:51.641Z" }, - { url = "https://files.pythonhosted.org/packages/8d/94/d359e708672878d7638a04a0448edf7c707f9e5606cee11e15aaa5c7535a/pyarrow-23.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:5abde149bb3ce524782d838eb67ac095cd3fd6090eba051130589793f1a7f76d", size = 27521148, upload-time = "2026-02-16T10:08:58.077Z" }, - { url = "https://files.pythonhosted.org/packages/b0/41/8e6b6ef7e225d4ceead8459427a52afdc23379768f54dd3566014d7618c1/pyarrow-23.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6f0147ee9e0386f519c952cc670eb4a8b05caa594eeffe01af0e25f699e4e9bb", size = 34302230, upload-time = "2026-02-16T10:09:03.859Z" }, - { url = "https://files.pythonhosted.org/packages/bf/4a/1472c00392f521fea03ae93408bf445cc7bfa1ab81683faf9bc188e36629/pyarrow-23.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:0ae6e17c828455b6265d590100c295193f93cc5675eb0af59e49dbd00d2de350", size = 35850050, upload-time = "2026-02-16T10:09:11.877Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b2/bd1f2f05ded56af7f54d702c8364c9c43cd6abb91b0e9933f3d77b4f4132/pyarrow-23.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:fed7020203e9ef273360b9e45be52a2a47d3103caf156a30ace5247ffb51bdbd", size = 44491918, upload-time = "2026-02-16T10:09:18.144Z" }, - { url = "https://files.pythonhosted.org/packages/0b/62/96459ef5b67957eac38a90f541d1c28833d1b367f014a482cb63f3b7cd2d/pyarrow-23.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:26d50dee49d741ac0e82185033488d28d35be4d763ae6f321f97d1140eb7a0e9", size = 47562811, upload-time = "2026-02-16T10:09:25.792Z" }, - { url = "https://files.pythonhosted.org/packages/7d/94/1170e235add1f5f45a954e26cd0e906e7e74e23392dcb560de471f7366ec/pyarrow-23.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c30143b17161310f151f4a2bcfe41b5ff744238c1039338779424e38579d701", size = 48183766, upload-time = "2026-02-16T10:09:34.645Z" }, - { url = "https://files.pythonhosted.org/packages/0e/2d/39a42af4570377b99774cdb47f63ee6c7da7616bd55b3d5001aa18edfe4f/pyarrow-23.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db2190fa79c80a23fdd29fef4b8992893f024ae7c17d2f5f4db7171fa30c2c78", size = 50607669, upload-time = "2026-02-16T10:09:44.153Z" }, - { url = "https://files.pythonhosted.org/packages/00/ca/db94101c187f3df742133ac837e93b1f269ebdac49427f8310ee40b6a58f/pyarrow-23.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:f00f993a8179e0e1c9713bcc0baf6d6c01326a406a9c23495ec1ba9c9ebf2919", size = 27527698, upload-time = "2026-02-16T10:09:50.263Z" }, - { url = "https://files.pythonhosted.org/packages/9a/4b/4166bb5abbfe6f750fc60ad337c43ecf61340fa52ab386da6e8dbf9e63c4/pyarrow-23.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f4b0dbfa124c0bb161f8b5ebb40f1a680b70279aa0c9901d44a2b5a20806039f", size = 34214575, upload-time = "2026-02-16T10:09:56.225Z" }, - { url = "https://files.pythonhosted.org/packages/e1/da/3f941e3734ac8088ea588b53e860baeddac8323ea40ce22e3d0baa865cc9/pyarrow-23.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:7707d2b6673f7de054e2e83d59f9e805939038eebe1763fe811ee8fa5c0cd1a7", size = 35832540, upload-time = "2026-02-16T10:10:03.428Z" }, - { url = "https://files.pythonhosted.org/packages/88/7c/3d841c366620e906d54430817531b877ba646310296df42ef697308c2705/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:86ff03fb9f1a320266e0de855dee4b17da6794c595d207f89bba40d16b5c78b9", size = 44470940, upload-time = "2026-02-16T10:10:10.704Z" }, - { url = "https://files.pythonhosted.org/packages/2c/a5/da83046273d990f256cb79796a190bbf7ec999269705ddc609403f8c6b06/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:813d99f31275919c383aab17f0f455a04f5a429c261cc411b1e9a8f5e4aaaa05", size = 47586063, upload-time = "2026-02-16T10:10:17.95Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/b7d2ebcff47a514f47f9da1e74b7949138c58cfeb108cdd4ee62f43f0cf3/pyarrow-23.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bf5842f960cddd2ef757d486041d57c96483efc295a8c4a0e20e704cbbf39c67", size = 48173045, upload-time = "2026-02-16T10:10:25.363Z" }, - { url = "https://files.pythonhosted.org/packages/43/b2/b40961262213beaba6acfc88698eb773dfce32ecdf34d19291db94c2bd73/pyarrow-23.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564baf97c858ecc03ec01a41062e8f4698abc3e6e2acd79c01c2e97880a19730", size = 50621741, upload-time = "2026-02-16T10:10:33.477Z" }, - { url = "https://files.pythonhosted.org/packages/f6/70/1fdda42d65b28b078e93d75d371b2185a61da89dda4def8ba6ba41ebdeb4/pyarrow-23.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:07deae7783782ac7250989a7b2ecde9b3c343a643f82e8a4df03d93b633006f0", size = 27620678, upload-time = "2026-02-16T10:10:39.31Z" }, - { url = "https://files.pythonhosted.org/packages/47/10/2cbe4c6f0fb83d2de37249567373d64327a5e4d8db72f486db42875b08f6/pyarrow-23.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6b8fda694640b00e8af3c824f99f789e836720aa8c9379fb435d4c4953a756b8", size = 34210066, upload-time = "2026-02-16T10:10:45.487Z" }, - { url = "https://files.pythonhosted.org/packages/cb/4f/679fa7e84dadbaca7a65f7cdba8d6c83febbd93ca12fa4adf40ba3b6362b/pyarrow-23.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:8ff51b1addc469b9444b7c6f3548e19dc931b172ab234e995a60aea9f6e6025f", size = 35825526, upload-time = "2026-02-16T10:10:52.266Z" }, - { url = "https://files.pythonhosted.org/packages/f9/63/d2747d930882c9d661e9398eefc54f15696547b8983aaaf11d4a2e8b5426/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:71c5be5cbf1e1cb6169d2a0980850bccb558ddc9b747b6206435313c47c37677", size = 44473279, upload-time = "2026-02-16T10:11:01.557Z" }, - { url = "https://files.pythonhosted.org/packages/b3/93/10a48b5e238de6d562a411af6467e71e7aedbc9b87f8d3a35f1560ae30fb/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9b6f4f17b43bc39d56fec96e53fe89d94bac3eb134137964371b45352d40d0c2", size = 47585798, upload-time = "2026-02-16T10:11:09.401Z" }, - { url = "https://files.pythonhosted.org/packages/5c/20/476943001c54ef078dbf9542280e22741219a184a0632862bca4feccd666/pyarrow-23.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fc13fc6c403d1337acab46a2c4346ca6c9dec5780c3c697cf8abfd5e19b6b37", size = 48179446, upload-time = "2026-02-16T10:11:17.781Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b6/5dd0c47b335fcd8edba9bfab78ad961bd0fd55ebe53468cc393f45e0be60/pyarrow-23.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c16ed4f53247fa3ffb12a14d236de4213a4415d127fe9cebed33d51671113e2", size = 50623972, upload-time = "2026-02-16T10:11:26.185Z" }, - { url = "https://files.pythonhosted.org/packages/d5/09/a532297c9591a727d67760e2e756b83905dd89adb365a7f6e9c72578bcc1/pyarrow-23.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:cecfb12ef629cf6be0b1887f9f86463b0dd3dc3195ae6224e74006be4736035a", size = 27540749, upload-time = "2026-02-16T10:12:23.297Z" }, - { url = "https://files.pythonhosted.org/packages/a5/8e/38749c4b1303e6ae76b3c80618f84861ae0c55dd3c2273842ea6f8258233/pyarrow-23.0.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:29f7f7419a0e30264ea261fdc0e5fe63ce5a6095003db2945d7cd78df391a7e1", size = 34471544, upload-time = "2026-02-16T10:11:32.535Z" }, - { url = "https://files.pythonhosted.org/packages/a3/73/f237b2bc8c669212f842bcfd842b04fc8d936bfc9d471630569132dc920d/pyarrow-23.0.1-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:33d648dc25b51fd8055c19e4261e813dfc4d2427f068bcecc8b53d01b81b0500", size = 35949911, upload-time = "2026-02-16T10:11:39.813Z" }, - { url = "https://files.pythonhosted.org/packages/0c/86/b912195eee0903b5611bf596833def7d146ab2d301afeb4b722c57ffc966/pyarrow-23.0.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd395abf8f91c673dd3589cadc8cc1ee4e8674fa61b2e923c8dd215d9c7d1f41", size = 44520337, upload-time = "2026-02-16T10:11:47.764Z" }, - { url = "https://files.pythonhosted.org/packages/69/c2/f2a717fb824f62d0be952ea724b4f6f9372a17eed6f704b5c9526f12f2f1/pyarrow-23.0.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:00be9576d970c31defb5c32eb72ef585bf600ef6d0a82d5eccaae96639cf9d07", size = 47548944, upload-time = "2026-02-16T10:11:56.607Z" }, - { url = "https://files.pythonhosted.org/packages/84/a7/90007d476b9f0dc308e3bc57b832d004f848fd6c0da601375d20d92d1519/pyarrow-23.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c2139549494445609f35a5cda4eb94e2c9e4d704ce60a095b342f82460c73a83", size = 48236269, upload-time = "2026-02-16T10:12:04.47Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3f/b16fab3e77709856eb6ac328ce35f57a6d4a18462c7ca5186ef31b45e0e0/pyarrow-23.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7044b442f184d84e2351e5084600f0d7343d6117aabcbc1ac78eb1ae11eb4125", size = 50604794, upload-time = "2026-02-16T10:12:11.797Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a1/22df0620a9fac31d68397a75465c344e83c3dfe521f7612aea33e27ab6c0/pyarrow-23.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a35581e856a2fafa12f3f54fce4331862b1cfb0bef5758347a858a4aa9d6bae8", size = 27660642, upload-time = "2026-02-16T10:12:17.746Z" }, - { url = "https://files.pythonhosted.org/packages/8d/1b/6da9a89583ce7b23ac611f183ae4843cd3a6cf54f079549b0e8c14031e73/pyarrow-23.0.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:5df1161da23636a70838099d4aaa65142777185cc0cdba4037a18cee7d8db9ca", size = 34238755, upload-time = "2026-02-16T10:12:32.819Z" }, - { url = "https://files.pythonhosted.org/packages/ae/b5/d58a241fbe324dbaeb8df07be6af8752c846192d78d2272e551098f74e88/pyarrow-23.0.1-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:fa8e51cb04b9f8c9c5ace6bab63af9a1f88d35c0d6cbf53e8c17c098552285e1", size = 35847826, upload-time = "2026-02-16T10:12:38.949Z" }, - { url = "https://files.pythonhosted.org/packages/54/a5/8cbc83f04aba433ca7b331b38f39e000efd9f0c7ce47128670e737542996/pyarrow-23.0.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0b95a3994f015be13c63148fef8832e8a23938128c185ee951c98908a696e0eb", size = 44536859, upload-time = "2026-02-16T10:12:45.467Z" }, - { url = "https://files.pythonhosted.org/packages/36/2e/c0f017c405fcdc252dbccafbe05e36b0d0eb1ea9a958f081e01c6972927f/pyarrow-23.0.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4982d71350b1a6e5cfe1af742c53dfb759b11ce14141870d05d9e540d13bc5d1", size = 47614443, upload-time = "2026-02-16T10:12:55.525Z" }, - { url = "https://files.pythonhosted.org/packages/af/6b/2314a78057912f5627afa13ba43809d9d653e6630859618b0fd81a4e0759/pyarrow-23.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c250248f1fe266db627921c89b47b7c06fee0489ad95b04d50353537d74d6886", size = 48232991, upload-time = "2026-02-16T10:13:04.729Z" }, - { url = "https://files.pythonhosted.org/packages/40/f2/1bcb1d3be3460832ef3370d621142216e15a2c7c62602a4ea19ec240dd64/pyarrow-23.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f4763b83c11c16e5f4c15601ba6dfa849e20723b46aa2617cb4bffe8768479f", size = 50645077, upload-time = "2026-02-16T10:13:14.147Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3f/b1da7b61cd66566a4d4c8383d376c606d1c34a906c3f1cb35c479f59d1aa/pyarrow-23.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:3a4c85ef66c134161987c17b147d6bffdca4566f9a4c1d81a0a01cdf08414ea5", size = 28234271, upload-time = "2026-02-16T10:14:09.397Z" }, - { url = "https://files.pythonhosted.org/packages/b5/78/07f67434e910a0f7323269be7bfbf58699bd0c1d080b18a1ab49ba943fe8/pyarrow-23.0.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:17cd28e906c18af486a499422740298c52d7c6795344ea5002a7720b4eadf16d", size = 34488692, upload-time = "2026-02-16T10:13:21.541Z" }, - { url = "https://files.pythonhosted.org/packages/50/76/34cf7ae93ece1f740a04910d9f7e80ba166b9b4ab9596a953e9e62b90fe1/pyarrow-23.0.1-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:76e823d0e86b4fb5e1cf4a58d293036e678b5a4b03539be933d3b31f9406859f", size = 35964383, upload-time = "2026-02-16T10:13:28.63Z" }, - { url = "https://files.pythonhosted.org/packages/46/90/459b827238936d4244214be7c684e1b366a63f8c78c380807ae25ed92199/pyarrow-23.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a62e1899e3078bf65943078b3ad2a6ddcacf2373bc06379aac61b1e548a75814", size = 44538119, upload-time = "2026-02-16T10:13:35.506Z" }, - { url = "https://files.pythonhosted.org/packages/28/a1/93a71ae5881e99d1f9de1d4554a87be37da11cd6b152239fb5bd924fdc64/pyarrow-23.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:df088e8f640c9fae3b1f495b3c64755c4e719091caf250f3a74d095ddf3c836d", size = 47571199, upload-time = "2026-02-16T10:13:42.504Z" }, - { url = "https://files.pythonhosted.org/packages/88/a3/d2c462d4ef313521eaf2eff04d204ac60775263f1fb08c374b543f79f610/pyarrow-23.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:46718a220d64677c93bc243af1d44b55998255427588e400677d7192671845c7", size = 48259435, upload-time = "2026-02-16T10:13:49.226Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f1/11a544b8c3d38a759eb3fbb022039117fd633e9a7b19e4841cc3da091915/pyarrow-23.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a09f3876e87f48bc2f13583ab551f0379e5dfb83210391e68ace404181a20690", size = 50629149, upload-time = "2026-02-16T10:13:57.238Z" }, - { url = "https://files.pythonhosted.org/packages/50/f2/c0e76a0b451ffdf0cf788932e182758eb7558953f4f27f1aff8e2518b653/pyarrow-23.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:527e8d899f14bd15b740cd5a54ad56b7f98044955373a17179d5956ddb93d9ce", size = 28365807, upload-time = "2026-02-16T10:14:03.892Z" }, -] - [[package]] name = "pyasn1" version = "0.6.3" @@ -1787,19 +1381,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] -[[package]] -name = "pytest-rerunfailures" -version = "16.1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -dependencies = [ - { name = "packaging" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/de/04/71e9520551fc8fe2cf5c1a1842e4e600265b0815f2016b7c27ec85688682/pytest_rerunfailures-16.1.tar.gz", hash = "sha256:c38b266db8a808953ebd71ac25c381cb1981a78ff9340a14bcb9f1b9bff1899e", size = 30889, upload-time = "2025-10-10T07:06:01.238Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/54/60eabb34445e3db3d3d874dc1dfa72751bfec3265bd611cb13c8b290adea/pytest_rerunfailures-16.1-py3-none-any.whl", hash = "sha256:5d11b12c0ca9a1665b5054052fcc1084f8deadd9328962745ef6b04e26382e86", size = 14093, upload-time = "2025-10-10T07:06:00.019Z" }, -] - [[package]] name = "pytest-xdist" version = "3.8.0" @@ -1813,27 +1394,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, ] -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - -[[package]] -name = "pytz" -version = "2026.1.post1" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088, upload-time = "2026-03-03T07:47:50.683Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489, upload-time = "2026-03-03T07:47:49.167Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3" @@ -2104,15 +1664,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, ] -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, -] - [[package]] name = "sniffio" version = "1.3.1" @@ -2302,15 +1853,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] -[[package]] -name = "tzdata" -version = "2025.3" -source = { registry = "https://pypi-proxy.cloud.databricks.com/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, -] - [[package]] name = "urllib3" version = "2.6.3"