From 88821c710de290b37a6dbc9a81ef0a1cc396db62 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 18 Apr 2025 22:17:20 +0000 Subject: [PATCH 01/17] chore(internal): version bump From 03cd93334de3db9beea06ae0f62bcb0ab61466a4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:35:59 +0000 Subject: [PATCH 02/17] chore(internal): version bump From 444cd038fc03a545fc2da0a6e5ab14ea84102408 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 19 Apr 2025 02:14:16 +0000 Subject: [PATCH 03/17] chore(internal): update models test --- tests/test_models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_models.py b/tests/test_models.py index fb9d99b1..c96609ce 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 From 4f39e46167b88a0c7198bdd8ad7d31bf118d485c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 20 Apr 2025 23:16:04 +0000 Subject: [PATCH 04/17] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/clusters.py | 6 ++++-- src/codex/types/projects/cluster_list_params.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index c1c522e8..f033bbda 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 36 -openapi_spec_hash: 6311021a3aba7ac56cc3b474762945c0 +openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d config_hash: adbedb6317fca6f566f54564cc341846 diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py index e35e8ae8..584cde18 100644 --- a/src/codex/resources/projects/clusters.py +++ b/src/codex/resources/projects/clusters.py @@ -50,7 +50,8 @@ def list( self, project_id: str, *, - eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]] | NotGiven = NOT_GIVEN, + eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]] + | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, @@ -164,7 +165,8 @@ def list( self, project_id: str, *, - eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]] | NotGiven = NOT_GIVEN, + eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]] + | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, diff --git a/src/codex/types/projects/cluster_list_params.py b/src/codex/types/projects/cluster_list_params.py index d1d818e3..fa84a6b0 100644 --- a/src/codex/types/projects/cluster_list_params.py +++ b/src/codex/types/projects/cluster_list_params.py @@ -9,7 +9,7 @@ class ClusterListParams(TypedDict, total=False): - eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful"]] + eval_issue_types: List[Literal["hallucination", "search_failure", "unhelpful", "difficult_query"]] limit: int From b038fbe3e90f3096b0913256db9e31ca52cd4001 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:41:55 +0000 Subject: [PATCH 05/17] feat(api): add project increment_queries and other recent endpoints --- .stats.yml | 4 +- api.md | 15 +- .../resources/organizations/organizations.py | 158 ++++++++ src/codex/resources/projects/entries.py | 289 +++++++++++++- src/codex/resources/projects/projects.py | 78 ++++ src/codex/types/__init__.py | 4 + .../organization_list_members_response.py | 17 + ...anization_retrieve_permissions_response.py | 8 + src/codex/types/projects/__init__.py | 2 + .../types/projects/entry_notify_sme_params.py | 21 + .../projects/entry_notify_sme_response.py | 14 + tests/api_resources/projects/test_entries.py | 366 +++++++++++++++++- tests/api_resources/test_organizations.py | 174 ++++++++- tests/api_resources/test_projects.py | 84 ++++ 14 files changed, 1227 insertions(+), 7 deletions(-) create mode 100644 src/codex/types/organization_list_members_response.py create mode 100644 src/codex/types/organization_retrieve_permissions_response.py create mode 100644 src/codex/types/projects/entry_notify_sme_params.py create mode 100644 src/codex/types/projects/entry_notify_sme_response.py diff --git a/.stats.yml b/.stats.yml index f033bbda..d78252d8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 36 +configured_endpoints: 42 openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d -config_hash: adbedb6317fca6f566f54564cc341846 +config_hash: 93ac12138700569dc57329400410c0fd diff --git a/api.md b/api.md index e9dd565d..5a314a2d 100644 --- a/api.md +++ b/api.md @@ -16,12 +16,18 @@ Methods: Types: ```python -from codex.types import OrganizationSchemaPublic +from codex.types import ( + OrganizationSchemaPublic, + OrganizationListMembersResponse, + OrganizationRetrievePermissionsResponse, +) ``` Methods: - client.organizations.retrieve(organization_id) -> OrganizationSchemaPublic +- client.organizations.list_members(organization_id) -> OrganizationListMembersResponse +- client.organizations.retrieve_permissions(organization_id) -> OrganizationRetrievePermissionsResponse ## Billing @@ -140,6 +146,7 @@ from codex.types import ( ProjectRetrieveResponse, ProjectListResponse, ProjectExportResponse, + ProjectIncrementQueriesResponse, ) ``` @@ -151,6 +158,7 @@ Methods: - client.projects.list(\*\*params) -> ProjectListResponse - client.projects.delete(project_id) -> None - client.projects.export(project_id) -> object +- client.projects.increment_queries(project_id) -> object ## AccessKeys @@ -179,7 +187,7 @@ Methods: Types: ```python -from codex.types.projects import Entry, EntryQueryResponse +from codex.types.projects import Entry, EntryNotifySmeResponse, EntryQueryResponse ``` Methods: @@ -188,7 +196,10 @@ Methods: - client.projects.entries.retrieve(entry_id, \*, project_id) -> Entry - client.projects.entries.update(entry_id, \*, project_id, \*\*params) -> Entry - client.projects.entries.delete(entry_id, \*, project_id) -> None +- client.projects.entries.notify_sme(entry_id, \*, project_id, \*\*params) -> EntryNotifySmeResponse +- client.projects.entries.publish_draft_answer(entry_id, \*, project_id) -> Entry - client.projects.entries.query(project_id, \*\*params) -> EntryQueryResponse +- client.projects.entries.unpublish_answer(entry_id, \*, project_id) -> Entry ## Clusters diff --git a/src/codex/resources/organizations/organizations.py b/src/codex/resources/organizations/organizations.py index 025ecba4..f1eb4d5e 100644 --- a/src/codex/resources/organizations/organizations.py +++ b/src/codex/resources/organizations/organizations.py @@ -23,6 +23,8 @@ AsyncBillingResourceWithStreamingResponse, ) from ...types.organization_schema_public import OrganizationSchemaPublic +from ...types.organization_list_members_response import OrganizationListMembersResponse +from ...types.organization_retrieve_permissions_response import OrganizationRetrievePermissionsResponse __all__ = ["OrganizationsResource", "AsyncOrganizationsResource"] @@ -84,6 +86,72 @@ def retrieve( cast_to=OrganizationSchemaPublic, ) + def list_members( + self, + organization_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> OrganizationListMembersResponse: + """ + Get a list of organization members with their names and emails. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return self._get( + f"/api/organizations/{organization_id}/members", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationListMembersResponse, + ) + + def retrieve_permissions( + self, + organization_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> OrganizationRetrievePermissionsResponse: + """ + Get the user's permissions for this organization. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return self._get( + f"/api/organizations/{organization_id}/permissions", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationRetrievePermissionsResponse, + ) + class AsyncOrganizationsResource(AsyncAPIResource): @cached_property @@ -142,6 +210,72 @@ async def retrieve( cast_to=OrganizationSchemaPublic, ) + async def list_members( + self, + organization_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> OrganizationListMembersResponse: + """ + Get a list of organization members with their names and emails. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return await self._get( + f"/api/organizations/{organization_id}/members", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationListMembersResponse, + ) + + async def retrieve_permissions( + self, + organization_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> OrganizationRetrievePermissionsResponse: + """ + Get the user's permissions for this organization. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not organization_id: + raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") + return await self._get( + f"/api/organizations/{organization_id}/permissions", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationRetrievePermissionsResponse, + ) + class OrganizationsResourceWithRawResponse: def __init__(self, organizations: OrganizationsResource) -> None: @@ -150,6 +284,12 @@ def __init__(self, organizations: OrganizationsResource) -> None: self.retrieve = to_raw_response_wrapper( organizations.retrieve, ) + self.list_members = to_raw_response_wrapper( + organizations.list_members, + ) + self.retrieve_permissions = to_raw_response_wrapper( + organizations.retrieve_permissions, + ) @cached_property def billing(self) -> BillingResourceWithRawResponse: @@ -163,6 +303,12 @@ def __init__(self, organizations: AsyncOrganizationsResource) -> None: self.retrieve = async_to_raw_response_wrapper( organizations.retrieve, ) + self.list_members = async_to_raw_response_wrapper( + organizations.list_members, + ) + self.retrieve_permissions = async_to_raw_response_wrapper( + organizations.retrieve_permissions, + ) @cached_property def billing(self) -> AsyncBillingResourceWithRawResponse: @@ -176,6 +322,12 @@ def __init__(self, organizations: OrganizationsResource) -> None: self.retrieve = to_streamed_response_wrapper( organizations.retrieve, ) + self.list_members = to_streamed_response_wrapper( + organizations.list_members, + ) + self.retrieve_permissions = to_streamed_response_wrapper( + organizations.retrieve_permissions, + ) @cached_property def billing(self) -> BillingResourceWithStreamingResponse: @@ -189,6 +341,12 @@ def __init__(self, organizations: AsyncOrganizationsResource) -> None: self.retrieve = async_to_streamed_response_wrapper( organizations.retrieve, ) + self.list_members = async_to_streamed_response_wrapper( + organizations.list_members, + ) + self.retrieve_permissions = async_to_streamed_response_wrapper( + organizations.retrieve_permissions, + ) @cached_property def billing(self) -> AsyncBillingResourceWithStreamingResponse: diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index f0fa39cb..f88e2243 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -21,9 +21,10 @@ async_to_streamed_response_wrapper, ) from ..._base_client import make_request_options -from ...types.projects import entry_query_params, entry_create_params, entry_update_params +from ...types.projects import entry_query_params, entry_create_params, entry_update_params, entry_notify_sme_params from ...types.projects.entry import Entry from ...types.projects.entry_query_response import EntryQueryResponse +from ...types.projects.entry_notify_sme_response import EntryNotifySmeResponse __all__ = ["EntriesResource", "AsyncEntriesResource"] @@ -229,6 +230,92 @@ def delete( cast_to=NoneType, ) + def notify_sme( + self, + entry_id: str, + *, + project_id: str, + email: str, + view_context: entry_notify_sme_params.ViewContext, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> EntryNotifySmeResponse: + """ + Notify a subject matter expert to review and answer a specific entry. + + Returns: SMENotificationResponse with status and notification details + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return self._post( + f"/api/projects/{project_id}/entries/{entry_id}/notifications", + body=maybe_transform( + { + "email": email, + "view_context": view_context, + }, + entry_notify_sme_params.EntryNotifySmeParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EntryNotifySmeResponse, + ) + + def publish_draft_answer( + self, + entry_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Entry: + """Promote a draft answer to a published answer for a knowledge entry. + + This always + results in the entry's draft answer being removed. If the entry already has a + published answer, it will be overwritten and permanently lost. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return self._put( + f"/api/projects/{project_id}/entries/{entry_id}/publish_draft_answer", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + def query( self, project_id: str, @@ -291,6 +378,45 @@ def query( cast_to=EntryQueryResponse, ) + def unpublish_answer( + self, + entry_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Entry: + """Unpublish an answer for a knowledge entry. + + This always results in the entry's + answer being removed. If the entry does not already have a draft answer, the + current answer will be retained as the draft answer. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return self._put( + f"/api/projects/{project_id}/entries/{entry_id}/unpublish_answer", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + class AsyncEntriesResource(AsyncAPIResource): @cached_property @@ -493,6 +619,92 @@ async def delete( cast_to=NoneType, ) + async def notify_sme( + self, + entry_id: str, + *, + project_id: str, + email: str, + view_context: entry_notify_sme_params.ViewContext, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> EntryNotifySmeResponse: + """ + Notify a subject matter expert to review and answer a specific entry. + + Returns: SMENotificationResponse with status and notification details + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return await self._post( + f"/api/projects/{project_id}/entries/{entry_id}/notifications", + body=await async_maybe_transform( + { + "email": email, + "view_context": view_context, + }, + entry_notify_sme_params.EntryNotifySmeParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EntryNotifySmeResponse, + ) + + async def publish_draft_answer( + self, + entry_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Entry: + """Promote a draft answer to a published answer for a knowledge entry. + + This always + results in the entry's draft answer being removed. If the entry already has a + published answer, it will be overwritten and permanently lost. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return await self._put( + f"/api/projects/{project_id}/entries/{entry_id}/publish_draft_answer", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + async def query( self, project_id: str, @@ -557,6 +769,45 @@ async def query( cast_to=EntryQueryResponse, ) + async def unpublish_answer( + self, + entry_id: str, + *, + project_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Entry: + """Unpublish an answer for a knowledge entry. + + This always results in the entry's + answer being removed. If the entry does not already have a draft answer, the + current answer will be retained as the draft answer. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + if not entry_id: + raise ValueError(f"Expected a non-empty value for `entry_id` but received {entry_id!r}") + return await self._put( + f"/api/projects/{project_id}/entries/{entry_id}/unpublish_answer", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Entry, + ) + class EntriesResourceWithRawResponse: def __init__(self, entries: EntriesResource) -> None: @@ -574,9 +825,18 @@ def __init__(self, entries: EntriesResource) -> None: self.delete = to_raw_response_wrapper( entries.delete, ) + self.notify_sme = to_raw_response_wrapper( + entries.notify_sme, + ) + self.publish_draft_answer = to_raw_response_wrapper( + entries.publish_draft_answer, + ) self.query = to_raw_response_wrapper( entries.query, ) + self.unpublish_answer = to_raw_response_wrapper( + entries.unpublish_answer, + ) class AsyncEntriesResourceWithRawResponse: @@ -595,9 +855,18 @@ def __init__(self, entries: AsyncEntriesResource) -> None: self.delete = async_to_raw_response_wrapper( entries.delete, ) + self.notify_sme = async_to_raw_response_wrapper( + entries.notify_sme, + ) + self.publish_draft_answer = async_to_raw_response_wrapper( + entries.publish_draft_answer, + ) self.query = async_to_raw_response_wrapper( entries.query, ) + self.unpublish_answer = async_to_raw_response_wrapper( + entries.unpublish_answer, + ) class EntriesResourceWithStreamingResponse: @@ -616,9 +885,18 @@ def __init__(self, entries: EntriesResource) -> None: self.delete = to_streamed_response_wrapper( entries.delete, ) + self.notify_sme = to_streamed_response_wrapper( + entries.notify_sme, + ) + self.publish_draft_answer = to_streamed_response_wrapper( + entries.publish_draft_answer, + ) self.query = to_streamed_response_wrapper( entries.query, ) + self.unpublish_answer = to_streamed_response_wrapper( + entries.unpublish_answer, + ) class AsyncEntriesResourceWithStreamingResponse: @@ -637,6 +915,15 @@ def __init__(self, entries: AsyncEntriesResource) -> None: self.delete = async_to_streamed_response_wrapper( entries.delete, ) + self.notify_sme = async_to_streamed_response_wrapper( + entries.notify_sme, + ) + self.publish_draft_answer = async_to_streamed_response_wrapper( + entries.publish_draft_answer, + ) self.query = async_to_streamed_response_wrapper( entries.query, ) + self.unpublish_answer = async_to_streamed_response_wrapper( + entries.unpublish_answer, + ) diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index ccc77268..47639a5a 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -324,6 +324,39 @@ def export( cast_to=object, ) + def increment_queries( + self, + project_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> object: + """ + Increment the queries metric for a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._post( + f"/api/projects/{project_id}/increment_queries", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=object, + ) + class AsyncProjectsResource(AsyncAPIResource): @cached_property @@ -596,6 +629,39 @@ async def export( cast_to=object, ) + async def increment_queries( + self, + project_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> object: + """ + Increment the queries metric for a project. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._post( + f"/api/projects/{project_id}/increment_queries", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=object, + ) + class ProjectsResourceWithRawResponse: def __init__(self, projects: ProjectsResource) -> None: @@ -619,6 +685,9 @@ def __init__(self, projects: ProjectsResource) -> None: self.export = to_raw_response_wrapper( projects.export, ) + self.increment_queries = to_raw_response_wrapper( + projects.increment_queries, + ) @cached_property def access_keys(self) -> AccessKeysResourceWithRawResponse: @@ -655,6 +724,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.export = async_to_raw_response_wrapper( projects.export, ) + self.increment_queries = async_to_raw_response_wrapper( + projects.increment_queries, + ) @cached_property def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse: @@ -691,6 +763,9 @@ def __init__(self, projects: ProjectsResource) -> None: self.export = to_streamed_response_wrapper( projects.export, ) + self.increment_queries = to_streamed_response_wrapper( + projects.increment_queries, + ) @cached_property def access_keys(self) -> AccessKeysResourceWithStreamingResponse: @@ -727,6 +802,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.export = async_to_streamed_response_wrapper( projects.export, ) + self.increment_queries = async_to_streamed_response_wrapper( + projects.increment_queries, + ) @cached_property def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse: diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index 6c184372..3e65a680 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -16,3 +16,7 @@ from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams +from .organization_list_members_response import OrganizationListMembersResponse as OrganizationListMembersResponse +from .organization_retrieve_permissions_response import ( + OrganizationRetrievePermissionsResponse as OrganizationRetrievePermissionsResponse, +) diff --git a/src/codex/types/organization_list_members_response.py b/src/codex/types/organization_list_members_response.py new file mode 100644 index 00000000..37897d56 --- /dev/null +++ b/src/codex/types/organization_list_members_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import TypeAlias + +from .._models import BaseModel + +__all__ = ["OrganizationListMembersResponse", "OrganizationListMembersResponseItem"] + + +class OrganizationListMembersResponseItem(BaseModel): + email: str + + name: str + + +OrganizationListMembersResponse: TypeAlias = List[OrganizationListMembersResponseItem] diff --git a/src/codex/types/organization_retrieve_permissions_response.py b/src/codex/types/organization_retrieve_permissions_response.py new file mode 100644 index 00000000..ee7b0671 --- /dev/null +++ b/src/codex/types/organization_retrieve_permissions_response.py @@ -0,0 +1,8 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict +from typing_extensions import TypeAlias + +__all__ = ["OrganizationRetrievePermissionsResponse"] + +OrganizationRetrievePermissionsResponse: TypeAlias = Dict[str, bool] diff --git a/src/codex/types/projects/__init__.py b/src/codex/types/projects/__init__.py index 2733406c..2b69e570 100644 --- a/src/codex/types/projects/__init__.py +++ b/src/codex/types/projects/__init__.py @@ -10,9 +10,11 @@ from .entry_update_params import EntryUpdateParams as EntryUpdateParams from .entry_query_response import EntryQueryResponse as EntryQueryResponse from .cluster_list_response import ClusterListResponse as ClusterListResponse +from .entry_notify_sme_params import EntryNotifySmeParams as EntryNotifySmeParams from .access_key_create_params import AccessKeyCreateParams as AccessKeyCreateParams from .access_key_list_response import AccessKeyListResponse as AccessKeyListResponse from .access_key_update_params import AccessKeyUpdateParams as AccessKeyUpdateParams +from .entry_notify_sme_response import EntryNotifySmeResponse as EntryNotifySmeResponse from .cluster_list_variants_response import ClusterListVariantsResponse as ClusterListVariantsResponse from .access_key_retrieve_project_id_response import ( AccessKeyRetrieveProjectIDResponse as AccessKeyRetrieveProjectIDResponse, diff --git a/src/codex/types/projects/entry_notify_sme_params.py b/src/codex/types/projects/entry_notify_sme_params.py new file mode 100644 index 00000000..409f8bc5 --- /dev/null +++ b/src/codex/types/projects/entry_notify_sme_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["EntryNotifySmeParams", "ViewContext"] + + +class EntryNotifySmeParams(TypedDict, total=False): + project_id: Required[str] + + email: Required[str] + + view_context: Required[ViewContext] + + +class ViewContext(TypedDict, total=False): + page: Required[int] + + filter: Literal["unanswered", "answered", "all", "hallucination", "search_failure", "unhelpful", "difficult_query"] diff --git a/src/codex/types/projects/entry_notify_sme_response.py b/src/codex/types/projects/entry_notify_sme_response.py new file mode 100644 index 00000000..dd05a6cf --- /dev/null +++ b/src/codex/types/projects/entry_notify_sme_response.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + + +from ..._models import BaseModel + +__all__ = ["EntryNotifySmeResponse"] + + +class EntryNotifySmeResponse(BaseModel): + entry_id: str + + recipient_email: str + + status: str diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index ca7eecbb..31a5e408 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -9,7 +9,11 @@ from codex import Codex, AsyncCodex from tests.utils import assert_matches_type -from codex.types.projects import Entry, EntryQueryResponse +from codex.types.projects import ( + Entry, + EntryQueryResponse, + EntryNotifySmeResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -247,6 +251,134 @@ def test_path_params_delete(self, client: Codex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) + @pytest.mark.skip() + @parametrize + def test_method_notify_sme(self, client: Codex) -> None: + entry = client.projects.entries.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_notify_sme_with_all_params(self, client: Codex) -> None: + entry = client.projects.entries.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={ + "page": 0, + "filter": "unanswered", + }, + ) + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_notify_sme(self, client: Codex) -> None: + response = client.projects.entries.with_raw_response.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = response.parse() + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_notify_sme(self, client: Codex) -> None: + with client.projects.entries.with_streaming_response.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = response.parse() + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_notify_sme(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.entries.with_raw_response.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + email="email", + view_context={"page": 0}, + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + client.projects.entries.with_raw_response.notify_sme( + entry_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) + + @pytest.mark.skip() + @parametrize + def test_method_publish_draft_answer(self, client: Codex) -> None: + entry = client.projects.entries.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_publish_draft_answer(self, client: Codex) -> None: + response = client.projects.entries.with_raw_response.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_publish_draft_answer(self, client: Codex) -> None: + with client.projects.entries.with_streaming_response.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_publish_draft_answer(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.entries.with_raw_response.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + client.projects.entries.with_raw_response.publish_draft_answer( + entry_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + @pytest.mark.skip() @parametrize def test_method_query(self, client: Codex) -> None: @@ -308,6 +440,58 @@ def test_path_params_query(self, client: Codex) -> None: question="question", ) + @pytest.mark.skip() + @parametrize + def test_method_unpublish_answer(self, client: Codex) -> None: + entry = client.projects.entries.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_unpublish_answer(self, client: Codex) -> None: + response = client.projects.entries.with_raw_response.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_unpublish_answer(self, client: Codex) -> None: + with client.projects.entries.with_streaming_response.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_unpublish_answer(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.entries.with_raw_response.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + client.projects.entries.with_raw_response.unpublish_answer( + entry_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + class TestAsyncEntries: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -542,6 +726,134 @@ async def test_path_params_delete(self, async_client: AsyncCodex) -> None: project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) + @pytest.mark.skip() + @parametrize + async def test_method_notify_sme(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_notify_sme_with_all_params(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={ + "page": 0, + "filter": "unanswered", + }, + ) + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_notify_sme(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.entries.with_raw_response.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = await response.parse() + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_notify_sme(self, async_client: AsyncCodex) -> None: + async with async_client.projects.entries.with_streaming_response.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = await response.parse() + assert_matches_type(EntryNotifySmeResponse, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_notify_sme(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.entries.with_raw_response.notify_sme( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + email="email", + view_context={"page": 0}, + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + await async_client.projects.entries.with_raw_response.notify_sme( + entry_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + email="email", + view_context={"page": 0}, + ) + + @pytest.mark.skip() + @parametrize + async def test_method_publish_draft_answer(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_publish_draft_answer(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.entries.with_raw_response.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_publish_draft_answer(self, async_client: AsyncCodex) -> None: + async with async_client.projects.entries.with_streaming_response.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_publish_draft_answer(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.entries.with_raw_response.publish_draft_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + await async_client.projects.entries.with_raw_response.publish_draft_answer( + entry_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + @pytest.mark.skip() @parametrize async def test_method_query(self, async_client: AsyncCodex) -> None: @@ -602,3 +914,55 @@ async def test_path_params_query(self, async_client: AsyncCodex) -> None: project_id="", question="question", ) + + @pytest.mark.skip() + @parametrize + async def test_method_unpublish_answer(self, async_client: AsyncCodex) -> None: + entry = await async_client.projects.entries.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_unpublish_answer(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.entries.with_raw_response.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_unpublish_answer(self, async_client: AsyncCodex) -> None: + async with async_client.projects.entries.with_streaming_response.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + entry = await response.parse() + assert_matches_type(Entry, entry, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_unpublish_answer(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.entries.with_raw_response.unpublish_answer( + entry_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + project_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `entry_id` but received ''"): + await async_client.projects.entries.with_raw_response.unpublish_answer( + entry_id="", + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py index 1a13f77a..c665e85c 100644 --- a/tests/api_resources/test_organizations.py +++ b/tests/api_resources/test_organizations.py @@ -8,7 +8,11 @@ import pytest from codex import Codex, AsyncCodex -from codex.types import OrganizationSchemaPublic +from codex.types import ( + OrganizationSchemaPublic, + OrganizationListMembersResponse, + OrganizationRetrievePermissionsResponse, +) from tests.utils import assert_matches_type base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -59,6 +63,90 @@ def test_path_params_retrieve(self, client: Codex) -> None: "", ) + @pytest.mark.skip() + @parametrize + def test_method_list_members(self, client: Codex) -> None: + organization = client.organizations.list_members( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_list_members(self, client: Codex) -> None: + response = client.organizations.with_raw_response.list_members( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + organization = response.parse() + assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_list_members(self, client: Codex) -> None: + with client.organizations.with_streaming_response.list_members( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + organization = response.parse() + assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_list_members(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + client.organizations.with_raw_response.list_members( + "", + ) + + @pytest.mark.skip() + @parametrize + def test_method_retrieve_permissions(self, client: Codex) -> None: + organization = client.organizations.retrieve_permissions( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_retrieve_permissions(self, client: Codex) -> None: + response = client.organizations.with_raw_response.retrieve_permissions( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + organization = response.parse() + assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_retrieve_permissions(self, client: Codex) -> None: + with client.organizations.with_streaming_response.retrieve_permissions( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + organization = response.parse() + assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_retrieve_permissions(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + client.organizations.with_raw_response.retrieve_permissions( + "", + ) + class TestAsyncOrganizations: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -104,3 +192,87 @@ async def test_path_params_retrieve(self, async_client: AsyncCodex) -> None: await async_client.organizations.with_raw_response.retrieve( "", ) + + @pytest.mark.skip() + @parametrize + async def test_method_list_members(self, async_client: AsyncCodex) -> None: + organization = await async_client.organizations.list_members( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_list_members(self, async_client: AsyncCodex) -> None: + response = await async_client.organizations.with_raw_response.list_members( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + organization = await response.parse() + assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_list_members(self, async_client: AsyncCodex) -> None: + async with async_client.organizations.with_streaming_response.list_members( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + organization = await response.parse() + assert_matches_type(OrganizationListMembersResponse, organization, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_list_members(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + await async_client.organizations.with_raw_response.list_members( + "", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_retrieve_permissions(self, async_client: AsyncCodex) -> None: + organization = await async_client.organizations.retrieve_permissions( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_retrieve_permissions(self, async_client: AsyncCodex) -> None: + response = await async_client.organizations.with_raw_response.retrieve_permissions( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + organization = await response.parse() + assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_retrieve_permissions(self, async_client: AsyncCodex) -> None: + async with async_client.organizations.with_streaming_response.retrieve_permissions( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + organization = await response.parse() + assert_matches_type(OrganizationRetrievePermissionsResponse, organization, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_retrieve_permissions(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `organization_id` but received ''"): + await async_client.organizations.with_raw_response.retrieve_permissions( + "", + ) diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 1e2fccbc..bfa657d2 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -323,6 +323,48 @@ def test_path_params_export(self, client: Codex) -> None: "", ) + @pytest.mark.skip() + @parametrize + def test_method_increment_queries(self, client: Codex) -> None: + project = client.projects.increment_queries( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(object, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_increment_queries(self, client: Codex) -> None: + response = client.projects.with_raw_response.increment_queries( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(object, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_increment_queries(self, client: Codex) -> None: + with client.projects.with_streaming_response.increment_queries( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(object, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_increment_queries(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.with_raw_response.increment_queries( + "", + ) + class TestAsyncProjects: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -628,3 +670,45 @@ async def test_path_params_export(self, async_client: AsyncCodex) -> None: await async_client.projects.with_raw_response.export( "", ) + + @pytest.mark.skip() + @parametrize + async def test_method_increment_queries(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.increment_queries( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(object, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_increment_queries(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.with_raw_response.increment_queries( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = await response.parse() + assert_matches_type(object, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_increment_queries(self, async_client: AsyncCodex) -> None: + async with async_client.projects.with_streaming_response.increment_queries( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(object, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_increment_queries(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.with_raw_response.increment_queries( + "", + ) From e81cc3d451c7c8d163c0b80d983140506a0adb66 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:48:01 +0000 Subject: [PATCH 06/17] feat(api): add project increment_queries and other recent endpoints --- .stats.yml | 2 +- api.md | 12 +++--------- src/codex/resources/users/myself/api_key.py | 10 +++++----- src/codex/resources/users/myself/myself.py | 10 +++++----- src/codex/resources/users/users.py | 10 +++++----- src/codex/types/__init__.py | 1 - src/codex/types/users/__init__.py | 1 + .../{user.py => users/user_schema_public.py} | 6 +++--- tests/api_resources/test_users.py | 18 +++++++++--------- .../api_resources/users/myself/test_api_key.py | 15 +++++++-------- tests/api_resources/users/test_myself.py | 14 +++++++------- 11 files changed, 46 insertions(+), 53 deletions(-) rename src/codex/types/{user.py => users/user_schema_public.py} (80%) diff --git a/.stats.yml b/.stats.yml index d78252d8..aa1535bd 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 42 openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d -config_hash: 93ac12138700569dc57329400410c0fd +config_hash: 2d88a0a41f5faca603ff2789a116d988 diff --git a/api.md b/api.md index 5a314a2d..617cdd24 100644 --- a/api.md +++ b/api.md @@ -83,15 +83,9 @@ Methods: # Users -Types: - -```python -from codex.types import User -``` - Methods: -- client.users.activate_account(\*\*params) -> User +- client.users.activate_account(\*\*params) -> UserSchemaPublic ## Myself @@ -103,13 +97,13 @@ from codex.types.users import UserSchema, UserSchemaPublic Methods: -- client.users.myself.retrieve() -> User +- client.users.myself.retrieve() -> UserSchemaPublic ### APIKey Methods: -- client.users.myself.api_key.retrieve() -> User +- client.users.myself.api_key.retrieve() -> UserSchemaPublic - client.users.myself.api_key.refresh() -> UserSchema ### Organizations diff --git a/src/codex/resources/users/myself/api_key.py b/src/codex/resources/users/myself/api_key.py index 87f647d3..72f1502b 100644 --- a/src/codex/resources/users/myself/api_key.py +++ b/src/codex/resources/users/myself/api_key.py @@ -13,9 +13,9 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ....types.user import User from ...._base_client import make_request_options from ....types.users.user_schema import UserSchema +from ....types.users.user_schema_public import UserSchemaPublic __all__ = ["APIKeyResource", "AsyncAPIKeyResource"] @@ -49,14 +49,14 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> User: + ) -> UserSchemaPublic: """Get user when authenticated with API key.""" return self._get( "/api/users/myself/api-key", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=User, + cast_to=UserSchemaPublic, ) def refresh( @@ -108,14 +108,14 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> User: + ) -> UserSchemaPublic: """Get user when authenticated with API key.""" return await self._get( "/api/users/myself/api-key", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=User, + cast_to=UserSchemaPublic, ) async def refresh( diff --git a/src/codex/resources/users/myself/myself.py b/src/codex/resources/users/myself/myself.py index 2cc4868b..3ee27229 100644 --- a/src/codex/resources/users/myself/myself.py +++ b/src/codex/resources/users/myself/myself.py @@ -21,7 +21,6 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ....types.user import User from .organizations import ( OrganizationsResource, AsyncOrganizationsResource, @@ -31,6 +30,7 @@ AsyncOrganizationsResourceWithStreamingResponse, ) from ...._base_client import make_request_options +from ....types.users.user_schema_public import UserSchemaPublic __all__ = ["MyselfResource", "AsyncMyselfResource"] @@ -72,14 +72,14 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> User: + ) -> UserSchemaPublic: """Get user info for frontend.""" return self._get( "/api/users/myself", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=User, + cast_to=UserSchemaPublic, ) @@ -120,14 +120,14 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> User: + ) -> UserSchemaPublic: """Get user info for frontend.""" return await self._get( "/api/users/myself", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=User, + cast_to=UserSchemaPublic, ) diff --git a/src/codex/resources/users/users.py b/src/codex/resources/users/users.py index a7d9d2ab..c276f950 100644 --- a/src/codex/resources/users/users.py +++ b/src/codex/resources/users/users.py @@ -21,7 +21,6 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ...types.user import User from .verification import ( VerificationResource, AsyncVerificationResource, @@ -39,6 +38,7 @@ AsyncMyselfResourceWithStreamingResponse, ) from ..._base_client import make_request_options +from ...types.users.user_schema_public import UserSchemaPublic __all__ = ["UsersResource", "AsyncUsersResource"] @@ -87,7 +87,7 @@ def activate_account( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> User: + ) -> UserSchemaPublic: """ Activate an authenticated user's account @@ -117,7 +117,7 @@ def activate_account( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=User, + cast_to=UserSchemaPublic, ) @@ -165,7 +165,7 @@ async def activate_account( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> User: + ) -> UserSchemaPublic: """ Activate an authenticated user's account @@ -195,7 +195,7 @@ async def activate_account( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=User, + cast_to=UserSchemaPublic, ) diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index 3e65a680..008c4a67 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations -from .user import User as User from .tlm_score_params import TlmScoreParams as TlmScoreParams from .tlm_prompt_params import TlmPromptParams as TlmPromptParams from .tlm_score_response import TlmScoreResponse as TlmScoreResponse diff --git a/src/codex/types/users/__init__.py b/src/codex/types/users/__init__.py index 438bc6f3..23dbc910 100644 --- a/src/codex/types/users/__init__.py +++ b/src/codex/types/users/__init__.py @@ -3,4 +3,5 @@ from __future__ import annotations from .user_schema import UserSchema as UserSchema +from .user_schema_public import UserSchemaPublic as UserSchemaPublic from .verification_resend_response import VerificationResendResponse as VerificationResendResponse diff --git a/src/codex/types/user.py b/src/codex/types/users/user_schema_public.py similarity index 80% rename from src/codex/types/user.py rename to src/codex/types/users/user_schema_public.py index 3d7ec233..181113b0 100644 --- a/src/codex/types/user.py +++ b/src/codex/types/users/user_schema_public.py @@ -2,12 +2,12 @@ from typing import Optional -from .._models import BaseModel +from ..._models import BaseModel -__all__ = ["User"] +__all__ = ["UserSchemaPublic"] -class User(BaseModel): +class UserSchemaPublic(BaseModel): id: str api_key: str diff --git a/tests/api_resources/test_users.py b/tests/api_resources/test_users.py index 7e78b99e..101d0f66 100644 --- a/tests/api_resources/test_users.py +++ b/tests/api_resources/test_users.py @@ -8,9 +8,9 @@ import pytest from codex import Codex, AsyncCodex -from codex.types import User from tests.utils import assert_matches_type from codex._utils import parse_datetime +from codex.types.users import UserSchemaPublic base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -25,7 +25,7 @@ def test_method_activate_account(self, client: Codex) -> None: first_name="first_name", last_name="last_name", ) - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) @pytest.mark.skip() @parametrize @@ -39,7 +39,7 @@ def test_method_activate_account_with_all_params(self, client: Codex) -> None: phone_number="phone_number", user_provided_company_name="user_provided_company_name", ) - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) @pytest.mark.skip() @parametrize @@ -52,7 +52,7 @@ def test_raw_response_activate_account(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = response.parse() - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) @pytest.mark.skip() @parametrize @@ -65,7 +65,7 @@ def test_streaming_response_activate_account(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = response.parse() - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) assert cast(Any, response.is_closed) is True @@ -80,7 +80,7 @@ async def test_method_activate_account(self, async_client: AsyncCodex) -> None: first_name="first_name", last_name="last_name", ) - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) @pytest.mark.skip() @parametrize @@ -94,7 +94,7 @@ async def test_method_activate_account_with_all_params(self, async_client: Async phone_number="phone_number", user_provided_company_name="user_provided_company_name", ) - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) @pytest.mark.skip() @parametrize @@ -107,7 +107,7 @@ async def test_raw_response_activate_account(self, async_client: AsyncCodex) -> assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = await response.parse() - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) @pytest.mark.skip() @parametrize @@ -120,6 +120,6 @@ async def test_streaming_response_activate_account(self, async_client: AsyncCode assert response.http_request.headers.get("X-Stainless-Lang") == "python" user = await response.parse() - assert_matches_type(User, user, path=["response"]) + assert_matches_type(UserSchemaPublic, user, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/users/myself/test_api_key.py b/tests/api_resources/users/myself/test_api_key.py index a58477bc..c6ed0209 100644 --- a/tests/api_resources/users/myself/test_api_key.py +++ b/tests/api_resources/users/myself/test_api_key.py @@ -8,9 +8,8 @@ import pytest from codex import Codex, AsyncCodex -from codex.types import User from tests.utils import assert_matches_type -from codex.types.users import UserSchema +from codex.types.users import UserSchema, UserSchemaPublic base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -22,7 +21,7 @@ class TestAPIKey: @parametrize def test_method_retrieve(self, client: Codex) -> None: api_key = client.users.myself.api_key.retrieve() - assert_matches_type(User, api_key, path=["response"]) + assert_matches_type(UserSchemaPublic, api_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -32,7 +31,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" api_key = response.parse() - assert_matches_type(User, api_key, path=["response"]) + assert_matches_type(UserSchemaPublic, api_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -42,7 +41,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" api_key = response.parse() - assert_matches_type(User, api_key, path=["response"]) + assert_matches_type(UserSchemaPublic, api_key, path=["response"]) assert cast(Any, response.is_closed) is True @@ -82,7 +81,7 @@ class TestAsyncAPIKey: @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: api_key = await async_client.users.myself.api_key.retrieve() - assert_matches_type(User, api_key, path=["response"]) + assert_matches_type(UserSchemaPublic, api_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -92,7 +91,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" api_key = await response.parse() - assert_matches_type(User, api_key, path=["response"]) + assert_matches_type(UserSchemaPublic, api_key, path=["response"]) @pytest.mark.skip() @parametrize @@ -102,7 +101,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert response.http_request.headers.get("X-Stainless-Lang") == "python" api_key = await response.parse() - assert_matches_type(User, api_key, path=["response"]) + assert_matches_type(UserSchemaPublic, api_key, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/users/test_myself.py b/tests/api_resources/users/test_myself.py index 174f2cec..63123275 100644 --- a/tests/api_resources/users/test_myself.py +++ b/tests/api_resources/users/test_myself.py @@ -8,8 +8,8 @@ import pytest from codex import Codex, AsyncCodex -from codex.types import User from tests.utils import assert_matches_type +from codex.types.users import UserSchemaPublic base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -21,7 +21,7 @@ class TestMyself: @parametrize def test_method_retrieve(self, client: Codex) -> None: myself = client.users.myself.retrieve() - assert_matches_type(User, myself, path=["response"]) + assert_matches_type(UserSchemaPublic, myself, path=["response"]) @pytest.mark.skip() @parametrize @@ -31,7 +31,7 @@ def test_raw_response_retrieve(self, client: Codex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" myself = response.parse() - assert_matches_type(User, myself, path=["response"]) + assert_matches_type(UserSchemaPublic, myself, path=["response"]) @pytest.mark.skip() @parametrize @@ -41,7 +41,7 @@ def test_streaming_response_retrieve(self, client: Codex) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" myself = response.parse() - assert_matches_type(User, myself, path=["response"]) + assert_matches_type(UserSchemaPublic, myself, path=["response"]) assert cast(Any, response.is_closed) is True @@ -53,7 +53,7 @@ class TestAsyncMyself: @parametrize async def test_method_retrieve(self, async_client: AsyncCodex) -> None: myself = await async_client.users.myself.retrieve() - assert_matches_type(User, myself, path=["response"]) + assert_matches_type(UserSchemaPublic, myself, path=["response"]) @pytest.mark.skip() @parametrize @@ -63,7 +63,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncCodex) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" myself = await response.parse() - assert_matches_type(User, myself, path=["response"]) + assert_matches_type(UserSchemaPublic, myself, path=["response"]) @pytest.mark.skip() @parametrize @@ -73,6 +73,6 @@ async def test_streaming_response_retrieve(self, async_client: AsyncCodex) -> No assert response.http_request.headers.get("X-Stainless-Lang") == "python" myself = await response.parse() - assert_matches_type(User, myself, path=["response"]) + assert_matches_type(UserSchemaPublic, myself, path=["response"]) assert cast(Any, response.is_closed) is True From 5bf31f74b6008c9bda7b4ec290b1216a51ef9d1f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:59:42 +0000 Subject: [PATCH 07/17] feat(api): add project increment_queries and other recent endpoints --- .stats.yml | 2 +- src/codex/_client.py | 71 ------------------- src/codex/resources/projects/access_keys.py | 4 ++ src/codex/resources/projects/clusters.py | 4 +- src/codex/resources/projects/entries.py | 12 +++- src/codex/resources/projects/projects.py | 8 +-- src/codex/types/project_list_params.py | 6 +- .../projects/access_key_create_params.py | 2 + .../types/projects/entry_create_params.py | 2 + .../types/projects/entry_query_params.py | 2 + .../projects/test_access_keys.py | 2 + tests/api_resources/projects/test_entries.py | 4 ++ tests/api_resources/test_projects.py | 28 +++----- 13 files changed, 44 insertions(+), 103 deletions(-) diff --git a/.stats.yml b/.stats.yml index aa1535bd..fd7bea9a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 42 -openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d +openapi_spec_hash: 8d2e7726c60bca0dcfc72b1e2df34ef1 config_hash: 2d88a0a41f5faca603ff2789a116d988 diff --git a/src/codex/_client.py b/src/codex/_client.py index 2641513f..f035421c 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -13,7 +13,6 @@ from ._types import ( NOT_GIVEN, Omit, - Headers, Timeout, NotGiven, Transport, @@ -151,25 +150,6 @@ def __init__( def qs(self) -> Querystring: return Querystring(array_format="comma") - @property - @override - def auth_headers(self) -> dict[str, str]: - return {**self._authenticated_api_key, **self._public_access_key} - - @property - def _authenticated_api_key(self) -> dict[str, str]: - api_key = self.api_key - if api_key is None: - return {} - return {"X-API-Key": api_key} - - @property - def _public_access_key(self) -> dict[str, str]: - access_key = self.access_key - if access_key is None: - return {} - return {"X-Access-Key": access_key} - @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -179,22 +159,6 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } - @override - def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: - if self.api_key and headers.get("X-API-Key"): - return - if isinstance(custom_headers.get("X-API-Key"), Omit): - return - - if self.access_key and headers.get("X-Access-Key"): - return - if isinstance(custom_headers.get("X-Access-Key"), Omit): - return - - raise TypeError( - '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' - ) - def copy( self, *, @@ -379,25 +343,6 @@ def __init__( def qs(self) -> Querystring: return Querystring(array_format="comma") - @property - @override - def auth_headers(self) -> dict[str, str]: - return {**self._authenticated_api_key, **self._public_access_key} - - @property - def _authenticated_api_key(self) -> dict[str, str]: - api_key = self.api_key - if api_key is None: - return {} - return {"X-API-Key": api_key} - - @property - def _public_access_key(self) -> dict[str, str]: - access_key = self.access_key - if access_key is None: - return {} - return {"X-Access-Key": access_key} - @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -407,22 +352,6 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } - @override - def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: - if self.api_key and headers.get("X-API-Key"): - return - if isinstance(custom_headers.get("X-API-Key"), Omit): - return - - if self.access_key and headers.get("X-Access-Key"): - return - if isinstance(custom_headers.get("X-Access-Key"), Omit): - return - - raise TypeError( - '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' - ) - def copy( self, *, diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py index 61987399..9ba2e131 100644 --- a/src/codex/resources/projects/access_keys.py +++ b/src/codex/resources/projects/access_keys.py @@ -57,6 +57,7 @@ def create( name: str, description: Optional[str] | NotGiven = NOT_GIVEN, expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -85,6 +86,7 @@ def create( extra_headers = { **strip_not_given( { + "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -346,6 +348,7 @@ async def create( name: str, description: Optional[str] | NotGiven = NOT_GIVEN, expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, + x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -374,6 +377,7 @@ async def create( extra_headers = { **strip_not_given( { + "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py index 584cde18..eabbe60e 100644 --- a/src/codex/resources/projects/clusters.py +++ b/src/codex/resources/projects/clusters.py @@ -115,7 +115,7 @@ def list_variants( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ClusterListVariantsResponse: """ - Get Cluster Variants Route + Get Cluster Variants Args: extra_headers: Send extra headers @@ -230,7 +230,7 @@ async def list_variants( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ClusterListVariantsResponse: """ - Get Cluster Variants Route + Get Cluster Variants Args: extra_headers: Send extra headers diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index f88e2243..bdfee06d 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -57,6 +57,7 @@ def create( answer: Optional[str] | NotGiven = NOT_GIVEN, client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN, draft_answer: Optional[str] | NotGiven = NOT_GIVEN, + x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -85,6 +86,7 @@ def create( extra_headers = { **strip_not_given( { + "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -323,6 +325,7 @@ def query( question: str, use_llm_matching: bool | NotGiven = NOT_GIVEN, client_metadata: Optional[object] | NotGiven = NOT_GIVEN, + x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -335,7 +338,7 @@ def query( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> EntryQueryResponse: """ - Query Entries Route + Query Entries Args: extra_headers: Send extra headers @@ -351,6 +354,7 @@ def query( extra_headers = { **strip_not_given( { + "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -446,6 +450,7 @@ async def create( answer: Optional[str] | NotGiven = NOT_GIVEN, client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN, draft_answer: Optional[str] | NotGiven = NOT_GIVEN, + x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -474,6 +479,7 @@ async def create( extra_headers = { **strip_not_given( { + "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -712,6 +718,7 @@ async def query( question: str, use_llm_matching: bool | NotGiven = NOT_GIVEN, client_metadata: Optional[object] | NotGiven = NOT_GIVEN, + x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -724,7 +731,7 @@ async def query( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> EntryQueryResponse: """ - Query Entries Route + Query Entries Args: extra_headers: Send extra headers @@ -740,6 +747,7 @@ async def query( extra_headers = { **strip_not_given( { + "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 47639a5a..76237fb6 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -208,11 +208,11 @@ def update( def list( self, *, - organization_id: str, include_entry_counts: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + organization_id: str | NotGiven = NOT_GIVEN, query: Optional[str] | NotGiven = NOT_GIVEN, sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -243,11 +243,11 @@ def list( timeout=timeout, query=maybe_transform( { - "organization_id": organization_id, "include_entry_counts": include_entry_counts, "limit": limit, "offset": offset, "order": order, + "organization_id": organization_id, "query": query, "sort": sort, }, @@ -513,11 +513,11 @@ async def update( async def list( self, *, - organization_id: str, include_entry_counts: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + organization_id: str | NotGiven = NOT_GIVEN, query: Optional[str] | NotGiven = NOT_GIVEN, sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -548,11 +548,11 @@ async def list( timeout=timeout, query=await async_maybe_transform( { - "organization_id": organization_id, "include_entry_counts": include_entry_counts, "limit": limit, "offset": offset, "order": order, + "organization_id": organization_id, "query": query, "sort": sort, }, diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py index c2589598..0ab3b84b 100644 --- a/src/codex/types/project_list_params.py +++ b/src/codex/types/project_list_params.py @@ -3,14 +3,12 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Literal, Required, TypedDict +from typing_extensions import Literal, TypedDict __all__ = ["ProjectListParams"] class ProjectListParams(TypedDict, total=False): - organization_id: Required[str] - include_entry_counts: bool limit: int @@ -19,6 +17,8 @@ class ProjectListParams(TypedDict, total=False): order: Literal["asc", "desc"] + organization_id: str + query: Optional[str] sort: Literal["created_at", "updated_at"] diff --git a/src/codex/types/projects/access_key_create_params.py b/src/codex/types/projects/access_key_create_params.py index cf5f00fb..5035836d 100644 --- a/src/codex/types/projects/access_key_create_params.py +++ b/src/codex/types/projects/access_key_create_params.py @@ -18,6 +18,8 @@ class AccessKeyCreateParams(TypedDict, total=False): expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")] + x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py index f06846bb..490b0e83 100644 --- a/src/codex/types/projects/entry_create_params.py +++ b/src/codex/types/projects/entry_create_params.py @@ -19,6 +19,8 @@ class EntryCreateParams(TypedDict, total=False): draft_answer: Optional[str] + x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")] + x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py index d58b7bfa..db1aec77 100644 --- a/src/codex/types/projects/entry_query_params.py +++ b/src/codex/types/projects/entry_query_params.py @@ -17,6 +17,8 @@ class EntryQueryParams(TypedDict, total=False): client_metadata: Optional[object] + x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")] + x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py index ad4ee5e4..bcf3bb7a 100644 --- a/tests/api_resources/projects/test_access_keys.py +++ b/tests/api_resources/projects/test_access_keys.py @@ -39,6 +39,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: name="name", description="description", expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), + x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -399,6 +400,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> name="name", description="description", expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), + x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index 31a5e408..710d2146 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -39,6 +39,7 @@ def test_method_create_with_all_params(self, client: Codex) -> None: answer="answer", client_query_metadata=[{}], draft_answer="draft_answer", + x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -396,6 +397,7 @@ def test_method_query_with_all_params(self, client: Codex) -> None: question="question", use_llm_matching=True, client_metadata={}, + x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -514,6 +516,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> answer="answer", client_query_metadata=[{}], draft_answer="draft_answer", + x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -871,6 +874,7 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N question="question", use_llm_matching=True, client_metadata={}, + x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index bfa657d2..d71d0e19 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -194,20 +194,18 @@ def test_path_params_update(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_method_list(self, client: Codex) -> None: - project = client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + project = client.projects.list() assert_matches_type(ProjectListResponse, project, path=["response"]) @pytest.mark.skip() @parametrize def test_method_list_with_all_params(self, client: Codex) -> None: project = client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", include_entry_counts=True, limit=0, offset=0, order="asc", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", query="query", sort="created_at", ) @@ -216,9 +214,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_raw_response_list(self, client: Codex) -> None: - response = client.projects.with_raw_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + response = client.projects.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -228,9 +224,7 @@ def test_raw_response_list(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_streaming_response_list(self, client: Codex) -> None: - with client.projects.with_streaming_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: + with client.projects.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -542,20 +536,18 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None: @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: - project = await async_client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + project = await async_client.projects.list() assert_matches_type(ProjectListResponse, project, path=["response"]) @pytest.mark.skip() @parametrize async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", include_entry_counts=True, limit=0, offset=0, order="asc", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", query="query", sort="created_at", ) @@ -564,9 +556,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No @pytest.mark.skip() @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.with_raw_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + response = await async_client.projects.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -576,9 +566,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: @pytest.mark.skip() @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: - async with async_client.projects.with_streaming_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: + async with async_client.projects.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From 3f857575176d200a639880dfa7e60e3a9949750c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 18:17:09 +0000 Subject: [PATCH 08/17] feat(api): api update --- .stats.yml | 2 +- src/codex/_client.py | 71 +++++++++++++++++++ src/codex/resources/projects/access_keys.py | 4 -- src/codex/resources/projects/clusters.py | 4 +- src/codex/resources/projects/entries.py | 12 +--- src/codex/resources/projects/projects.py | 8 +-- src/codex/types/project_list_params.py | 6 +- .../projects/access_key_create_params.py | 2 - .../types/projects/entry_create_params.py | 2 - .../types/projects/entry_query_params.py | 2 - .../projects/test_access_keys.py | 2 - tests/api_resources/projects/test_entries.py | 4 -- tests/api_resources/test_projects.py | 28 +++++--- 13 files changed, 103 insertions(+), 44 deletions(-) diff --git a/.stats.yml b/.stats.yml index fd7bea9a..aa1535bd 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 42 -openapi_spec_hash: 8d2e7726c60bca0dcfc72b1e2df34ef1 +openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d config_hash: 2d88a0a41f5faca603ff2789a116d988 diff --git a/src/codex/_client.py b/src/codex/_client.py index f035421c..2641513f 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -13,6 +13,7 @@ from ._types import ( NOT_GIVEN, Omit, + Headers, Timeout, NotGiven, Transport, @@ -150,6 +151,25 @@ def __init__( def qs(self) -> Querystring: return Querystring(array_format="comma") + @property + @override + def auth_headers(self) -> dict[str, str]: + return {**self._authenticated_api_key, **self._public_access_key} + + @property + def _authenticated_api_key(self) -> dict[str, str]: + api_key = self.api_key + if api_key is None: + return {} + return {"X-API-Key": api_key} + + @property + def _public_access_key(self) -> dict[str, str]: + access_key = self.access_key + if access_key is None: + return {} + return {"X-Access-Key": access_key} + @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -159,6 +179,22 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } + @override + def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: + if self.api_key and headers.get("X-API-Key"): + return + if isinstance(custom_headers.get("X-API-Key"), Omit): + return + + if self.access_key and headers.get("X-Access-Key"): + return + if isinstance(custom_headers.get("X-Access-Key"), Omit): + return + + raise TypeError( + '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' + ) + def copy( self, *, @@ -343,6 +379,25 @@ def __init__( def qs(self) -> Querystring: return Querystring(array_format="comma") + @property + @override + def auth_headers(self) -> dict[str, str]: + return {**self._authenticated_api_key, **self._public_access_key} + + @property + def _authenticated_api_key(self) -> dict[str, str]: + api_key = self.api_key + if api_key is None: + return {} + return {"X-API-Key": api_key} + + @property + def _public_access_key(self) -> dict[str, str]: + access_key = self.access_key + if access_key is None: + return {} + return {"X-Access-Key": access_key} + @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -352,6 +407,22 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } + @override + def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: + if self.api_key and headers.get("X-API-Key"): + return + if isinstance(custom_headers.get("X-API-Key"), Omit): + return + + if self.access_key and headers.get("X-Access-Key"): + return + if isinstance(custom_headers.get("X-Access-Key"), Omit): + return + + raise TypeError( + '"Could not resolve authentication method. Expected either api_key or access_key to be set. Or for one of the `X-API-Key` or `X-Access-Key` headers to be explicitly omitted"' + ) + def copy( self, *, diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py index 9ba2e131..61987399 100644 --- a/src/codex/resources/projects/access_keys.py +++ b/src/codex/resources/projects/access_keys.py @@ -57,7 +57,6 @@ def create( name: str, description: Optional[str] | NotGiven = NOT_GIVEN, expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -86,7 +85,6 @@ def create( extra_headers = { **strip_not_given( { - "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -348,7 +346,6 @@ async def create( name: str, description: Optional[str] | NotGiven = NOT_GIVEN, expires_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, - x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -377,7 +374,6 @@ async def create( extra_headers = { **strip_not_given( { - "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, diff --git a/src/codex/resources/projects/clusters.py b/src/codex/resources/projects/clusters.py index eabbe60e..584cde18 100644 --- a/src/codex/resources/projects/clusters.py +++ b/src/codex/resources/projects/clusters.py @@ -115,7 +115,7 @@ def list_variants( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ClusterListVariantsResponse: """ - Get Cluster Variants + Get Cluster Variants Route Args: extra_headers: Send extra headers @@ -230,7 +230,7 @@ async def list_variants( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ClusterListVariantsResponse: """ - Get Cluster Variants + Get Cluster Variants Route Args: extra_headers: Send extra headers diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index bdfee06d..f88e2243 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -57,7 +57,6 @@ def create( answer: Optional[str] | NotGiven = NOT_GIVEN, client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN, draft_answer: Optional[str] | NotGiven = NOT_GIVEN, - x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -86,7 +85,6 @@ def create( extra_headers = { **strip_not_given( { - "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -325,7 +323,6 @@ def query( question: str, use_llm_matching: bool | NotGiven = NOT_GIVEN, client_metadata: Optional[object] | NotGiven = NOT_GIVEN, - x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -338,7 +335,7 @@ def query( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> EntryQueryResponse: """ - Query Entries + Query Entries Route Args: extra_headers: Send extra headers @@ -354,7 +351,6 @@ def query( extra_headers = { **strip_not_given( { - "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -450,7 +446,6 @@ async def create( answer: Optional[str] | NotGiven = NOT_GIVEN, client_query_metadata: Iterable[object] | NotGiven = NOT_GIVEN, draft_answer: Optional[str] | NotGiven = NOT_GIVEN, - x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -479,7 +474,6 @@ async def create( extra_headers = { **strip_not_given( { - "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, @@ -718,7 +712,6 @@ async def query( question: str, use_llm_matching: bool | NotGiven = NOT_GIVEN, client_metadata: Optional[object] | NotGiven = NOT_GIVEN, - x_access_key: str | NotGiven = NOT_GIVEN, x_client_library_version: str | NotGiven = NOT_GIVEN, x_integration_type: str | NotGiven = NOT_GIVEN, x_source: str | NotGiven = NOT_GIVEN, @@ -731,7 +724,7 @@ async def query( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> EntryQueryResponse: """ - Query Entries + Query Entries Route Args: extra_headers: Send extra headers @@ -747,7 +740,6 @@ async def query( extra_headers = { **strip_not_given( { - "x-access-key": x_access_key, "x-client-library-version": x_client_library_version, "x-integration-type": x_integration_type, "x-source": x_source, diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 76237fb6..47639a5a 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -208,11 +208,11 @@ def update( def list( self, *, + organization_id: str, include_entry_counts: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - organization_id: str | NotGiven = NOT_GIVEN, query: Optional[str] | NotGiven = NOT_GIVEN, sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -243,11 +243,11 @@ def list( timeout=timeout, query=maybe_transform( { + "organization_id": organization_id, "include_entry_counts": include_entry_counts, "limit": limit, "offset": offset, "order": order, - "organization_id": organization_id, "query": query, "sort": sort, }, @@ -513,11 +513,11 @@ async def update( async def list( self, *, + organization_id: str, include_entry_counts: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - organization_id: str | NotGiven = NOT_GIVEN, query: Optional[str] | NotGiven = NOT_GIVEN, sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -548,11 +548,11 @@ async def list( timeout=timeout, query=await async_maybe_transform( { + "organization_id": organization_id, "include_entry_counts": include_entry_counts, "limit": limit, "offset": offset, "order": order, - "organization_id": organization_id, "query": query, "sort": sort, }, diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py index 0ab3b84b..c2589598 100644 --- a/src/codex/types/project_list_params.py +++ b/src/codex/types/project_list_params.py @@ -3,12 +3,14 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Literal, TypedDict +from typing_extensions import Literal, Required, TypedDict __all__ = ["ProjectListParams"] class ProjectListParams(TypedDict, total=False): + organization_id: Required[str] + include_entry_counts: bool limit: int @@ -17,8 +19,6 @@ class ProjectListParams(TypedDict, total=False): order: Literal["asc", "desc"] - organization_id: str - query: Optional[str] sort: Literal["created_at", "updated_at"] diff --git a/src/codex/types/projects/access_key_create_params.py b/src/codex/types/projects/access_key_create_params.py index 5035836d..cf5f00fb 100644 --- a/src/codex/types/projects/access_key_create_params.py +++ b/src/codex/types/projects/access_key_create_params.py @@ -18,8 +18,6 @@ class AccessKeyCreateParams(TypedDict, total=False): expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")] - x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] diff --git a/src/codex/types/projects/entry_create_params.py b/src/codex/types/projects/entry_create_params.py index 490b0e83..f06846bb 100644 --- a/src/codex/types/projects/entry_create_params.py +++ b/src/codex/types/projects/entry_create_params.py @@ -19,8 +19,6 @@ class EntryCreateParams(TypedDict, total=False): draft_answer: Optional[str] - x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")] - x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] diff --git a/src/codex/types/projects/entry_query_params.py b/src/codex/types/projects/entry_query_params.py index db1aec77..d58b7bfa 100644 --- a/src/codex/types/projects/entry_query_params.py +++ b/src/codex/types/projects/entry_query_params.py @@ -17,8 +17,6 @@ class EntryQueryParams(TypedDict, total=False): client_metadata: Optional[object] - x_access_key: Annotated[str, PropertyInfo(alias="x-access-key")] - x_client_library_version: Annotated[str, PropertyInfo(alias="x-client-library-version")] x_integration_type: Annotated[str, PropertyInfo(alias="x-integration-type")] diff --git a/tests/api_resources/projects/test_access_keys.py b/tests/api_resources/projects/test_access_keys.py index bcf3bb7a..ad4ee5e4 100644 --- a/tests/api_resources/projects/test_access_keys.py +++ b/tests/api_resources/projects/test_access_keys.py @@ -39,7 +39,6 @@ def test_method_create_with_all_params(self, client: Codex) -> None: name="name", description="description", expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), - x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -400,7 +399,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> name="name", description="description", expires_at=parse_datetime("2019-12-27T18:11:19.117Z"), - x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", diff --git a/tests/api_resources/projects/test_entries.py b/tests/api_resources/projects/test_entries.py index 710d2146..31a5e408 100644 --- a/tests/api_resources/projects/test_entries.py +++ b/tests/api_resources/projects/test_entries.py @@ -39,7 +39,6 @@ def test_method_create_with_all_params(self, client: Codex) -> None: answer="answer", client_query_metadata=[{}], draft_answer="draft_answer", - x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -397,7 +396,6 @@ def test_method_query_with_all_params(self, client: Codex) -> None: question="question", use_llm_matching=True, client_metadata={}, - x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -516,7 +514,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncCodex) -> answer="answer", client_query_metadata=[{}], draft_answer="draft_answer", - x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", @@ -874,7 +871,6 @@ async def test_method_query_with_all_params(self, async_client: AsyncCodex) -> N question="question", use_llm_matching=True, client_metadata={}, - x_access_key="x-access-key", x_client_library_version="x-client-library-version", x_integration_type="x-integration-type", x_source="x-source", diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index d71d0e19..bfa657d2 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -194,18 +194,20 @@ def test_path_params_update(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_method_list(self, client: Codex) -> None: - project = client.projects.list() + project = client.projects.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert_matches_type(ProjectListResponse, project, path=["response"]) @pytest.mark.skip() @parametrize def test_method_list_with_all_params(self, client: Codex) -> None: project = client.projects.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", include_entry_counts=True, limit=0, offset=0, order="asc", - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", query="query", sort="created_at", ) @@ -214,7 +216,9 @@ def test_method_list_with_all_params(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_raw_response_list(self, client: Codex) -> None: - response = client.projects.with_raw_response.list() + response = client.projects.with_raw_response.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -224,7 +228,9 @@ def test_raw_response_list(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_streaming_response_list(self, client: Codex) -> None: - with client.projects.with_streaming_response.list() as response: + with client.projects.with_streaming_response.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -536,18 +542,20 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None: @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: - project = await async_client.projects.list() + project = await async_client.projects.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert_matches_type(ProjectListResponse, project, path=["response"]) @pytest.mark.skip() @parametrize async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", include_entry_counts=True, limit=0, offset=0, order="asc", - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", query="query", sort="created_at", ) @@ -556,7 +564,9 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No @pytest.mark.skip() @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.with_raw_response.list() + response = await async_client.projects.with_raw_response.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -566,7 +576,9 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: @pytest.mark.skip() @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: - async with async_client.projects.with_streaming_response.list() as response: + async with async_client.projects.with_streaming_response.list( + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From ed0b337167c713bebd54c21fe587ee05c57a90d4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 23:16:17 +0000 Subject: [PATCH 09/17] feat(api): api update --- .stats.yml | 2 +- src/codex/resources/projects/projects.py | 8 +++---- src/codex/types/project_list_params.py | 6 ++--- tests/api_resources/test_projects.py | 28 +++++++----------------- 4 files changed, 16 insertions(+), 28 deletions(-) diff --git a/.stats.yml b/.stats.yml index aa1535bd..42e47500 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 42 -openapi_spec_hash: 71ff1de391293cdfb6dcb761ed89210d +openapi_spec_hash: 684572da9b97ec2c9acf3ea698c7ce12 config_hash: 2d88a0a41f5faca603ff2789a116d988 diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 47639a5a..76237fb6 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -208,11 +208,11 @@ def update( def list( self, *, - organization_id: str, include_entry_counts: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + organization_id: str | NotGiven = NOT_GIVEN, query: Optional[str] | NotGiven = NOT_GIVEN, sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -243,11 +243,11 @@ def list( timeout=timeout, query=maybe_transform( { - "organization_id": organization_id, "include_entry_counts": include_entry_counts, "limit": limit, "offset": offset, "order": order, + "organization_id": organization_id, "query": query, "sort": sort, }, @@ -513,11 +513,11 @@ async def update( async def list( self, *, - organization_id: str, include_entry_counts: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, offset: int | NotGiven = NOT_GIVEN, order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + organization_id: str | NotGiven = NOT_GIVEN, query: Optional[str] | NotGiven = NOT_GIVEN, sort: Literal["created_at", "updated_at"] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -548,11 +548,11 @@ async def list( timeout=timeout, query=await async_maybe_transform( { - "organization_id": organization_id, "include_entry_counts": include_entry_counts, "limit": limit, "offset": offset, "order": order, + "organization_id": organization_id, "query": query, "sort": sort, }, diff --git a/src/codex/types/project_list_params.py b/src/codex/types/project_list_params.py index c2589598..0ab3b84b 100644 --- a/src/codex/types/project_list_params.py +++ b/src/codex/types/project_list_params.py @@ -3,14 +3,12 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Literal, Required, TypedDict +from typing_extensions import Literal, TypedDict __all__ = ["ProjectListParams"] class ProjectListParams(TypedDict, total=False): - organization_id: Required[str] - include_entry_counts: bool limit: int @@ -19,6 +17,8 @@ class ProjectListParams(TypedDict, total=False): order: Literal["asc", "desc"] + organization_id: str + query: Optional[str] sort: Literal["created_at", "updated_at"] diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index bfa657d2..d71d0e19 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -194,20 +194,18 @@ def test_path_params_update(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_method_list(self, client: Codex) -> None: - project = client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + project = client.projects.list() assert_matches_type(ProjectListResponse, project, path=["response"]) @pytest.mark.skip() @parametrize def test_method_list_with_all_params(self, client: Codex) -> None: project = client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", include_entry_counts=True, limit=0, offset=0, order="asc", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", query="query", sort="created_at", ) @@ -216,9 +214,7 @@ def test_method_list_with_all_params(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_raw_response_list(self, client: Codex) -> None: - response = client.projects.with_raw_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + response = client.projects.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -228,9 +224,7 @@ def test_raw_response_list(self, client: Codex) -> None: @pytest.mark.skip() @parametrize def test_streaming_response_list(self, client: Codex) -> None: - with client.projects.with_streaming_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: + with client.projects.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -542,20 +536,18 @@ async def test_path_params_update(self, async_client: AsyncCodex) -> None: @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncCodex) -> None: - project = await async_client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + project = await async_client.projects.list() assert_matches_type(ProjectListResponse, project, path=["response"]) @pytest.mark.skip() @parametrize async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> None: project = await async_client.projects.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", include_entry_counts=True, limit=0, offset=0, order="asc", + organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", query="query", sort="created_at", ) @@ -564,9 +556,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncCodex) -> No @pytest.mark.skip() @parametrize async def test_raw_response_list(self, async_client: AsyncCodex) -> None: - response = await async_client.projects.with_raw_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) + response = await async_client.projects.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -576,9 +566,7 @@ async def test_raw_response_list(self, async_client: AsyncCodex) -> None: @pytest.mark.skip() @parametrize async def test_streaming_response_list(self, async_client: AsyncCodex) -> None: - async with async_client.projects.with_streaming_response.list( - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: + async with async_client.projects.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From bcc2ed013f1e88ac4f97bdb0dc7e47364276e182 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:25:10 +0000 Subject: [PATCH 10/17] chore(ci): add timeout thresholds for CI jobs --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8b72361..1e4dab9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ on: jobs: lint: + timeout-minutes: 10 name: lint runs-on: ubuntu-latest steps: From bcf89d29f760b4eb2e9b7534034adfc28582ea03 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:25:42 +0000 Subject: [PATCH 11/17] chore(internal): import reformatting --- src/codex/_client.py | 5 +---- src/codex/resources/projects/access_keys.py | 6 +----- src/codex/resources/projects/entries.py | 6 +----- src/codex/resources/projects/projects.py | 5 +---- src/codex/resources/tlm.py | 5 +---- src/codex/resources/users/users.py | 5 +---- 6 files changed, 6 insertions(+), 26 deletions(-) diff --git a/src/codex/_client.py b/src/codex/_client.py index 2641513f..7464f6dc 100644 --- a/src/codex/_client.py +++ b/src/codex/_client.py @@ -20,10 +20,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 tlm, health from ._streaming import Stream as Stream, AsyncStream as AsyncStream diff --git a/src/codex/resources/projects/access_keys.py b/src/codex/resources/projects/access_keys.py index 61987399..15190030 100644 --- a/src/codex/resources/projects/access_keys.py +++ b/src/codex/resources/projects/access_keys.py @@ -8,11 +8,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import ( - maybe_transform, - strip_not_given, - async_maybe_transform, -) +from ..._utils import maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/codex/resources/projects/entries.py b/src/codex/resources/projects/entries.py index f88e2243..a9e690b9 100644 --- a/src/codex/resources/projects/entries.py +++ b/src/codex/resources/projects/entries.py @@ -7,11 +7,7 @@ import httpx from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import ( - maybe_transform, - strip_not_given, - async_maybe_transform, -) +from ..._utils import maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 76237fb6..95bcd3a3 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -17,10 +17,7 @@ AsyncEntriesResourceWithStreamingResponse, ) 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 .clusters import ( ClustersResource, AsyncClustersResource, diff --git a/src/codex/resources/tlm.py b/src/codex/resources/tlm.py index c6585d0d..78f97e2e 100644 --- a/src/codex/resources/tlm.py +++ b/src/codex/resources/tlm.py @@ -9,10 +9,7 @@ from ..types import tlm_score_params, tlm_prompt_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/codex/resources/users/users.py b/src/codex/resources/users/users.py index c276f950..d207a96d 100644 --- a/src/codex/resources/users/users.py +++ b/src/codex/resources/users/users.py @@ -9,10 +9,7 @@ from ...types import user_activate_account_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 ( From 2e9cf89c75d12ed44ace20998aa7245a8b241922 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:27:01 +0000 Subject: [PATCH 12/17] chore(internal): fix list file params --- src/codex/_utils/_utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/codex/_utils/_utils.py b/src/codex/_utils/_utils.py index e5811bba..ea3cf3f2 100644 --- a/src/codex/_utils/_utils.py +++ b/src/codex/_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 From 2f3a24a40294e21f2e3d9e9464796750ff25b51e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:27:27 +0000 Subject: [PATCH 13/17] chore(internal): refactor retries to not use recursion --- src/codex/_base_client.py | 414 ++++++++++++++++---------------------- 1 file changed, 175 insertions(+), 239 deletions(-) diff --git a/src/codex/_base_client.py b/src/codex/_base_client.py index 69be76db..0bf6ca7f 100644 --- a/src/codex/_base_client.py +++ b/src/codex/_base_client.py @@ -437,8 +437,7 @@ 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: - options.idempotency_key = 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 @@ -903,7 +902,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[True], stream_cls: Type[_StreamT], @@ -914,7 +912,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[False] = False, ) -> ResponseT: ... @@ -924,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, @@ -934,125 +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) - - 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) - - if options.idempotency_key: + 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 = options.idempotency_key + input_options.idempotency_key = self._idempotency_key() - 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, @@ -1062,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, *, @@ -1436,7 +1399,6 @@ async def request( options: FinalRequestOptions, *, stream: Literal[False] = False, - remaining_retries: Optional[int] = None, ) -> ResponseT: ... @overload @@ -1447,7 +1409,6 @@ async def request( *, stream: Literal[True], stream_cls: type[_AsyncStreamT], - remaining_retries: Optional[int] = None, ) -> _AsyncStreamT: ... @overload @@ -1458,7 +1419,6 @@ async def request( *, stream: bool, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, ) -> ResponseT | _AsyncStreamT: ... async def request( @@ -1468,120 +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) - - cast_to = self._maybe_override_cast_to(cast_to, options) - options = await self._prepare_options(options) - - 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) - - if options.idempotency_key: + 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 = options.idempotency_key + input_options.idempotency_key = self._idempotency_key() - 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) - 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) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = await self._prepare_options(options) - 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, - ) + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + await self._prepare_request(request) - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - 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, - ) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err + 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( - 'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase - ) + 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 - 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, - ) + # 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() - # 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 - 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, @@ -1591,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, *, From 2db34b6bcbd0b62c6474146d50c259244f13f84f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:28:00 +0000 Subject: [PATCH 14/17] fix(pydantic v1): more robust ModelField.annotation check --- src/codex/_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codex/_models.py b/src/codex/_models.py index 58b9263e..798956f1 100644 --- a/src/codex/_models.py +++ b/src/codex/_models.py @@ -626,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 From 9dec3acb85c55372a1fa629197bd459562980382 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:16:17 +0000 Subject: [PATCH 15/17] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 42e47500..185dd58f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ configured_endpoints: 42 -openapi_spec_hash: 684572da9b97ec2c9acf3ea698c7ce12 +openapi_spec_hash: 62b629dd5b215c1eebc57e0c6039eea7 config_hash: 2d88a0a41f5faca603ff2789a116d988 From ae79a63eb48beb6c4c72fd99e39c8de37915e028 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:53:25 +0000 Subject: [PATCH 16/17] feat(api): add proj analytics endpoint --- .stats.yml | 4 +- api.md | 2 + src/codex/resources/projects/projects.py | 126 +++++++++++++++++- src/codex/types/__init__.py | 2 + .../project_retrieve_analytics_params.py | 18 +++ .../project_retrieve_analytics_response.py | 43 ++++++ tests/api_resources/test_projects.py | 107 +++++++++++++++ 7 files changed, 299 insertions(+), 3 deletions(-) create mode 100644 src/codex/types/project_retrieve_analytics_params.py create mode 100644 src/codex/types/project_retrieve_analytics_response.py diff --git a/.stats.yml b/.stats.yml index 185dd58f..230d2377 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,3 +1,3 @@ -configured_endpoints: 42 +configured_endpoints: 43 openapi_spec_hash: 62b629dd5b215c1eebc57e0c6039eea7 -config_hash: 2d88a0a41f5faca603ff2789a116d988 +config_hash: 5e459b33c53ffa6e554087a779bdb790 diff --git a/api.md b/api.md index 617cdd24..f240632f 100644 --- a/api.md +++ b/api.md @@ -141,6 +141,7 @@ from codex.types import ( ProjectListResponse, ProjectExportResponse, ProjectIncrementQueriesResponse, + ProjectRetrieveAnalyticsResponse, ) ``` @@ -153,6 +154,7 @@ Methods: - client.projects.delete(project_id) -> None - client.projects.export(project_id) -> object - client.projects.increment_queries(project_id) -> object +- client.projects.retrieve_analytics(project_id, \*\*params) -> ProjectRetrieveAnalyticsResponse ## AccessKeys diff --git a/src/codex/resources/projects/projects.py b/src/codex/resources/projects/projects.py index 95bcd3a3..6ac036c0 100644 --- a/src/codex/resources/projects/projects.py +++ b/src/codex/resources/projects/projects.py @@ -7,7 +7,12 @@ import httpx -from ...types import project_list_params, project_create_params, project_update_params +from ...types import ( + project_list_params, + project_create_params, + project_update_params, + project_retrieve_analytics_params, +) from .entries import ( EntriesResource, AsyncEntriesResource, @@ -46,6 +51,7 @@ from ...types.project_list_response import ProjectListResponse from ...types.project_return_schema import ProjectReturnSchema from ...types.project_retrieve_response import ProjectRetrieveResponse +from ...types.project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse __all__ = ["ProjectsResource", "AsyncProjectsResource"] @@ -354,6 +360,59 @@ def increment_queries( cast_to=object, ) + def retrieve_analytics( + self, + project_id: str, + *, + end: int | NotGiven = NOT_GIVEN, + sme_limit: int | NotGiven = NOT_GIVEN, + start: int | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> ProjectRetrieveAnalyticsResponse: + """ + Get Project Analytics Route + + Args: + end: End timestamp in seconds since epoch + + sme_limit: Limit the number of top SME contributors to return. + + start: Start timestamp in seconds since epoch + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return self._get( + f"/api/projects/{project_id}/analytics/", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "end": end, + "sme_limit": sme_limit, + "start": start, + }, + project_retrieve_analytics_params.ProjectRetrieveAnalyticsParams, + ), + ), + cast_to=ProjectRetrieveAnalyticsResponse, + ) + class AsyncProjectsResource(AsyncAPIResource): @cached_property @@ -659,6 +718,59 @@ async def increment_queries( cast_to=object, ) + async def retrieve_analytics( + self, + project_id: str, + *, + end: int | NotGiven = NOT_GIVEN, + sme_limit: int | NotGiven = NOT_GIVEN, + start: int | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> ProjectRetrieveAnalyticsResponse: + """ + Get Project Analytics Route + + Args: + end: End timestamp in seconds since epoch + + sme_limit: Limit the number of top SME contributors to return. + + start: Start timestamp in seconds since epoch + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not project_id: + raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}") + return await self._get( + f"/api/projects/{project_id}/analytics/", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "end": end, + "sme_limit": sme_limit, + "start": start, + }, + project_retrieve_analytics_params.ProjectRetrieveAnalyticsParams, + ), + ), + cast_to=ProjectRetrieveAnalyticsResponse, + ) + class ProjectsResourceWithRawResponse: def __init__(self, projects: ProjectsResource) -> None: @@ -685,6 +797,9 @@ def __init__(self, projects: ProjectsResource) -> None: self.increment_queries = to_raw_response_wrapper( projects.increment_queries, ) + self.retrieve_analytics = to_raw_response_wrapper( + projects.retrieve_analytics, + ) @cached_property def access_keys(self) -> AccessKeysResourceWithRawResponse: @@ -724,6 +839,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.increment_queries = async_to_raw_response_wrapper( projects.increment_queries, ) + self.retrieve_analytics = async_to_raw_response_wrapper( + projects.retrieve_analytics, + ) @cached_property def access_keys(self) -> AsyncAccessKeysResourceWithRawResponse: @@ -763,6 +881,9 @@ def __init__(self, projects: ProjectsResource) -> None: self.increment_queries = to_streamed_response_wrapper( projects.increment_queries, ) + self.retrieve_analytics = to_streamed_response_wrapper( + projects.retrieve_analytics, + ) @cached_property def access_keys(self) -> AccessKeysResourceWithStreamingResponse: @@ -802,6 +923,9 @@ def __init__(self, projects: AsyncProjectsResource) -> None: self.increment_queries = async_to_streamed_response_wrapper( projects.increment_queries, ) + self.retrieve_analytics = async_to_streamed_response_wrapper( + projects.retrieve_analytics, + ) @cached_property def access_keys(self) -> AsyncAccessKeysResourceWithStreamingResponse: diff --git a/src/codex/types/__init__.py b/src/codex/types/__init__.py index 008c4a67..53d1ab6b 100644 --- a/src/codex/types/__init__.py +++ b/src/codex/types/__init__.py @@ -15,7 +15,9 @@ from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse from .organization_schema_public import OrganizationSchemaPublic as OrganizationSchemaPublic from .user_activate_account_params import UserActivateAccountParams as UserActivateAccountParams +from .project_retrieve_analytics_params import ProjectRetrieveAnalyticsParams as ProjectRetrieveAnalyticsParams from .organization_list_members_response import OrganizationListMembersResponse as OrganizationListMembersResponse +from .project_retrieve_analytics_response import ProjectRetrieveAnalyticsResponse as ProjectRetrieveAnalyticsResponse from .organization_retrieve_permissions_response import ( OrganizationRetrievePermissionsResponse as OrganizationRetrievePermissionsResponse, ) diff --git a/src/codex/types/project_retrieve_analytics_params.py b/src/codex/types/project_retrieve_analytics_params.py new file mode 100644 index 00000000..aadb4159 --- /dev/null +++ b/src/codex/types/project_retrieve_analytics_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ProjectRetrieveAnalyticsParams"] + + +class ProjectRetrieveAnalyticsParams(TypedDict, total=False): + end: int + """End timestamp in seconds since epoch""" + + sme_limit: int + """Limit the number of top SME contributors to return.""" + + start: int + """Start timestamp in seconds since epoch""" diff --git a/src/codex/types/project_retrieve_analytics_response.py b/src/codex/types/project_retrieve_analytics_response.py new file mode 100644 index 00000000..f17975ec --- /dev/null +++ b/src/codex/types/project_retrieve_analytics_response.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List + +from .._models import BaseModel + +__all__ = [ + "ProjectRetrieveAnalyticsResponse", + "AnswersPublished", + "AnswersPublishedAnswersByAuthor", + "BadResponses", + "Queries", +] + + +class AnswersPublishedAnswersByAuthor(BaseModel): + answers_published: int + + name: str + + user_id: str + + +class AnswersPublished(BaseModel): + answers_by_author: List[AnswersPublishedAnswersByAuthor] + + +class BadResponses(BaseModel): + responses_by_type: Dict[str, int] + + total: int + + +class Queries(BaseModel): + total: int + + +class ProjectRetrieveAnalyticsResponse(BaseModel): + answers_published: AnswersPublished + + bad_responses: BadResponses + + queries: Queries diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index d71d0e19..3a8f0ec8 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -12,6 +12,7 @@ ProjectListResponse, ProjectReturnSchema, ProjectRetrieveResponse, + ProjectRetrieveAnalyticsResponse, ) from tests.utils import assert_matches_type @@ -359,6 +360,59 @@ def test_path_params_increment_queries(self, client: Codex) -> None: "", ) + @pytest.mark.skip() + @parametrize + def test_method_retrieve_analytics(self, client: Codex) -> None: + project = client.projects.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_retrieve_analytics_with_all_params(self, client: Codex) -> None: + project = client.projects.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + end=0, + sme_limit=1, + start=0, + ) + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_retrieve_analytics(self, client: Codex) -> None: + response = client.projects.with_raw_response.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = response.parse() + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_retrieve_analytics(self, client: Codex) -> None: + with client.projects.with_streaming_response.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = response.parse() + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_retrieve_analytics(self, client: Codex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + client.projects.with_raw_response.retrieve_analytics( + project_id="", + ) + class TestAsyncProjects: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -700,3 +754,56 @@ async def test_path_params_increment_queries(self, async_client: AsyncCodex) -> await async_client.projects.with_raw_response.increment_queries( "", ) + + @pytest.mark.skip() + @parametrize + async def test_method_retrieve_analytics(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_retrieve_analytics_with_all_params(self, async_client: AsyncCodex) -> None: + project = await async_client.projects.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + end=0, + sme_limit=1, + start=0, + ) + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_retrieve_analytics(self, async_client: AsyncCodex) -> None: + response = await async_client.projects.with_raw_response.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + project = await response.parse() + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_retrieve_analytics(self, async_client: AsyncCodex) -> None: + async with async_client.projects.with_streaming_response.retrieve_analytics( + project_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + project = await response.parse() + assert_matches_type(ProjectRetrieveAnalyticsResponse, project, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_retrieve_analytics(self, async_client: AsyncCodex) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"): + await async_client.projects.with_raw_response.retrieve_analytics( + project_id="", + ) From f6fecb22eb94625d5784c4480428261c9f152ecb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:53:43 +0000 Subject: [PATCH 17/17] release: 0.1.0-alpha.17 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/codex/_version.py | 2 +- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7e56fe29..e2f2c074 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.16" + ".": "0.1.0-alpha.17" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 658e0649..3e462359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog +## 0.1.0-alpha.17 (2025-04-23) + +Full Changelog: [v0.1.0-alpha.16...v0.1.0-alpha.17](https://github.com/cleanlab/codex-python/compare/v0.1.0-alpha.16...v0.1.0-alpha.17) + +### Features + +* **api:** add proj analytics endpoint ([ae79a63](https://github.com/cleanlab/codex-python/commit/ae79a63eb48beb6c4c72fd99e39c8de37915e028)) +* **api:** add project increment_queries and other recent endpoints ([5bf31f7](https://github.com/cleanlab/codex-python/commit/5bf31f74b6008c9bda7b4ec290b1216a51ef9d1f)) +* **api:** add project increment_queries and other recent endpoints ([e81cc3d](https://github.com/cleanlab/codex-python/commit/e81cc3d451c7c8d163c0b80d983140506a0adb66)) +* **api:** add project increment_queries and other recent endpoints ([b038fbe](https://github.com/cleanlab/codex-python/commit/b038fbe3e90f3096b0913256db9e31ca52cd4001)) +* **api:** api update ([ed0b337](https://github.com/cleanlab/codex-python/commit/ed0b337167c713bebd54c21fe587ee05c57a90d4)) +* **api:** api update ([3f85757](https://github.com/cleanlab/codex-python/commit/3f857575176d200a639880dfa7e60e3a9949750c)) +* **api:** api update ([4f39e46](https://github.com/cleanlab/codex-python/commit/4f39e46167b88a0c7198bdd8ad7d31bf118d485c)) + + +### Bug Fixes + +* **pydantic v1:** more robust ModelField.annotation check ([2db34b6](https://github.com/cleanlab/codex-python/commit/2db34b6bcbd0b62c6474146d50c259244f13f84f)) + + +### Chores + +* **ci:** add timeout thresholds for CI jobs ([bcc2ed0](https://github.com/cleanlab/codex-python/commit/bcc2ed013f1e88ac4f97bdb0dc7e47364276e182)) +* **internal:** fix list file params ([2e9cf89](https://github.com/cleanlab/codex-python/commit/2e9cf89c75d12ed44ace20998aa7245a8b241922)) +* **internal:** import reformatting ([bcf89d2](https://github.com/cleanlab/codex-python/commit/bcf89d29f760b4eb2e9b7534034adfc28582ea03)) +* **internal:** refactor retries to not use recursion ([2f3a24a](https://github.com/cleanlab/codex-python/commit/2f3a24a40294e21f2e3d9e9464796750ff25b51e)) +* **internal:** update models test ([444cd03](https://github.com/cleanlab/codex-python/commit/444cd038fc03a545fc2da0a6e5ab14ea84102408)) +* **internal:** version bump ([03cd933](https://github.com/cleanlab/codex-python/commit/03cd93334de3db9beea06ae0f62bcb0ab61466a4)) +* **internal:** version bump ([88821c7](https://github.com/cleanlab/codex-python/commit/88821c710de290b37a6dbc9a81ef0a1cc396db62)) + ## 0.1.0-alpha.16 (2025-04-18) Full Changelog: [v0.1.0-alpha.15...v0.1.0-alpha.16](https://github.com/cleanlab/codex-python/compare/v0.1.0-alpha.15...v0.1.0-alpha.16) diff --git a/pyproject.toml b/pyproject.toml index b98b79a6..130b32e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codex-sdk" -version = "0.1.0-alpha.16" +version = "0.1.0-alpha.17" description = "Internal SDK used within cleanlab-codex package. Refer to https://pypi.org/project/cleanlab-codex/ instead." dynamic = ["readme"] license = "MIT" diff --git a/src/codex/_version.py b/src/codex/_version.py index b99310c3..264d49e0 100644 --- a/src/codex/_version.py +++ b/src/codex/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "codex" -__version__ = "0.1.0-alpha.16" # x-release-please-version +__version__ = "0.1.0-alpha.17" # x-release-please-version