diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81f6dc20..4631164a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,17 +1,18 @@ name: CI on: push: - branches: - - main - pull_request: - branches: - - main - - next + branches-ignore: + - 'generated' + - 'codegen/**' + - 'integrated/**' + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: + timeout-minutes: 10 name: lint - runs-on: ubuntu-latest + runs-on: ${{ github.repository == 'stainless-sdks/asktable-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 @@ -29,9 +30,34 @@ jobs: - name: Run lints run: ./scripts/lint + upload: + if: github.repository == 'stainless-sdks/asktable-python' + timeout-minutes: 10 + name: upload + permissions: + contents: read + id-token: write + runs-on: depot-ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Get GitHub OIDC Token + id: github-oidc + uses: actions/github-script@v6 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Upload tarball + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + run: ./scripts/utils/upload-artifact.sh + test: + timeout-minutes: 10 name: test - runs-on: ubuntu-latest + runs-on: ${{ github.repository == 'stainless-sdks/asktable-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fb1ef4a0..5f7bf255 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "5.3.1" + ".": "5.4.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index a75fdfa0..499f0df9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 96 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/datamini%2Fasktable-5f188889fe648ea83d57911900fe61825d9d7d1446fce966ffca7a9bbe2e3079.yml -openapi_spec_hash: 42af219a138819686969b60410be0cb6 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/datamini%2Fasktable-fd9a749a4afed8a45757ab4e83984c36c56cf6f4f0d53b80d4d5e0022869c3e1.yml +openapi_spec_hash: d5cf0471c4e715bdfbf597de3f591ef1 config_hash: a572ab842ea60ce13f1d1a1358440cbe diff --git a/CHANGELOG.md b/CHANGELOG.md index 62733995..820ef2c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,49 @@ # Changelog +## 5.4.0 (2025-05-27) + +Full Changelog: [v5.3.1...v5.4.0](https://github.com/DataMini/asktable-python/compare/v5.3.1...v5.4.0) + +### Features + +* **api:** api update ([84767d7](https://github.com/DataMini/asktable-python/commit/84767d7648a7df94f1caf85628576ea948d94475)) +* **api:** api update ([558ab83](https://github.com/DataMini/asktable-python/commit/558ab837046be3f9e902a2e073096217bda1438e)) +* **api:** api update ([07d802a](https://github.com/DataMini/asktable-python/commit/07d802a94360a7e66e0594b5ba39b9fc4916a56d)) +* **api:** api update ([18a5eaf](https://github.com/DataMini/asktable-python/commit/18a5eafaf07afc7c59141e957104728fe2d413b5)) +* **api:** api update ([dc90fe3](https://github.com/DataMini/asktable-python/commit/dc90fe3f3b0829913fde1a288eb8f3ff6fbf7e6d)) +* **api:** api update ([8fda6da](https://github.com/DataMini/asktable-python/commit/8fda6da68f5c439b69572ad06df119915475acd4)) +* **api:** api update ([8cfc989](https://github.com/DataMini/asktable-python/commit/8cfc98987196f71dc9b1ac788a66e7fb7a6b62c9)) +* **api:** api update ([317b682](https://github.com/DataMini/asktable-python/commit/317b682b9de2a8803f8d2be7be7a9ed8ad1e961c)) + + +### Bug Fixes + +* **package:** support direct resource imports ([e082d60](https://github.com/DataMini/asktable-python/commit/e082d60e17dacc06f1a3091454db8d522bf8e7a2)) +* **perf:** optimize some hot paths ([4ce95f7](https://github.com/DataMini/asktable-python/commit/4ce95f7c3a5f0b7568ad63d0bb6fe8dade3d20ff)) +* **perf:** skip traversing types for NotGiven values ([00250bb](https://github.com/DataMini/asktable-python/commit/00250bbfc900cb7003e3e77239ced58934b6d826)) +* **pydantic v1:** more robust ModelField.annotation check ([c16373d](https://github.com/DataMini/asktable-python/commit/c16373dba54d24f705650aa38d3b4c1a772f23ff)) + + +### Chores + +* broadly detect json family of content-type headers ([3bff15d](https://github.com/DataMini/asktable-python/commit/3bff15dcccae660478bd3e6a30aa609e4bcc186f)) +* **ci:** add timeout thresholds for CI jobs ([f51cedc](https://github.com/DataMini/asktable-python/commit/f51cedc6e85b7300ae989972c02c2f4303c7838a)) +* **ci:** fix installation instructions ([02f7bb0](https://github.com/DataMini/asktable-python/commit/02f7bb02172f9ac58153e92c2acc063c876fd63b)) +* **ci:** only use depot for staging repos ([6c155eb](https://github.com/DataMini/asktable-python/commit/6c155eb595e7227c769e953a5040b577f85f494c)) +* **ci:** upload sdks to package manager ([ed6c603](https://github.com/DataMini/asktable-python/commit/ed6c603a22bc79cf74791566cef81b536dc0d0b3)) +* **client:** minor internal fixes ([b4af709](https://github.com/DataMini/asktable-python/commit/b4af709160aa14f36bef3a624c069b21290ded66)) +* **docs:** grammar improvements ([8f09ac8](https://github.com/DataMini/asktable-python/commit/8f09ac84dd9df377925ec5ed214037c1c5b23d48)) +* **internal:** avoid errors for isinstance checks on proxies ([79c1774](https://github.com/DataMini/asktable-python/commit/79c1774d4d811f45d947711c509911d5b0880e72)) +* **internal:** base client updates ([7e7f8c5](https://github.com/DataMini/asktable-python/commit/7e7f8c56783a125505f587d6f206da00da98f438)) +* **internal:** bump pyright version ([b56ec6c](https://github.com/DataMini/asktable-python/commit/b56ec6cc156694361cbd43a2d8590813f30a47c0)) +* **internal:** codegen related update ([3719d1b](https://github.com/DataMini/asktable-python/commit/3719d1bf1db482d5f08368b6162d67c878e48369)) +* **internal:** fix list file params ([12bba1b](https://github.com/DataMini/asktable-python/commit/12bba1b329d0149f1b8065c983efbddda6a1a1d1)) +* **internal:** import reformatting ([bb1b5a8](https://github.com/DataMini/asktable-python/commit/bb1b5a84fbea13e655f968639f3e5edf584914ad)) +* **internal:** minor formatting changes ([1f83169](https://github.com/DataMini/asktable-python/commit/1f83169570f13ba1ede9c92a23e610018d39f8e0)) +* **internal:** refactor retries to not use recursion ([af64ff0](https://github.com/DataMini/asktable-python/commit/af64ff050f75fae9c6368c6c0d105197f227397c)) +* **internal:** update models test ([4e84f01](https://github.com/DataMini/asktable-python/commit/4e84f01f5b52e119f73cab72e4ea3ceaa1889e42)) +* **internal:** update pyright settings ([c1a4b25](https://github.com/DataMini/asktable-python/commit/c1a4b25eda6192bfb974f1aa6b21ae35f4ea762e)) + ## 5.3.1 (2025-04-10) Full Changelog: [v5.3.0...v5.3.1](https://github.com/DataMini/asktable-python/compare/v5.3.0...v5.3.1) diff --git a/SECURITY.md b/SECURITY.md index 6cf7553c..e9a4fcb9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,11 +16,11 @@ before making any information public. ## Reporting Non-SDK Related Security Issues If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Asktable please follow the respective company's security reporting guidelines. +or products provided by Asktable, please follow the respective company's security reporting guidelines. ### Asktable Terms and Policies -Please contact hi@datamini.ai for any questions or concerns regarding security of our services. +Please contact hi@datamini.ai for any questions or concerns regarding the security of our services. --- diff --git a/pyproject.toml b/pyproject.toml index 1ca6d95b..c1ce99ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "asktable" -version = "5.3.1" +version = "5.4.0" description = "The official Python library for the Asktable API" dynamic = ["readme"] license = "Apache-2.0" @@ -42,7 +42,7 @@ Repository = "https://github.com/DataMini/asktable-python" managed = true # version pins are in requirements-dev.lock dev-dependencies = [ - "pyright>=1.1.359", + "pyright==1.1.399", "mypy", "respx", "pytest", @@ -147,6 +147,7 @@ exclude = [ ] reportImplicitOverride = true +reportOverlappingOverload = false reportImportCycles = false reportPrivateUsage = false diff --git a/requirements-dev.lock b/requirements-dev.lock index a151188f..19fed616 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -69,7 +69,7 @@ pydantic-core==2.27.1 # via pydantic pygments==2.18.0 # via rich -pyright==1.1.392.post0 +pyright==1.1.399 pytest==8.3.3 # via pytest-asyncio pytest-asyncio==0.24.0 diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 00000000..2acefac1 --- /dev/null +++ b/scripts/utils/upload-artifact.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -exuo pipefail + +RESPONSE=$(curl -X POST "$URL" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + +SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') + +if [[ "$SIGNED_URL" == "null" ]]; then + echo -e "\033[31mFailed to get signed URL.\033[0m" + exit 1 +fi + +UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ + -H "Content-Type: application/gzip" \ + --data-binary @- "$SIGNED_URL" 2>&1) + +if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then + echo -e "\033[32mUploaded build to Stainless storage.\033[0m" + echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/asktable-python/$SHA'\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi diff --git a/src/asktable/__init__.py b/src/asktable/__init__.py index 2bf9f563..76486c01 100644 --- a/src/asktable/__init__.py +++ b/src/asktable/__init__.py @@ -1,5 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import typing as _t + from . import types from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes from ._utils import file_from_path @@ -78,6 +80,9 @@ "DefaultAsyncHttpxClient", ] +if not _t.TYPE_CHECKING: + from ._utils._resources_proxy import resources as resources + _setup_logging() # Update the __module__ attribute for exported symbols so that diff --git a/src/asktable/_base_client.py b/src/asktable/_base_client.py index b43d29e9..5d4a145a 100644 --- a/src/asktable/_base_client.py +++ b/src/asktable/_base_client.py @@ -98,7 +98,11 @@ _AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any]) if TYPE_CHECKING: - from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT + from httpx._config import ( + DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage] + ) + + HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG else: try: from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT @@ -115,6 +119,7 @@ class PageInfo: url: URL | NotGiven params: Query | NotGiven + json: Body | NotGiven @overload def __init__( @@ -130,19 +135,30 @@ def __init__( params: Query, ) -> None: ... + @overload + def __init__( + self, + *, + json: Body, + ) -> None: ... + def __init__( self, *, url: URL | NotGiven = NOT_GIVEN, + json: Body | NotGiven = NOT_GIVEN, params: Query | NotGiven = NOT_GIVEN, ) -> None: self.url = url + self.json = json self.params = params @override def __repr__(self) -> str: if self.url: return f"{self.__class__.__name__}(url={self.url})" + if self.json: + return f"{self.__class__.__name__}(json={self.json})" return f"{self.__class__.__name__}(params={self.params})" @@ -191,6 +207,19 @@ def _info_to_options(self, info: PageInfo) -> FinalRequestOptions: options.url = str(url) return options + if not isinstance(info.json, NotGiven): + if not is_mapping(info.json): + raise TypeError("Pagination is only supported with mappings") + + if not options.json_data: + options.json_data = {**info.json} + else: + if not is_mapping(options.json_data): + raise TypeError("Pagination is only supported with mappings") + + options.json_data = {**options.json_data, **info.json} + return options + raise ValueError("Unexpected PageInfo state") @@ -408,8 +437,8 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0 headers = httpx.Headers(headers_dict) idempotency_header = self._idempotency_header - if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers: - headers[idempotency_header] = options.idempotency_key or self._idempotency_key() + if idempotency_header and options.idempotency_key and idempotency_header not in headers: + headers[idempotency_header] = options.idempotency_key # Don't set these headers if they were already set or removed by the caller. We check # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case. @@ -873,7 +902,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[True], stream_cls: Type[_StreamT], @@ -884,7 +912,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[False] = False, ) -> ResponseT: ... @@ -894,7 +921,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: Type[_StreamT] | None = None, @@ -904,121 +930,109 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: type[_StreamT] | None = None, ) -> ResponseT | _StreamT: - if remaining_retries is not None: - retries_taken = options.get_max_retries(self.max_retries) - remaining_retries - else: - retries_taken = 0 - - return self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - retries_taken=retries_taken, - ) + cast_to = self._maybe_override_cast_to(cast_to, options) - def _request( - self, - *, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - retries_taken: int, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: # create a copy of the options we were given so that if the # options are mutated later & we then retry, the retries are # given the original options input_options = model_copy(options) + if input_options.idempotency_key is None and input_options.method.lower() != "get": + # ensure the idempotency key is reused between requests + input_options.idempotency_key = self._idempotency_key() - cast_to = self._maybe_override_cast_to(cast_to, options) - options = self._prepare_options(options) - - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken - request = self._build_request(options, retries_taken=retries_taken) - self._prepare_request(request) - - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - log.debug("Sending HTTP Request: %s %s", request.method, request.url) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = self._prepare_options(options) - try: - response = self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + self._prepare_request(request) - if remaining_retries > 0: - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - if remaining_retries > 0: - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, + response = None + try: + response = self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err - - log.debug( - 'HTTP Response: %s %s "%i %s" %s', - request.method, - request.url, - response.status_code, - response.reason_phrase, - response.headers, - ) + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + err.response.close() + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if remaining_retries > 0 and self._should_retry(err.response): - err.response.close() - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - response_headers=err.response.headers, - stream=stream, - stream_cls=stream_cls, - ) + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + err.response.read() - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - err.response.read() + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None + break + assert response is not None, "could not resolve response (should never happen)" return self._process_response( cast_to=cast_to, options=options, @@ -1028,37 +1042,20 @@ def _request( retries_taken=retries_taken, ) - def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - *, - retries_taken: int, - response_headers: httpx.Headers | None, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken + def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken if remaining_retries == 1: log.debug("1 retry left") else: log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers) + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) log.info("Retrying request to %s in %f seconds", options.url, timeout) - # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a - # different thread if necessary. time.sleep(timeout) - return self._request( - options=options, - cast_to=cast_to, - retries_taken=retries_taken + 1, - stream=stream, - stream_cls=stream_cls, - ) - def _process_response( self, *, @@ -1402,7 +1399,6 @@ async def request( options: FinalRequestOptions, *, stream: Literal[False] = False, - remaining_retries: Optional[int] = None, ) -> ResponseT: ... @overload @@ -1413,7 +1409,6 @@ async def request( *, stream: Literal[True], stream_cls: type[_AsyncStreamT], - remaining_retries: Optional[int] = None, ) -> _AsyncStreamT: ... @overload @@ -1424,7 +1419,6 @@ async def request( *, stream: bool, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, ) -> ResponseT | _AsyncStreamT: ... async def request( @@ -1434,116 +1428,111 @@ async def request( *, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, - ) -> ResponseT | _AsyncStreamT: - if remaining_retries is not None: - retries_taken = options.get_max_retries(self.max_retries) - remaining_retries - else: - retries_taken = 0 - - return await self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - retries_taken=retries_taken, - ) - - async def _request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - retries_taken: int, ) -> ResponseT | _AsyncStreamT: if self._platform is None: # `get_platform` can make blocking IO calls so we # execute it earlier while we are in an async context self._platform = await asyncify(get_platform)() + cast_to = self._maybe_override_cast_to(cast_to, options) + # create a copy of the options we were given so that if the # options are mutated later & we then retry, the retries are # given the original options input_options = model_copy(options) + if input_options.idempotency_key is None and input_options.method.lower() != "get": + # ensure the idempotency key is reused between requests + input_options.idempotency_key = self._idempotency_key() - cast_to = self._maybe_override_cast_to(cast_to, options) - options = await self._prepare_options(options) + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken - request = self._build_request(options, retries_taken=retries_taken) - await self._prepare_request(request) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = await self._prepare_options(options) - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + await self._prepare_request(request) - try: - response = await self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) - - if remaining_retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - if remaining_retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, + response = None + try: + response = await self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + await err.response.aclose() + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue - log.debug( - 'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase - ) + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + await err.response.aread() - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if remaining_retries > 0 and self._should_retry(err.response): - await err.response.aclose() - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - response_headers=err.response.headers, - stream=stream, - stream_cls=stream_cls, - ) + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - await err.response.aread() - - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None + break + assert response is not None, "could not resolve response (should never happen)" return await self._process_response( cast_to=cast_to, options=options, @@ -1553,35 +1542,20 @@ async def _request( retries_taken=retries_taken, ) - async def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - *, - retries_taken: int, - response_headers: httpx.Headers | None, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - ) -> ResponseT | _AsyncStreamT: - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken + async def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken if remaining_retries == 1: log.debug("1 retry left") else: log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers) + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) log.info("Retrying request to %s in %f seconds", options.url, timeout) await anyio.sleep(timeout) - return await self._request( - options=options, - cast_to=cast_to, - retries_taken=retries_taken + 1, - stream=stream, - stream_cls=stream_cls, - ) - async def _process_response( self, *, diff --git a/src/asktable/_client.py b/src/asktable/_client.py index 3e986563..bd58763f 100644 --- a/src/asktable/_client.py +++ b/src/asktable/_client.py @@ -19,10 +19,7 @@ ProxiesTypes, RequestOptions, ) -from ._utils import ( - is_given, - get_async_library, -) +from ._utils import is_given, get_async_library from ._version import __version__ from .resources import ( auth, diff --git a/src/asktable/_models.py b/src/asktable/_models.py index 34935716..798956f1 100644 --- a/src/asktable/_models.py +++ b/src/asktable/_models.py @@ -19,7 +19,6 @@ ) import pydantic -import pydantic.generics from pydantic.fields import FieldInfo from ._types import ( @@ -627,8 +626,8 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, # Note: if one variant defines an alias then they all should discriminator_alias = field_info.alias - if field_info.annotation and is_literal_type(field_info.annotation): - for entry in get_args(field_info.annotation): + if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): + for entry in get_args(annotation): if isinstance(entry, str): mapping[entry] = variant diff --git a/src/asktable/_response.py b/src/asktable/_response.py index e7528b55..0c91a6f9 100644 --- a/src/asktable/_response.py +++ b/src/asktable/_response.py @@ -233,7 +233,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: # split is required to handle cases where additional information is included # in the response, e.g. application/json; charset=utf-8 content_type, *_ = response.headers.get("content-type", "*").split(";") - if content_type != "application/json": + if not content_type.endswith("json"): if is_basemodel(cast_to): try: data = response.json() diff --git a/src/asktable/_utils/_proxy.py b/src/asktable/_utils/_proxy.py index ffd883e9..0f239a33 100644 --- a/src/asktable/_utils/_proxy.py +++ b/src/asktable/_utils/_proxy.py @@ -46,7 +46,10 @@ def __dir__(self) -> Iterable[str]: @property # type: ignore @override def __class__(self) -> type: # pyright: ignore - proxied = self.__get_proxied__() + try: + proxied = self.__get_proxied__() + except Exception: + return type(self) if issubclass(type(proxied), LazyProxy): return type(proxied) return proxied.__class__ diff --git a/src/asktable/_utils/_resources_proxy.py b/src/asktable/_utils/_resources_proxy.py new file mode 100644 index 00000000..d9107701 --- /dev/null +++ b/src/asktable/_utils/_resources_proxy.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import Any +from typing_extensions import override + +from ._proxy import LazyProxy + + +class ResourcesProxy(LazyProxy[Any]): + """A proxy for the `asktable.resources` module. + + This is used so that we can lazily import `asktable.resources` only when + needed *and* so that users can just import `asktable` and reference `asktable.resources` + """ + + @override + def __load__(self) -> Any: + import importlib + + mod = importlib.import_module("asktable.resources") + return mod + + +resources = ResourcesProxy().__as_proxied__() diff --git a/src/asktable/_utils/_transform.py b/src/asktable/_utils/_transform.py index 3ec62081..b0cc20a7 100644 --- a/src/asktable/_utils/_transform.py +++ b/src/asktable/_utils/_transform.py @@ -5,13 +5,15 @@ import pathlib from typing import Any, Mapping, TypeVar, cast from datetime import date, datetime -from typing_extensions import Literal, get_args, override, get_type_hints +from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints import anyio import pydantic from ._utils import ( is_list, + is_given, + lru_cache, is_mapping, is_iterable, ) @@ -108,6 +110,7 @@ class Params(TypedDict, total=False): return cast(_T, transformed) +@lru_cache(maxsize=8096) def _get_annotated_type(type_: type) -> type | None: """If the given type is an `Annotated` type then it is returned, if not `None` is returned. @@ -258,6 +261,11 @@ def _transform_typeddict( result: dict[str, object] = {} annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): + if not is_given(value): + # we don't need to include `NotGiven` values here as they'll + # be stripped out before the request is sent anyway + continue + type_ = annotations.get(key) if type_ is None: # we do not have a type annotation for this field, leave it as is @@ -415,6 +423,11 @@ async def _async_transform_typeddict( result: dict[str, object] = {} annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): + if not is_given(value): + # we don't need to include `NotGiven` values here as they'll + # be stripped out before the request is sent anyway + continue + type_ = annotations.get(key) if type_ is None: # we do not have a type annotation for this field, leave it as is @@ -422,3 +435,13 @@ async def _async_transform_typeddict( else: result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_) return result + + +@lru_cache(maxsize=8096) +def get_type_hints( + obj: Any, + globalns: dict[str, Any] | None = None, + localns: Mapping[str, Any] | None = None, + include_extras: bool = False, +) -> dict[str, Any]: + return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras) diff --git a/src/asktable/_utils/_typing.py b/src/asktable/_utils/_typing.py index 278749b1..1bac9542 100644 --- a/src/asktable/_utils/_typing.py +++ b/src/asktable/_utils/_typing.py @@ -13,6 +13,7 @@ get_origin, ) +from ._utils import lru_cache from .._types import InheritsGeneric from .._compat import is_union as _is_union @@ -66,6 +67,7 @@ def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]: # Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] +@lru_cache(maxsize=8096) def strip_annotated_type(typ: type) -> type: if is_required_type(typ) or is_annotated_type(typ): return strip_annotated_type(cast(type, get_args(typ)[0])) @@ -108,7 +110,7 @@ class MyResponse(Foo[_T]): ``` """ cls = cast(object, get_origin(typ) or typ) - if cls in generic_bases: + if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains] # we're given the class directly return extract_type_arg(typ, index) diff --git a/src/asktable/_utils/_utils.py b/src/asktable/_utils/_utils.py index e5811bba..ea3cf3f2 100644 --- a/src/asktable/_utils/_utils.py +++ b/src/asktable/_utils/_utils.py @@ -72,8 +72,16 @@ def _extract_items( from .._files import assert_is_file_content # We have exhausted the path, return the entry we found. - assert_is_file_content(obj, key=flattened_key) assert flattened_key is not None + + if is_list(obj): + files: list[tuple[str, FileTypes]] = [] + for entry in obj: + assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "") + files.append((flattened_key + "[]", cast(FileTypes, entry))) + return files + + assert_is_file_content(obj, key=flattened_key) return [(flattened_key, cast(FileTypes, obj))] index += 1 diff --git a/src/asktable/_version.py b/src/asktable/_version.py index cf688864..d445532a 100644 --- a/src/asktable/_version.py +++ b/src/asktable/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "asktable" -__version__ = "5.3.1" # x-release-please-version +__version__ = "5.4.0" # x-release-please-version diff --git a/src/asktable/resources/answers.py b/src/asktable/resources/answers.py index 155a89bd..8dbd2955 100644 --- a/src/asktable/resources/answers.py +++ b/src/asktable/resources/answers.py @@ -8,10 +8,7 @@ from ..types import answer_list_params, answer_create_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/asktable/resources/auth.py b/src/asktable/resources/auth.py index b8ed671d..065e06f1 100644 --- a/src/asktable/resources/auth.py +++ b/src/asktable/resources/auth.py @@ -9,10 +9,7 @@ from ..types import auth_create_token_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/asktable/resources/bots.py b/src/asktable/resources/bots.py index 1cfc9725..13932733 100644 --- a/src/asktable/resources/bots.py +++ b/src/asktable/resources/bots.py @@ -8,10 +8,7 @@ from ..types import bot_list_params, bot_create_params, bot_invite_params, bot_update_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/asktable/resources/business_glossary.py b/src/asktable/resources/business_glossary.py index 18700d43..2eed15db 100644 --- a/src/asktable/resources/business_glossary.py +++ b/src/asktable/resources/business_glossary.py @@ -6,16 +6,9 @@ import httpx -from ..types import ( - business_glossary_list_params, - business_glossary_create_params, - business_glossary_update_params, -) +from ..types import business_glossary_list_params, business_glossary_create_params, business_glossary_update_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -122,6 +115,7 @@ def update( self, entry_id: str, *, + active: Optional[bool] | NotGiven = NOT_GIVEN, aliases: Optional[List[str]] | NotGiven = NOT_GIVEN, definition: Optional[str] | NotGiven = NOT_GIVEN, payload: Optional[object] | NotGiven = NOT_GIVEN, @@ -137,6 +131,8 @@ def update( 更新业务术语 Args: + active: 业务术语是否生效 + aliases: 业务术语同义词 definition: 业务术语定义 @@ -159,6 +155,7 @@ def update( f"/v1/business-glossary/{entry_id}", body=maybe_transform( { + "active": active, "aliases": aliases, "definition": definition, "payload": payload, @@ -346,6 +343,7 @@ async def update( self, entry_id: str, *, + active: Optional[bool] | NotGiven = NOT_GIVEN, aliases: Optional[List[str]] | NotGiven = NOT_GIVEN, definition: Optional[str] | NotGiven = NOT_GIVEN, payload: Optional[object] | NotGiven = NOT_GIVEN, @@ -361,6 +359,8 @@ async def update( 更新业务术语 Args: + active: 业务术语是否生效 + aliases: 业务术语同义词 definition: 业务术语定义 @@ -383,6 +383,7 @@ async def update( f"/v1/business-glossary/{entry_id}", body=await async_maybe_transform( { + "active": active, "aliases": aliases, "definition": definition, "payload": payload, diff --git a/src/asktable/resources/chats/chats.py b/src/asktable/resources/chats/chats.py index 123f7d69..fd159838 100644 --- a/src/asktable/resources/chats/chats.py +++ b/src/asktable/resources/chats/chats.py @@ -8,10 +8,7 @@ from ...types import chat_list_params, chat_create_params from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from .messages import ( MessagesResource, AsyncMessagesResource, diff --git a/src/asktable/resources/chats/messages.py b/src/asktable/resources/chats/messages.py index 8ad81c31..7c3ca18a 100644 --- a/src/asktable/resources/chats/messages.py +++ b/src/asktable/resources/chats/messages.py @@ -7,10 +7,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/asktable/resources/datasources/datasources.py b/src/asktable/resources/datasources/datasources.py index a2b1b03e..239b7943 100644 --- a/src/asktable/resources/datasources/datasources.py +++ b/src/asktable/resources/datasources/datasources.py @@ -31,12 +31,7 @@ AsyncIndexesResourceWithStreamingResponse, ) from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes -from ..._utils import ( - extract_files, - maybe_transform, - deepcopy_minimal, - async_maybe_transform, -) +from ..._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -113,6 +108,12 @@ def create( "adbmysql", "adbpostgres", "xugu", + "doris", + "greenplum", + "selectdb", + "databend", + "sqlserver", + "mogdb", ], access_config: Optional[datasource_create_params.AccessConfig] | NotGiven = NOT_GIVEN, name: Optional[str] | NotGiven = NOT_GIVEN, @@ -213,6 +214,12 @@ def update( "adbmysql", "adbpostgres", "xugu", + "doris", + "greenplum", + "selectdb", + "databend", + "sqlserver", + "mogdb", ] ] | NotGiven = NOT_GIVEN, @@ -488,7 +495,7 @@ def update_field( schema_name: str, table_name: str, identifiable_type: Optional[ - Literal["plain", "person_name", "email", "ssn", "id", "phone", "address", "company"] + Literal["plain", "person_name", "email", "ssn", "id", "phone", "address", "company", "bank_card"] ] | NotGiven = NOT_GIVEN, visibility: Optional[bool] | NotGiven = NOT_GIVEN, @@ -595,6 +602,12 @@ async def create( "adbmysql", "adbpostgres", "xugu", + "doris", + "greenplum", + "selectdb", + "databend", + "sqlserver", + "mogdb", ], access_config: Optional[datasource_create_params.AccessConfig] | NotGiven = NOT_GIVEN, name: Optional[str] | NotGiven = NOT_GIVEN, @@ -695,6 +708,12 @@ async def update( "adbmysql", "adbpostgres", "xugu", + "doris", + "greenplum", + "selectdb", + "databend", + "sqlserver", + "mogdb", ] ] | NotGiven = NOT_GIVEN, @@ -970,7 +989,7 @@ async def update_field( schema_name: str, table_name: str, identifiable_type: Optional[ - Literal["plain", "person_name", "email", "ssn", "id", "phone", "address", "company"] + Literal["plain", "person_name", "email", "ssn", "id", "phone", "address", "company", "bank_card"] ] | NotGiven = NOT_GIVEN, visibility: Optional[bool] | NotGiven = NOT_GIVEN, diff --git a/src/asktable/resources/datasources/indexes.py b/src/asktable/resources/datasources/indexes.py index a4df4476..3ffd6f4a 100644 --- a/src/asktable/resources/datasources/indexes.py +++ b/src/asktable/resources/datasources/indexes.py @@ -5,10 +5,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/asktable/resources/datasources/meta.py b/src/asktable/resources/datasources/meta.py index ab219d17..431f1fba 100644 --- a/src/asktable/resources/datasources/meta.py +++ b/src/asktable/resources/datasources/meta.py @@ -7,10 +7,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/asktable/resources/datasources/upload_params.py b/src/asktable/resources/datasources/upload_params.py index 54fd1d93..01578571 100644 --- a/src/asktable/resources/datasources/upload_params.py +++ b/src/asktable/resources/datasources/upload_params.py @@ -7,10 +7,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/asktable/resources/extapis/extapis.py b/src/asktable/resources/extapis/extapis.py index 9c392981..080c9361 100644 --- a/src/asktable/resources/extapis/extapis.py +++ b/src/asktable/resources/extapis/extapis.py @@ -16,10 +16,7 @@ ) from ...types import extapi_list_params, extapi_create_params, extapi_update_params from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/asktable/resources/extapis/routes.py b/src/asktable/resources/extapis/routes.py index 2551affa..2f168725 100644 --- a/src/asktable/resources/extapis/routes.py +++ b/src/asktable/resources/extapis/routes.py @@ -9,10 +9,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/asktable/resources/integration.py b/src/asktable/resources/integration.py index f4384669..e82d2672 100644 --- a/src/asktable/resources/integration.py +++ b/src/asktable/resources/integration.py @@ -8,10 +8,7 @@ from ..types import integration_excel_csv_ask_params, integration_create_excel_ds_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/asktable/resources/policies.py b/src/asktable/resources/policies.py index 7452080f..fc27f2c4 100644 --- a/src/asktable/resources/policies.py +++ b/src/asktable/resources/policies.py @@ -9,10 +9,7 @@ from ..types import policy_list_params, policy_create_params, policy_update_params from .._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -67,7 +64,7 @@ def create( Args: dataset_config: 数据集配置 - name: 名称,小写英文字母,数字和下划线组合,不超过 64 个字符 + name: 名称 permission: 权限 @@ -148,7 +145,7 @@ def update( Args: dataset_config: 数据集配置 - name: 名称,小写英文字母,数字和下划线组合,不超过 64 个字符 + name: 名称 permission: 权限 @@ -307,7 +304,7 @@ async def create( Args: dataset_config: 数据集配置 - name: 名称,小写英文字母,数字和下划线组合,不超过 64 个字符 + name: 名称 permission: 权限 @@ -388,7 +385,7 @@ async def update( Args: dataset_config: 数据集配置 - name: 名称,小写英文字母,数字和下划线组合,不超过 64 个字符 + name: 名称 permission: 权限 diff --git a/src/asktable/resources/polish.py b/src/asktable/resources/polish.py index 3343078e..23df7f15 100644 --- a/src/asktable/resources/polish.py +++ b/src/asktable/resources/polish.py @@ -8,10 +8,7 @@ from ..types import polish_create_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/asktable/resources/preferences.py b/src/asktable/resources/preferences.py index 886db40e..a96452be 100644 --- a/src/asktable/resources/preferences.py +++ b/src/asktable/resources/preferences.py @@ -8,10 +8,7 @@ from ..types import preference_create_params, preference_update_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/asktable/resources/project.py b/src/asktable/resources/project.py index 3208457e..5624ab32 100644 --- a/src/asktable/resources/project.py +++ b/src/asktable/resources/project.py @@ -8,10 +8,7 @@ from ..types import project_update_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/asktable/resources/roles.py b/src/asktable/resources/roles.py index 6d64fcde..c05f9a91 100644 --- a/src/asktable/resources/roles.py +++ b/src/asktable/resources/roles.py @@ -8,10 +8,7 @@ from ..types import role_list_params, role_create_params, role_update_params, role_get_variables_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -64,7 +61,7 @@ def create( 创建一个新的角色 Args: - name: 名称,小写英文字母,数字和下划线组合,不超过 64 个字符 + name: 名称 policy_ids: 策略列表。注意:如果为空或者不传则不绑定策略 @@ -141,7 +138,7 @@ def update( 更新某个角色 Args: - name: 名称,小写英文字母,数字和下划线组合,不超过 64 个字符 + name: 名称 policy_ids: 策略列表。注意:如果为空或者不传则不绑定策略 @@ -377,7 +374,7 @@ async def create( 创建一个新的角色 Args: - name: 名称,小写英文字母,数字和下划线组合,不超过 64 个字符 + name: 名称 policy_ids: 策略列表。注意:如果为空或者不传则不绑定策略 @@ -454,7 +451,7 @@ async def update( 更新某个角色 Args: - name: 名称,小写英文字母,数字和下划线组合,不超过 64 个字符 + name: 名称 policy_ids: 策略列表。注意:如果为空或者不传则不绑定策略 diff --git a/src/asktable/resources/scores.py b/src/asktable/resources/scores.py index cf0b5112..343a17e8 100644 --- a/src/asktable/resources/scores.py +++ b/src/asktable/resources/scores.py @@ -6,10 +6,7 @@ from ..types import score_create_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/asktable/resources/securetunnels.py b/src/asktable/resources/securetunnels.py index 2614f8e1..13476a28 100644 --- a/src/asktable/resources/securetunnels.py +++ b/src/asktable/resources/securetunnels.py @@ -13,10 +13,7 @@ securetunnel_list_links_params, ) from .._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/asktable/resources/sqls.py b/src/asktable/resources/sqls.py index 367f98b0..fc871c84 100644 --- a/src/asktable/resources/sqls.py +++ b/src/asktable/resources/sqls.py @@ -8,10 +8,7 @@ from ..types import sql_list_params, sql_create_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -52,6 +49,7 @@ def create( *, datasource_id: str, question: str, + parameterize: bool | NotGiven = NOT_GIVEN, role_id: Optional[str] | NotGiven = NOT_GIVEN, role_variables: Optional[object] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -69,6 +67,8 @@ def create( question: 查询语句 + parameterize: 是否将参数分开传递 + role_id: 角色 ID,将扮演这个角色来执行对话,用于权限控制。若无,则跳过鉴权,即可查询所有 数据 @@ -88,6 +88,7 @@ def create( { "datasource_id": datasource_id, "question": question, + "parameterize": parameterize, "role_id": role_id, "role_variables": role_variables, }, @@ -176,6 +177,7 @@ async def create( *, datasource_id: str, question: str, + parameterize: bool | NotGiven = NOT_GIVEN, role_id: Optional[str] | NotGiven = NOT_GIVEN, role_variables: Optional[object] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -193,6 +195,8 @@ async def create( question: 查询语句 + parameterize: 是否将参数分开传递 + role_id: 角色 ID,将扮演这个角色来执行对话,用于权限控制。若无,则跳过鉴权,即可查询所有 数据 @@ -212,6 +216,7 @@ async def create( { "datasource_id": datasource_id, "question": question, + "parameterize": parameterize, "role_id": role_id, "role_variables": role_variables, }, diff --git a/src/asktable/resources/sys/projects/api_keys.py b/src/asktable/resources/sys/projects/api_keys.py index 11d84397..4b17a377 100644 --- a/src/asktable/resources/sys/projects/api_keys.py +++ b/src/asktable/resources/sys/projects/api_keys.py @@ -8,10 +8,7 @@ import httpx from ...._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ...._utils import ( - maybe_transform, - async_maybe_transform, -) +from ...._utils import maybe_transform, async_maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import ( diff --git a/src/asktable/resources/sys/projects/projects.py b/src/asktable/resources/sys/projects/projects.py index 5d91c1ed..eb8edeb3 100644 --- a/src/asktable/resources/sys/projects/projects.py +++ b/src/asktable/resources/sys/projects/projects.py @@ -15,10 +15,7 @@ AsyncAPIKeysResourceWithStreamingResponse, ) from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ...._utils import ( - maybe_transform, - async_maybe_transform, -) +from ...._utils import maybe_transform, async_maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import ( diff --git a/src/asktable/resources/trainings.py b/src/asktable/resources/trainings.py index 0a7f0922..6812639c 100644 --- a/src/asktable/resources/trainings.py +++ b/src/asktable/resources/trainings.py @@ -8,10 +8,7 @@ from ..types import training_list_params, training_create_params, training_delete_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/asktable/types/answer_response.py b/src/asktable/types/answer_response.py index 3bc03be2..04112480 100644 --- a/src/asktable/types/answer_response.py +++ b/src/asktable/types/answer_response.py @@ -5,7 +5,7 @@ from .._models import BaseModel -__all__ = ["AnswerResponse", "Answer", "AnswerAttachment", "Request"] +__all__ = ["AnswerResponse", "Answer", "AnswerAttachment", "Request", "Timing"] class AnswerAttachment(BaseModel): @@ -44,6 +44,14 @@ class Request(BaseModel): """是否同时将数据,作为 json 格式的附件一起返回""" +class Timing(BaseModel): + accessor_duration: Optional[float] = None + + llm_duration: Optional[float] = None + + total_duration: Optional[float] = None + + class AnswerResponse(BaseModel): id: str @@ -63,4 +71,6 @@ class AnswerResponse(BaseModel): err_msg: Optional[str] = None + timing: Optional[Timing] = None + trace_id: Optional[str] = None diff --git a/src/asktable/types/business_glossary_create_params.py b/src/asktable/types/business_glossary_create_params.py index 08220cef..e9f3d42d 100644 --- a/src/asktable/types/business_glossary_create_params.py +++ b/src/asktable/types/business_glossary_create_params.py @@ -19,6 +19,9 @@ class Body(TypedDict, total=False): term: Required[str] """业务术语""" + active: bool + """业务术语是否生效""" + aliases: Optional[List[str]] """业务术语同义词""" diff --git a/src/asktable/types/business_glossary_update_params.py b/src/asktable/types/business_glossary_update_params.py index ea78c5db..89bad224 100644 --- a/src/asktable/types/business_glossary_update_params.py +++ b/src/asktable/types/business_glossary_update_params.py @@ -9,6 +9,9 @@ class BusinessGlossaryUpdateParams(TypedDict, total=False): + active: Optional[bool] + """业务术语是否生效""" + aliases: Optional[List[str]] """业务术语同义词""" diff --git a/src/asktable/types/datasource.py b/src/asktable/types/datasource.py index 8cfe326c..70a1ae18 100644 --- a/src/asktable/types/datasource.py +++ b/src/asktable/types/datasource.py @@ -32,6 +32,12 @@ class Datasource(BaseModel): "adbmysql", "adbpostgres", "xugu", + "doris", + "greenplum", + "selectdb", + "databend", + "sqlserver", + "mogdb", ] """数据源引擎""" diff --git a/src/asktable/types/datasource_create_params.py b/src/asktable/types/datasource_create_params.py index 8ddc93cb..afd362b0 100644 --- a/src/asktable/types/datasource_create_params.py +++ b/src/asktable/types/datasource_create_params.py @@ -31,6 +31,12 @@ class DatasourceCreateParams(TypedDict, total=False): "adbmysql", "adbpostgres", "xugu", + "doris", + "greenplum", + "selectdb", + "databend", + "sqlserver", + "mogdb", ] ] """数据源引擎""" @@ -70,7 +76,7 @@ class AccessConfigAccessConfigConnectionCreate(TypedDict, total=False): class AccessConfigAccessConfigFileCreate(TypedDict, total=False): files: Required[List[str]] - """数据源文件列表""" + """数据源文件 URL 列表, 创建时可以传入 URL""" AccessConfig: TypeAlias = Union[AccessConfigAccessConfigConnectionCreate, AccessConfigAccessConfigFileCreate] diff --git a/src/asktable/types/datasource_retrieve_response.py b/src/asktable/types/datasource_retrieve_response.py index 50a24de8..d244225c 100644 --- a/src/asktable/types/datasource_retrieve_response.py +++ b/src/asktable/types/datasource_retrieve_response.py @@ -11,6 +11,7 @@ "AccessConfig", "AccessConfigAccessConfigConnectionResponse", "AccessConfigAccessConfigFileResponse", + "AccessConfigAccessConfigFileResponseFile", ] @@ -46,9 +47,18 @@ class AccessConfigAccessConfigConnectionResponse(BaseModel): """数据库用户名""" +class AccessConfigAccessConfigFileResponseFile(BaseModel): + id: str + + filename: str + + custom_config: Optional[object] = None + """文件自定义配置""" + + class AccessConfigAccessConfigFileResponse(BaseModel): - files: List[str] - """数据源文件列表""" + files: List[AccessConfigAccessConfigFileResponseFile] + """数据源文件 ID 列表""" AccessConfig: TypeAlias = Union[AccessConfigAccessConfigConnectionResponse, AccessConfigAccessConfigFileResponse, None] @@ -77,6 +87,12 @@ class DatasourceRetrieveResponse(BaseModel): "adbmysql", "adbpostgres", "xugu", + "doris", + "greenplum", + "selectdb", + "databend", + "sqlserver", + "mogdb", ] """数据源引擎""" diff --git a/src/asktable/types/datasource_update_field_params.py b/src/asktable/types/datasource_update_field_params.py index f756bdf4..977caae5 100644 --- a/src/asktable/types/datasource_update_field_params.py +++ b/src/asktable/types/datasource_update_field_params.py @@ -15,7 +15,9 @@ class DatasourceUpdateFieldParams(TypedDict, total=False): table_name: Required[str] - identifiable_type: Optional[Literal["plain", "person_name", "email", "ssn", "id", "phone", "address", "company"]] + identifiable_type: Optional[ + Literal["plain", "person_name", "email", "ssn", "id", "phone", "address", "company", "bank_card"] + ] """identifiable type""" visibility: Optional[bool] diff --git a/src/asktable/types/datasource_update_params.py b/src/asktable/types/datasource_update_params.py index 4abe181d..ff22daff 100644 --- a/src/asktable/types/datasource_update_params.py +++ b/src/asktable/types/datasource_update_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import List, Union, Optional +from typing import Union, Iterable, Optional from typing_extensions import Literal, Required, TypeAlias, TypedDict __all__ = [ @@ -10,6 +10,7 @@ "AccessConfig", "AccessConfigAccessConfigConnectionUpdate", "AccessConfigAccessConfigFileUpdate", + "AccessConfigAccessConfigFileUpdateFile", ] @@ -37,6 +38,12 @@ class DatasourceUpdateParams(TypedDict, total=False): "adbmysql", "adbpostgres", "xugu", + "doris", + "greenplum", + "selectdb", + "databend", + "sqlserver", + "mogdb", ] ] """数据源引擎""" @@ -89,9 +96,18 @@ class AccessConfigAccessConfigConnectionUpdate(TypedDict, total=False): """数据库用户名""" +class AccessConfigAccessConfigFileUpdateFile(TypedDict, total=False): + id: Required[str] + + filename: Required[str] + + custom_config: Optional[object] + """文件自定义配置""" + + class AccessConfigAccessConfigFileUpdate(TypedDict, total=False): - files: Required[List[str]] - """数据源文件列表""" + files: Required[Iterable[AccessConfigAccessConfigFileUpdateFile]] + """数据源文件 ID 列表""" AccessConfig: TypeAlias = Union[AccessConfigAccessConfigConnectionUpdate, AccessConfigAccessConfigFileUpdate] diff --git a/src/asktable/types/datasources/meta_create_params.py b/src/asktable/types/datasources/meta_create_params.py index 4d452ff8..86783588 100644 --- a/src/asktable/types/datasources/meta_create_params.py +++ b/src/asktable/types/datasources/meta_create_params.py @@ -28,7 +28,7 @@ class MetaSchemasTablesFields(TypedDict, total=False): data_type: Optional[str] """field data type""" - identifiable_type: Literal["plain", "person_name", "email", "ssn", "id", "phone", "address", "company"] + identifiable_type: Literal["plain", "person_name", "email", "ssn", "id", "phone", "address", "company", "bank_card"] """identifiable type""" sample_data: Optional[str] diff --git a/src/asktable/types/datasources/meta_update_params.py b/src/asktable/types/datasources/meta_update_params.py index 862f0776..2a14b685 100644 --- a/src/asktable/types/datasources/meta_update_params.py +++ b/src/asktable/types/datasources/meta_update_params.py @@ -26,7 +26,7 @@ class MetaSchemasTablesFields(TypedDict, total=False): data_type: Optional[str] """field data type""" - identifiable_type: Literal["plain", "person_name", "email", "ssn", "id", "phone", "address", "company"] + identifiable_type: Literal["plain", "person_name", "email", "ssn", "id", "phone", "address", "company", "bank_card"] """identifiable type""" sample_data: Optional[str] diff --git a/src/asktable/types/entry.py b/src/asktable/types/entry.py index d493a3ef..a4140a5d 100644 --- a/src/asktable/types/entry.py +++ b/src/asktable/types/entry.py @@ -24,6 +24,9 @@ class Entry(BaseModel): term: str """业务术语""" + active: Optional[bool] = None + """业务术语是否生效""" + aliases: Optional[List[str]] = None """业务术语同义词""" diff --git a/src/asktable/types/entry_with_definition.py b/src/asktable/types/entry_with_definition.py index d7011a9e..9b76baff 100644 --- a/src/asktable/types/entry_with_definition.py +++ b/src/asktable/types/entry_with_definition.py @@ -27,6 +27,9 @@ class EntryWithDefinition(BaseModel): term: str """业务术语""" + active: Optional[bool] = None + """业务术语是否生效""" + aliases: Optional[List[str]] = None """业务术语同义词""" diff --git a/src/asktable/types/file_ask_response.py b/src/asktable/types/file_ask_response.py index a870ad8c..bbf7f554 100644 --- a/src/asktable/types/file_ask_response.py +++ b/src/asktable/types/file_ask_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .._models import BaseModel from .datasource import Datasource from .answer_response import AnswerResponse diff --git a/src/asktable/types/meta.py b/src/asktable/types/meta.py index 05cd5f20..f94d2037 100644 --- a/src/asktable/types/meta.py +++ b/src/asktable/types/meta.py @@ -35,7 +35,7 @@ class SchemasTablesFields(BaseModel): """field data type""" identifiable_type: Optional[ - Literal["plain", "person_name", "email", "ssn", "id", "phone", "address", "company"] + Literal["plain", "person_name", "email", "ssn", "id", "phone", "address", "company", "bank_card"] ] = None """identifiable type""" diff --git a/src/asktable/types/policy_create_params.py b/src/asktable/types/policy_create_params.py index 3c53d086..9630207c 100644 --- a/src/asktable/types/policy_create_params.py +++ b/src/asktable/types/policy_create_params.py @@ -13,7 +13,7 @@ class PolicyCreateParams(TypedDict, total=False): """数据集配置""" name: Required[str] - """名称,小写英文字母,数字和下划线组合,不超过 64 个字符""" + """名称""" permission: Required[Literal["allow", "deny"]] """权限""" @@ -87,4 +87,7 @@ class DatasetConfig(TypedDict, total=False): - 暂不支持对字段使用函数计算,比如不支持 "YEAR(public.user.created_at) = 2023" - 暂不支持多个过滤条件的组合,,比如不支持 "uid = {{user_id}} AND city_id = {{city_id}}" + - 支持中文 Unicode 编码范围:4E00-9FFF(查询是否支持参考 + :https://www.unicode.org/cgi-bin/GetUnihanData.pl, 编码范围参考 + :https://www.unicode.org/charts/PDF/U4E00.pdf) """ diff --git a/src/asktable/types/policy_update_params.py b/src/asktable/types/policy_update_params.py index dfe7e6e4..dc1675b2 100644 --- a/src/asktable/types/policy_update_params.py +++ b/src/asktable/types/policy_update_params.py @@ -13,7 +13,7 @@ class PolicyUpdateParams(TypedDict, total=False): """数据集配置""" name: Optional[str] - """名称,小写英文字母,数字和下划线组合,不超过 64 个字符""" + """名称""" permission: Optional[Literal["allow", "deny"]] """权限""" @@ -107,4 +107,7 @@ class DatasetConfig(TypedDict, total=False): - 暂不支持对字段使用函数计算,比如不支持 "YEAR(public.user.created_at) = 2023" - 暂不支持多个过滤条件的组合,,比如不支持 "uid = {{user_id}} AND city_id = {{city_id}}" + - 支持中文 Unicode 编码范围:4E00-9FFF(查询是否支持参考 + :https://www.unicode.org/cgi-bin/GetUnihanData.pl, 编码范围参考 + :https://www.unicode.org/charts/PDF/U4E00.pdf) """ diff --git a/src/asktable/types/polish_create_response.py b/src/asktable/types/polish_create_response.py index b92795cc..888e42c3 100644 --- a/src/asktable/types/polish_create_response.py +++ b/src/asktable/types/polish_create_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .._models import BaseModel __all__ = ["PolishCreateResponse"] diff --git a/src/asktable/types/query_response.py b/src/asktable/types/query_response.py index c8234056..f57289fb 100644 --- a/src/asktable/types/query_response.py +++ b/src/asktable/types/query_response.py @@ -5,13 +5,19 @@ from .._models import BaseModel -__all__ = ["QueryResponse", "Query", "Request"] +__all__ = ["QueryResponse", "Query", "Request", "Timing"] class Query(BaseModel): sql: str """SQL 语句""" + parameterized_sql: Optional[str] = None + """参数化后的 SQL 语句""" + + params: Optional[object] = None + """参数""" + class Request(BaseModel): datasource_id: str @@ -20,6 +26,9 @@ class Request(BaseModel): question: str """查询语句""" + parameterize: Optional[bool] = None + """是否将参数分开传递""" + role_id: Optional[str] = None """ 角色 ID,将扮演这个角色来执行对话,用于权限控制。若无,则跳过鉴权,即可查询所有 @@ -30,6 +39,12 @@ class Request(BaseModel): """在扮演这个角色时需要传递的变量值,用 Key-Value 形式传递""" +class Timing(BaseModel): + llm_duration: Optional[float] = None + + total_duration: Optional[float] = None + + class QueryResponse(BaseModel): id: str @@ -49,4 +64,6 @@ class QueryResponse(BaseModel): err_msg: Optional[str] = None + timing: Optional[Timing] = None + trace_id: Optional[str] = None diff --git a/src/asktable/types/role.py b/src/asktable/types/role.py index 19e7fad9..5683f5c5 100644 --- a/src/asktable/types/role.py +++ b/src/asktable/types/role.py @@ -18,7 +18,6 @@ class Role(BaseModel): modified_at: datetime name: str - """名称,小写英文字母,数字和下划线组合,不超过 64 个字符""" project_id: str diff --git a/src/asktable/types/role_create_params.py b/src/asktable/types/role_create_params.py index 4b4dab67..4750a3e0 100644 --- a/src/asktable/types/role_create_params.py +++ b/src/asktable/types/role_create_params.py @@ -10,7 +10,7 @@ class RoleCreateParams(TypedDict, total=False): name: Required[str] - """名称,小写英文字母,数字和下划线组合,不超过 64 个字符""" + """名称""" policy_ids: Optional[List[str]] """策略列表。注意:如果为空或者不传则不绑定策略""" diff --git a/src/asktable/types/role_update_params.py b/src/asktable/types/role_update_params.py index 8495f380..6cb3bb99 100644 --- a/src/asktable/types/role_update_params.py +++ b/src/asktable/types/role_update_params.py @@ -10,7 +10,7 @@ class RoleUpdateParams(TypedDict, total=False): name: Optional[str] - """名称,小写英文字母,数字和下划线组合,不超过 64 个字符""" + """名称""" policy_ids: Optional[List[str]] """策略列表。注意:如果为空或者不传则不绑定策略""" diff --git a/src/asktable/types/shared/policy.py b/src/asktable/types/shared/policy.py index d87698e2..0fd1b6db 100644 --- a/src/asktable/types/shared/policy.py +++ b/src/asktable/types/shared/policy.py @@ -50,7 +50,6 @@ class Policy(BaseModel): modified_at: datetime name: str - """名称,小写英文字母,数字和下划线组合,不超过 64 个字符""" permission: Literal["allow", "deny"] """权限""" diff --git a/src/asktable/types/sql_create_params.py b/src/asktable/types/sql_create_params.py index 52d3cfb1..515dbd47 100644 --- a/src/asktable/types/sql_create_params.py +++ b/src/asktable/types/sql_create_params.py @@ -15,6 +15,9 @@ class SqlCreateParams(TypedDict, total=False): question: Required[str] """查询语句""" + parameterize: bool + """是否将参数分开传递""" + role_id: Optional[str] """ 角色 ID,将扮演这个角色来执行对话,用于权限控制。若无,则跳过鉴权,即可查询所有 diff --git a/src/asktable/types/sys/model_group.py b/src/asktable/types/sys/model_group.py index 8d700122..d37d6270 100644 --- a/src/asktable/types/sys/model_group.py +++ b/src/asktable/types/sys/model_group.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from ..._models import BaseModel __all__ = ["ModelGroup"] diff --git a/src/asktable/types/training_create_params.py b/src/asktable/types/training_create_params.py index 9e7f2a70..f8dec4df 100644 --- a/src/asktable/types/training_create_params.py +++ b/src/asktable/types/training_create_params.py @@ -22,6 +22,9 @@ class Body(TypedDict, total=False): sql: Required[str] """用户问题对应的 SQL""" + active: bool + """是否启用""" + chat_id: Optional[str] """聊天 ID""" diff --git a/src/asktable/types/training_create_response.py b/src/asktable/types/training_create_response.py index 97788641..278021f5 100644 --- a/src/asktable/types/training_create_response.py +++ b/src/asktable/types/training_create_response.py @@ -14,10 +14,14 @@ class TrainingCreateResponseItem(BaseModel): """训练数据 ID""" created_at: datetime + """创建时间""" datasource_id: str """数据源 ID""" + modified_at: datetime + """更新时间""" + project_id: str """项目 ID""" @@ -30,6 +34,9 @@ class TrainingCreateResponseItem(BaseModel): sql: str """用户问题对应的 SQL""" + active: Optional[bool] = None + """是否启用""" + chat_id: Optional[str] = None """聊天 ID""" diff --git a/src/asktable/types/training_list_response.py b/src/asktable/types/training_list_response.py index 54e63cb5..bc22d53a 100644 --- a/src/asktable/types/training_list_response.py +++ b/src/asktable/types/training_list_response.py @@ -14,10 +14,14 @@ class TrainingListResponse(BaseModel): """训练数据 ID""" created_at: datetime + """创建时间""" datasource_id: str """数据源 ID""" + modified_at: datetime + """更新时间""" + project_id: str """项目 ID""" @@ -30,6 +34,9 @@ class TrainingListResponse(BaseModel): sql: str """用户问题对应的 SQL""" + active: Optional[bool] = None + """是否启用""" + chat_id: Optional[str] = None """聊天 ID""" diff --git a/tests/api_resources/test_business_glossary.py b/tests/api_resources/test_business_glossary.py index ede95e7a..744c497e 100644 --- a/tests/api_resources/test_business_glossary.py +++ b/tests/api_resources/test_business_glossary.py @@ -117,6 +117,7 @@ def test_method_update(self, client: Asktable) -> None: def test_method_update_with_all_params(self, client: Asktable) -> None: business_glossary = client.business_glossary.update( entry_id="entry_id", + active=True, aliases=["string"], definition="definition", payload={}, @@ -326,6 +327,7 @@ async def test_method_update(self, async_client: AsyncAsktable) -> None: async def test_method_update_with_all_params(self, async_client: AsyncAsktable) -> None: business_glossary = await async_client.business_glossary.update( entry_id="entry_id", + active=True, aliases=["string"], definition="definition", payload={}, diff --git a/tests/api_resources/test_policies.py b/tests/api_resources/test_policies.py index 1c03eafa..0323ef89 100644 --- a/tests/api_resources/test_policies.py +++ b/tests/api_resources/test_policies.py @@ -22,7 +22,7 @@ class TestPolicies: def test_method_create(self, client: Asktable) -> None: policy = client.policies.create( dataset_config={"datasource_ids": ["string"]}, - name="name", + name="policy_name", permission="allow", ) assert_matches_type(Policy, policy, path=["response"]) @@ -47,7 +47,7 @@ def test_method_create_with_all_params(self, client: Asktable) -> None: ], }, }, - name="name", + name="policy_name", permission="allow", ) assert_matches_type(Policy, policy, path=["response"]) @@ -56,7 +56,7 @@ def test_method_create_with_all_params(self, client: Asktable) -> None: def test_raw_response_create(self, client: Asktable) -> None: response = client.policies.with_raw_response.create( dataset_config={"datasource_ids": ["string"]}, - name="name", + name="policy_name", permission="allow", ) @@ -69,7 +69,7 @@ def test_raw_response_create(self, client: Asktable) -> None: def test_streaming_response_create(self, client: Asktable) -> None: with client.policies.with_streaming_response.create( dataset_config={"datasource_ids": ["string"]}, - name="name", + name="policy_name", permission="allow", ) as response: assert not response.is_closed @@ -183,7 +183,7 @@ def test_method_update_with_all_params(self, client: Asktable) -> None: ], }, }, - name="name", + name="policy_name", permission="allow", ) assert_matches_type(Policy, policy, path=["response"]) @@ -300,7 +300,7 @@ class TestAsyncPolicies: async def test_method_create(self, async_client: AsyncAsktable) -> None: policy = await async_client.policies.create( dataset_config={"datasource_ids": ["string"]}, - name="name", + name="policy_name", permission="allow", ) assert_matches_type(Policy, policy, path=["response"]) @@ -325,7 +325,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncAsktable) ], }, }, - name="name", + name="policy_name", permission="allow", ) assert_matches_type(Policy, policy, path=["response"]) @@ -334,7 +334,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncAsktable) async def test_raw_response_create(self, async_client: AsyncAsktable) -> None: response = await async_client.policies.with_raw_response.create( dataset_config={"datasource_ids": ["string"]}, - name="name", + name="policy_name", permission="allow", ) @@ -347,7 +347,7 @@ async def test_raw_response_create(self, async_client: AsyncAsktable) -> None: async def test_streaming_response_create(self, async_client: AsyncAsktable) -> None: async with async_client.policies.with_streaming_response.create( dataset_config={"datasource_ids": ["string"]}, - name="name", + name="policy_name", permission="allow", ) as response: assert not response.is_closed @@ -461,7 +461,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncAsktable) ], }, }, - name="name", + name="policy_name", permission="allow", ) assert_matches_type(Policy, policy, path=["response"]) diff --git a/tests/api_resources/test_roles.py b/tests/api_resources/test_roles.py index 8f83b821..37f39920 100644 --- a/tests/api_resources/test_roles.py +++ b/tests/api_resources/test_roles.py @@ -24,14 +24,14 @@ class TestRoles: @parametrize def test_method_create(self, client: Asktable) -> None: role = client.roles.create( - name="name", + name="role_name", ) assert_matches_type(Role, role, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: Asktable) -> None: role = client.roles.create( - name="name", + name="role_name", policy_ids=["policy_6uOnay1xDNsxLoHmCGf3", "policy_6uOnay1xDNsxLoHmCGf2"], ) assert_matches_type(Role, role, path=["response"]) @@ -39,7 +39,7 @@ def test_method_create_with_all_params(self, client: Asktable) -> None: @parametrize def test_raw_response_create(self, client: Asktable) -> None: response = client.roles.with_raw_response.create( - name="name", + name="role_name", ) assert response.is_closed is True @@ -50,7 +50,7 @@ def test_raw_response_create(self, client: Asktable) -> None: @parametrize def test_streaming_response_create(self, client: Asktable) -> None: with client.roles.with_streaming_response.create( - name="name", + name="role_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -109,7 +109,7 @@ def test_method_update(self, client: Asktable) -> None: def test_method_update_with_all_params(self, client: Asktable) -> None: role = client.roles.update( role_id="role_id", - name="name", + name="role_name", policy_ids=["policy_6uOnay1xDNsxLoHmCGf3", "policy_6uOnay1xDNsxLoHmCGf2"], ) assert_matches_type(Role, role, path=["response"]) @@ -310,14 +310,14 @@ class TestAsyncRoles: @parametrize async def test_method_create(self, async_client: AsyncAsktable) -> None: role = await async_client.roles.create( - name="name", + name="role_name", ) assert_matches_type(Role, role, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncAsktable) -> None: role = await async_client.roles.create( - name="name", + name="role_name", policy_ids=["policy_6uOnay1xDNsxLoHmCGf3", "policy_6uOnay1xDNsxLoHmCGf2"], ) assert_matches_type(Role, role, path=["response"]) @@ -325,7 +325,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncAsktable) @parametrize async def test_raw_response_create(self, async_client: AsyncAsktable) -> None: response = await async_client.roles.with_raw_response.create( - name="name", + name="role_name", ) assert response.is_closed is True @@ -336,7 +336,7 @@ async def test_raw_response_create(self, async_client: AsyncAsktable) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncAsktable) -> None: async with async_client.roles.with_streaming_response.create( - name="name", + name="role_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -395,7 +395,7 @@ async def test_method_update(self, async_client: AsyncAsktable) -> None: async def test_method_update_with_all_params(self, async_client: AsyncAsktable) -> None: role = await async_client.roles.update( role_id="role_id", - name="name", + name="role_name", policy_ids=["policy_6uOnay1xDNsxLoHmCGf3", "policy_6uOnay1xDNsxLoHmCGf2"], ) assert_matches_type(Role, role, path=["response"]) diff --git a/tests/api_resources/test_sqls.py b/tests/api_resources/test_sqls.py index 9ba2d05f..258ae9a0 100644 --- a/tests/api_resources/test_sqls.py +++ b/tests/api_resources/test_sqls.py @@ -31,6 +31,7 @@ def test_method_create_with_all_params(self, client: Asktable) -> None: sql = client.sqls.create( datasource_id="datasource_id", question="question", + parameterize=True, role_id="role_id", role_variables={}, ) @@ -113,6 +114,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncAsktable) sql = await async_client.sqls.create( datasource_id="datasource_id", question="question", + parameterize=True, role_id="role_id", role_variables={}, ) diff --git a/tests/conftest.py b/tests/conftest.py index 769bda69..fe426c1f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,7 @@ from asktable import Asktable, AsyncAsktable if TYPE_CHECKING: - from _pytest.fixtures import FixtureRequest + from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] pytest.register_assert_rewrite("tests.utils") diff --git a/tests/test_models.py b/tests/test_models.py index 89906cad..8184554c 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -492,12 +492,15 @@ class Model(BaseModel): resource_id: Optional[str] = None m = Model.construct() + assert m.resource_id is None assert "resource_id" not in m.model_fields_set m = Model.construct(resource_id=None) + assert m.resource_id is None assert "resource_id" in m.model_fields_set m = Model.construct(resource_id="foo") + assert m.resource_id == "foo" assert "resource_id" in m.model_fields_set @@ -832,7 +835,7 @@ class B(BaseModel): @pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") def test_type_alias_type() -> None: - Alias = TypeAliasType("Alias", str) + Alias = TypeAliasType("Alias", str) # pyright: ignore class Model(BaseModel): alias: Alias diff --git a/tests/test_transform.py b/tests/test_transform.py index a8a384a5..33ea35d8 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -8,7 +8,7 @@ import pytest -from asktable._types import Base64FileInput +from asktable._types import NOT_GIVEN, Base64FileInput from asktable._utils import ( PropertyInfo, transform as _transform, @@ -444,3 +444,10 @@ async def test_transform_skipping(use_async: bool) -> None: # iterables of ints are converted to a list data = iter([1, 2, 3]) assert await transform(data, Iterable[int], use_async) == [1, 2, 3] + + +@parametrize +@pytest.mark.asyncio +async def test_strips_notgiven(use_async: bool) -> None: + assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} + assert await transform({"foo_bar": NOT_GIVEN}, Foo1, use_async) == {} diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py index 14dbd6ab..c2d771cd 100644 --- a/tests/test_utils/test_proxy.py +++ b/tests/test_utils/test_proxy.py @@ -21,3 +21,14 @@ def test_recursive_proxy() -> None: assert dir(proxy) == [] assert type(proxy).__name__ == "RecursiveLazyProxy" assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy" + + +def test_isinstance_does_not_error() -> None: + class AlwaysErrorProxy(LazyProxy[Any]): + @override + def __load__(self) -> Any: + raise RuntimeError("Mocking missing dependency") + + proxy = AlwaysErrorProxy() + assert not isinstance(proxy, dict) + assert isinstance(proxy, LazyProxy)