From ddd9fac7031757a440cc616884f862be84a7d68d Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Thu, 29 Oct 2026 17:27:25 +0100 Subject: [PATCH 1/5] feat: Add missing multi-tenant-related params to Actor update --- src/apify_client/_resource_clients/actor.py | 8 ++++++++ src/apify_client/_resource_clients/actor_collection.py | 8 ++++++++ tests/unit/test_actor_update_fields.py | 7 ++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/apify_client/_resource_clients/actor.py b/src/apify_client/_resource_clients/actor.py index 6bd06fbb..53d20558 100644 --- a/src/apify_client/_resource_clients/actor.py +++ b/src/apify_client/_resource_clients/actor.py @@ -131,6 +131,7 @@ def update( actor_standby_memory_mbytes: int | None = None, actor_standby_disable_standby_fields_override: bool | None = None, actor_standby_should_pass_actor_input: bool | None = None, + actor_standby_tenancy: str | None = None, pricing_infos: list[dict[str, Any]] | None = None, actor_permission_level: ActorPermissionLevel | None = None, tagged_builds: dict[str, dict[str, str] | None] | None = None, @@ -175,6 +176,8 @@ def update( overridden elsewhere. actor_standby_should_pass_actor_input: Whether to pass the Actor input to the Standby runs. If false, the Standby runs start with no input. + actor_standby_tenancy: Whether each user gets their own Standby runs (`SINGLE_TENANT`) or the runs are + shared across users (`MULTI_TENANT`). pricing_infos: A list of objects that describes the pricing of the Actor. actor_permission_level: The permission level of the Actor on Apify platform. tagged_builds: A dictionary mapping build tag names to their settings. Use it to create, update, @@ -214,6 +217,7 @@ def update( memory_mbytes=actor_standby_memory_mbytes, disable_standby_fields_override=actor_standby_disable_standby_fields_override, should_pass_actor_input=actor_standby_should_pass_actor_input, + tenancy=actor_standby_tenancy, ), example_run_input=ExampleRunInput( body=example_run_input_body, @@ -691,6 +695,7 @@ async def update( actor_standby_memory_mbytes: int | None = None, actor_standby_disable_standby_fields_override: bool | None = None, actor_standby_should_pass_actor_input: bool | None = None, + actor_standby_tenancy: str | None = None, pricing_infos: list[dict[str, Any]] | None = None, actor_permission_level: ActorPermissionLevel | None = None, tagged_builds: dict[str, dict[str, str] | None] | None = None, @@ -735,6 +740,8 @@ async def update( overridden elsewhere. actor_standby_should_pass_actor_input: Whether to pass the Actor input to the Standby runs. If false, the Standby runs start with no input. + actor_standby_tenancy: Whether each user gets their own Standby runs (`SINGLE_TENANT`) or the runs are + shared across users (`MULTI_TENANT`). pricing_infos: A list of objects that describes the pricing of the Actor. actor_permission_level: The permission level of the Actor on Apify platform. tagged_builds: A dictionary mapping build tag names to their settings. Use it to create, update, @@ -774,6 +781,7 @@ async def update( memory_mbytes=actor_standby_memory_mbytes, disable_standby_fields_override=actor_standby_disable_standby_fields_override, should_pass_actor_input=actor_standby_should_pass_actor_input, + tenancy=actor_standby_tenancy, ), example_run_input=ExampleRunInput( body=example_run_input_body, diff --git a/src/apify_client/_resource_clients/actor_collection.py b/src/apify_client/_resource_clients/actor_collection.py index 3d20423b..1081da73 100644 --- a/src/apify_client/_resource_clients/actor_collection.py +++ b/src/apify_client/_resource_clients/actor_collection.py @@ -137,6 +137,7 @@ def create( actor_standby_memory_mbytes: int | None = None, actor_standby_disable_standby_fields_override: bool | None = None, actor_standby_should_pass_actor_input: bool | None = None, + actor_standby_tenancy: str | None = None, actor_permission_level: ActorPermissionLevel | None = None, timeout: Timeout = 'medium', ) -> ActorResource: @@ -179,6 +180,8 @@ def create( overridden elsewhere. actor_standby_should_pass_actor_input: Whether to pass the Actor input to the Standby runs. If false, the Standby runs start with no input. + actor_standby_tenancy: Whether each user gets their own Standby runs (`SINGLE_TENANT`) or the runs are + shared across users (`MULTI_TENANT`). actor_permission_level: The permission level of the Actor on Apify platform. timeout: Timeout for the API HTTP request. @@ -213,6 +216,7 @@ def create( memory_mbytes=actor_standby_memory_mbytes, disable_standby_fields_override=actor_standby_disable_standby_fields_override, should_pass_actor_input=actor_standby_should_pass_actor_input, + tenancy=actor_standby_tenancy, ), example_run_input=ExampleRunInput( body=example_run_input_body, @@ -334,6 +338,7 @@ async def create( actor_standby_memory_mbytes: int | None = None, actor_standby_disable_standby_fields_override: bool | None = None, actor_standby_should_pass_actor_input: bool | None = None, + actor_standby_tenancy: str | None = None, actor_permission_level: ActorPermissionLevel | None = None, timeout: Timeout = 'medium', ) -> ActorResource: @@ -376,6 +381,8 @@ async def create( overridden elsewhere. actor_standby_should_pass_actor_input: Whether to pass the Actor input to the Standby runs. If false, the Standby runs start with no input. + actor_standby_tenancy: Whether each user gets their own Standby runs (`SINGLE_TENANT`) or the runs are + shared across users (`MULTI_TENANT`). actor_permission_level: The permission level of the Actor on Apify platform. timeout: Timeout for the API HTTP request. @@ -410,6 +417,7 @@ async def create( memory_mbytes=actor_standby_memory_mbytes, disable_standby_fields_override=actor_standby_disable_standby_fields_override, should_pass_actor_input=actor_standby_should_pass_actor_input, + tenancy=actor_standby_tenancy, ), example_run_input=ExampleRunInput( body=example_run_input_body, diff --git a/tests/unit/test_actor_update_fields.py b/tests/unit/test_actor_update_fields.py index d6060b0e..6910b2c2 100644 --- a/tests/unit/test_actor_update_fields.py +++ b/tests/unit/test_actor_update_fields.py @@ -37,10 +37,15 @@ _FIELDS: dict[str, Any] = { 'actor_standby_disable_standby_fields_override': True, 'actor_standby_should_pass_actor_input': False, + 'actor_standby_tenancy': 'MULTI_TENANT', } _EXPECTED_BODY = { - 'actorStandby': {'disableStandbyFieldsOverride': True, 'shouldPassActorInput': False}, + 'actorStandby': { + 'disableStandbyFieldsOverride': True, + 'shouldPassActorInput': False, + 'tenancy': 'MULTI_TENANT', + }, } From 805bcc74655931f2bef479e7a7efe0884fb296f9 Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Mon, 2 Nov 2026 16:19:46 +0100 Subject: [PATCH 2/5] Change test fixture to correspond with platform behavior better --- tests/unit/test_actor_update_fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_actor_update_fields.py b/tests/unit/test_actor_update_fields.py index 6910b2c2..445e9258 100644 --- a/tests/unit/test_actor_update_fields.py +++ b/tests/unit/test_actor_update_fields.py @@ -37,14 +37,14 @@ _FIELDS: dict[str, Any] = { 'actor_standby_disable_standby_fields_override': True, 'actor_standby_should_pass_actor_input': False, - 'actor_standby_tenancy': 'MULTI_TENANT', + 'actor_standby_tenancy': 'SINGLE_TENANT', } _EXPECTED_BODY = { 'actorStandby': { 'disableStandbyFieldsOverride': True, 'shouldPassActorInput': False, - 'tenancy': 'MULTI_TENANT', + 'tenancy': 'SINGLE_TENANT', }, } From 014aab49bdb03c7d3f7c092e27f3d987a1343ecc Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Thu, 5 Nov 2026 11:41:37 +0100 Subject: [PATCH 3/5] Fix Tenancy enum generation and use it --- pyproject.toml | 1 + scripts/postprocess_generated_models.py | 7 +++-- src/apify_client/_literals.py | 25 +++++++++++++++ src/apify_client/_models.py | 31 ++----------------- src/apify_client/_resource_clients/actor.py | 6 ++-- .../_resource_clients/actor_collection.py | 6 ++-- 6 files changed, 39 insertions(+), 37 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0c5e7c81..97fb1a4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -168,6 +168,7 @@ indent-style = "space" "TC003", # Move standard library import into a type-checking block ] "src/apify_client/_literals.py" = [ + "E501", # Line too long "PYI051", # `Literal[...]` is redundant in a union with `str`; the open `Literal[...] | str` is deliberate — it keeps known members as hints while tolerating unknown API values (see scripts/postprocess_generated_models.py) ] diff --git a/scripts/postprocess_generated_models.py b/scripts/postprocess_generated_models.py index e35a01b1..d1b9ff86 100644 --- a/scripts/postprocess_generated_models.py +++ b/scripts/postprocess_generated_models.py @@ -114,10 +114,11 @@ def fix_discriminators(content: str) -> str: def convert_enums_to_literals(content: str) -> str: - """Rewrite every `class X(StrEnum): ...` into an open literal `X = Literal[...] | str` alias. + """Rewrite every `class X(StrEnum): ...` (or `Enum`) into an open literal `X = Literal[...] | str` alias. Each member assignment (`NAME = 'value'`) contributes its string value to the literal in - declaration order. The trailing `| str` widens the alias so unknown values (e.g. a new enum + declaration order; a `None` member of a nullable enum is dropped, since the fields using it are already + `X | None`. The trailing `| str` widens the alias so unknown values (e.g. a new enum member the platform introduces before this client is regenerated) still validate against the Pydantic models, while the known members remain as autocomplete hints. The class docstring, if present, is preserved as a trailing bare-string docstring after the alias — matching the @@ -134,7 +135,7 @@ def convert_enums_to_literals(content: str) -> str: if not isinstance(node, ast.ClassDef): continue base_names = _base_names(node) - if 'StrEnum' not in base_names: + if 'StrEnum' not in base_names and 'Enum' not in base_names: continue values: list[str] = [ diff --git a/src/apify_client/_literals.py b/src/apify_client/_literals.py index de56efd5..a65af7c7 100644 --- a/src/apify_client/_literals.py +++ b/src/apify_client/_literals.py @@ -20,6 +20,17 @@ """Status of an Actor job (run or build).""" +ActorNotice = ( + Literal[ + 'NONE', + 'RESIDENTIAL_PROXY_REQUIRED', + 'UNDER_MAINTENANCE', + ] + | str +) +"""A warning displayed on the Actor's page in Apify Store and Console. Can be set by the Actor's developer or automatically by Apify's quality checks.""" + + ActorPermissionLevel = ( Literal[ 'LIMITED_PERMISSIONS', @@ -495,6 +506,10 @@ ) +MonitoringLevel = Literal['STANDARD',] | str +"""Level of monitoring services included in the plan. `null` or missing means the plan includes no monitoring service.""" + + OAuthProviderType = ( Literal[ 'CUSTOM', @@ -550,6 +565,16 @@ ) +Tenancy = ( + Literal[ + 'SINGLE_TENANT', + 'MULTI_TENANT', + ] + | str +) +"""Whether each user gets their own standby runs (`SINGLE_TENANT`) or runs are shared across users (`MULTI_TENANT`).""" + + VersionSourceType = ( Literal[ 'SOURCE_FILES', diff --git a/src/apify_client/_models.py b/src/apify_client/_models.py index 577e8b9a..9669f14e 100644 --- a/src/apify_client/_models.py +++ b/src/apify_client/_models.py @@ -2,7 +2,6 @@ from __future__ import annotations -from enum import Enum from typing import Annotated, Any, Literal from pydantic import AnyUrl, AwareDatetime, BaseModel, ConfigDict, EmailStr, Field, RootModel @@ -11,16 +10,19 @@ from apify_client._docs import docs_group from apify_client._literals import ( ActorJobStatus, + ActorNotice, ActorPermissionLevel, Deployment, Encoding, ErrorType, GeneralAccess, HttpMethod, + MonitoringLevel, OAuthProviderType, Provider, RunOrigin, SourceCodeFileFormat, + Tenancy, VersionSourceType, WebhookDispatchStatus, WebhookEventType, @@ -250,16 +252,6 @@ class ActorListItem(BaseModel): stats: ActorStats | None = None -@docs_group('Models') -class ActorNotice(Enum): - """A warning displayed on the Actor's page in Apify Store and Console. Can be set by the Actor's developer or automatically by Apify's quality checks.""" - - NONE = 'NONE' - RESIDENTIAL_PROXY_REQUIRED = 'RESIDENTIAL_PROXY_REQUIRED' - UNDER_MAINTENANCE = 'UNDER_MAINTENANCE' - NONE_TYPE_NONE = None - - @docs_group('Models') class ActorOAuthAccount(BaseModel): """An account a user connected through an Actor OAuth connection.""" @@ -2511,14 +2503,6 @@ class LockedRequestQueueHeadResponse(BaseModel): data: LockedRequestQueueHead -@docs_group('Models') -class MonitoringLevel(Enum): - """Level of monitoring services included in the plan. `null` or missing means the plan includes no monitoring service.""" - - STANDARD = 'STANDARD' - NONE_TYPE_NONE = None - - @docs_group('Models') class MonthlyUsage(BaseModel): model_config = ConfigDict( @@ -4417,15 +4401,6 @@ class TaskStats(BaseModel): """ -@docs_group('Models') -class Tenancy(Enum): - """Whether each user gets their own standby runs (`SINGLE_TENANT`) or runs are shared across users (`MULTI_TENANT`).""" - - SINGLE_TENANT = 'SINGLE_TENANT' - MULTI_TENANT = 'MULTI_TENANT' - NONE_TYPE_NONE = None - - @docs_group('Models') class TieredPricing(BaseModel): model_config = ConfigDict( diff --git a/src/apify_client/_resource_clients/actor.py b/src/apify_client/_resource_clients/actor.py index 274ccede..c72cd653 100644 --- a/src/apify_client/_resource_clients/actor.py +++ b/src/apify_client/_resource_clients/actor.py @@ -38,7 +38,7 @@ from decimal import Decimal from logging import Logger - from apify_client._literals import ActorJobStatus, ActorPermissionLevel, RunOrigin + from apify_client._literals import ActorJobStatus, ActorPermissionLevel, RunOrigin, Tenancy from apify_client._resource_clients import ( ActorServerClient, ActorServerClientAsync, @@ -134,7 +134,7 @@ def update( actor_standby_memory_mbytes: int | None = None, actor_standby_disable_standby_fields_override: bool | None = None, actor_standby_should_pass_actor_input: bool | None = None, - actor_standby_tenancy: str | None = None, + actor_standby_tenancy: Tenancy | None = None, pricing_infos: list[dict[str, Any]] | None = None, actor_permission_level: ActorPermissionLevel | None = None, tagged_builds: dict[str, dict[str, str] | None] | None = None, @@ -740,7 +740,7 @@ async def update( actor_standby_memory_mbytes: int | None = None, actor_standby_disable_standby_fields_override: bool | None = None, actor_standby_should_pass_actor_input: bool | None = None, - actor_standby_tenancy: str | None = None, + actor_standby_tenancy: Tenancy | None = None, pricing_infos: list[dict[str, Any]] | None = None, actor_permission_level: ActorPermissionLevel | None = None, tagged_builds: dict[str, dict[str, str] | None] | None = None, diff --git a/src/apify_client/_resource_clients/actor_collection.py b/src/apify_client/_resource_clients/actor_collection.py index 1081da73..75c84f40 100644 --- a/src/apify_client/_resource_clients/actor_collection.py +++ b/src/apify_client/_resource_clients/actor_collection.py @@ -21,7 +21,7 @@ from collections.abc import AsyncIterator, Iterator from datetime import timedelta - from apify_client._literals import ActorPermissionLevel + from apify_client._literals import ActorPermissionLevel, Tenancy from apify_client._models import ActorListItem from apify_client.types import Timeout @@ -137,7 +137,7 @@ def create( actor_standby_memory_mbytes: int | None = None, actor_standby_disable_standby_fields_override: bool | None = None, actor_standby_should_pass_actor_input: bool | None = None, - actor_standby_tenancy: str | None = None, + actor_standby_tenancy: Tenancy | None = None, actor_permission_level: ActorPermissionLevel | None = None, timeout: Timeout = 'medium', ) -> ActorResource: @@ -338,7 +338,7 @@ async def create( actor_standby_memory_mbytes: int | None = None, actor_standby_disable_standby_fields_override: bool | None = None, actor_standby_should_pass_actor_input: bool | None = None, - actor_standby_tenancy: str | None = None, + actor_standby_tenancy: Tenancy | None = None, actor_permission_level: ActorPermissionLevel | None = None, timeout: Timeout = 'medium', ) -> ActorResource: From e23d722f4c8efac7085d0b56b5c889181ee7986a Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Thu, 5 Nov 2026 15:46:02 +0100 Subject: [PATCH 4/5] More words --- src/apify_client/_resource_clients/actor.py | 4 ++-- src/apify_client/_resource_clients/actor_collection.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/apify_client/_resource_clients/actor.py b/src/apify_client/_resource_clients/actor.py index 2ab6f9d0..1f1d97d7 100644 --- a/src/apify_client/_resource_clients/actor.py +++ b/src/apify_client/_resource_clients/actor.py @@ -189,7 +189,7 @@ def update( server_should_pass_actor_input: Whether to pass the Actor input to the runs of the Server Actor. If false, they start with no input. server_tenancy: Whether each user gets their own runs of the Server Actor (`SINGLE_TENANT`) or the runs are - shared across users (`MULTI_TENANT`). + shared across users (`MULTI_TENANT`). `MULTI_TENANT` requires pay-per-event pricing. pricing_infos: A list of objects that describes the pricing of the Actor. actor_permission_level: The permission level of the Actor on Apify platform. tagged_builds: A dictionary mapping build tag names to their settings. Use it to create, update, @@ -830,7 +830,7 @@ async def update( server_should_pass_actor_input: Whether to pass the Actor input to the runs of the Server Actor. If false, they start with no input. server_tenancy: Whether each user gets their own runs of the Server Actor (`SINGLE_TENANT`) or the runs are - shared across users (`MULTI_TENANT`). + shared across users (`MULTI_TENANT`). `MULTI_TENANT` requires pay-per-event pricing. pricing_infos: A list of objects that describes the pricing of the Actor. actor_permission_level: The permission level of the Actor on Apify platform. tagged_builds: A dictionary mapping build tag names to their settings. Use it to create, update, diff --git a/src/apify_client/_resource_clients/actor_collection.py b/src/apify_client/_resource_clients/actor_collection.py index 7127a472..7838f412 100644 --- a/src/apify_client/_resource_clients/actor_collection.py +++ b/src/apify_client/_resource_clients/actor_collection.py @@ -190,7 +190,7 @@ def create( server_should_pass_actor_input: Whether to pass the Actor input to the runs of the Server Actor. If false, they start with no input. server_tenancy: Whether each user gets their own runs of the Server Actor (`SINGLE_TENANT`) or the runs are - shared across users (`MULTI_TENANT`). + shared across users (`MULTI_TENANT`). `MULTI_TENANT` requires pay-per-event pricing. actor_permission_level: The permission level of the Actor on Apify platform. timeout: Timeout for the API HTTP request. actor_standby_is_enabled: Deprecated alias of `server_is_enabled`. Will be removed in v4. @@ -426,7 +426,7 @@ async def create( server_should_pass_actor_input: Whether to pass the Actor input to the runs of the Server Actor. If false, they start with no input. server_tenancy: Whether each user gets their own runs of the Server Actor (`SINGLE_TENANT`) or the runs are - shared across users (`MULTI_TENANT`). + shared across users (`MULTI_TENANT`). `MULTI_TENANT` requires pay-per-event pricing. actor_permission_level: The permission level of the Actor on Apify platform. timeout: Timeout for the API HTTP request. actor_standby_is_enabled: Deprecated alias of `server_is_enabled`. Will be removed in v4. From 24b20075e233a3bf86bc2ac07a74feda9f0fdc68 Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Fri, 6 Nov 2026 11:01:50 +0100 Subject: [PATCH 5/5] Prevent orphaned commas in generated single-value literals --- scripts/postprocess_generated_models.py | 9 ++++++--- src/apify_client/_literals.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/postprocess_generated_models.py b/scripts/postprocess_generated_models.py index d1b9ff86..4591d53f 100644 --- a/scripts/postprocess_generated_models.py +++ b/scripts/postprocess_generated_models.py @@ -149,9 +149,12 @@ def convert_enums_to_literals(content: str) -> str: ] docstring = ast.get_docstring(node) - new_lines: list[str] = [f'{node.name} = Literal['] - new_lines.extend(f' {v!r},' for v in values) - new_lines.append('] | str') + if len(values) == 1: + new_lines: list[str] = [f'{node.name} = Literal[{values[0]!r}] | str'] + else: + new_lines = [f'{node.name} = Literal['] + new_lines.extend(f' {v!r},' for v in values) + new_lines.append('] | str') if docstring is not None: if '\n' in docstring: new_lines.append('"""') diff --git a/src/apify_client/_literals.py b/src/apify_client/_literals.py index a65af7c7..e4657ed9 100644 --- a/src/apify_client/_literals.py +++ b/src/apify_client/_literals.py @@ -506,7 +506,7 @@ ) -MonitoringLevel = Literal['STANDARD',] | str +MonitoringLevel = Literal['STANDARD'] | str """Level of monitoring services included in the plan. `null` or missing means the plan includes no monitoring service."""