Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions src/apify_client/_resource_clients/actor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import warnings
from typing import TYPE_CHECKING, Any, Literal

from pydantic import TypeAdapter
Expand Down Expand Up @@ -567,9 +568,10 @@ def validate_input(
self,
run_input: Any = None,
*,
build_tag: str | None = None,
build: str | None = None,
content_type: str | None = None,
timeout: Timeout = 'short',
build_tag: str | None = None,
) -> bool:
"""Validate an input for the Actor that defines an input schema.

Expand All @@ -578,21 +580,33 @@ def validate_input(
including an `io.IOBase` stream such as an open file, an iterable of byte chunks, or a streamed
`HttpResponse`, which are uploaded in chunks without being held in memory. Streaming is experimental,
and its behavior may change in future versions.
build_tag: The Actor's build tag.
build: The Actor build to validate the input against. It can be either a build tag or build number. By
default, the build specified in the default run configuration for the Actor (typically latest) is used.
content_type: The content type of the input.
timeout: Timeout for the API HTTP request.
build_tag: Deprecated alias of `build`. Will be removed in v4.

Returns:
True if the input is valid, else raise an exception with validation error details.
"""
if build_tag is not None:
warnings.warn(
'The `build_tag` argument is deprecated and will be removed in v4. Use `build` instead.',
DeprecationWarning,
stacklevel=3,
)
if build is not None:
raise ValueError('Pass only one of `build` and `build_tag`.')
build = build_tag

run_input, content_type = encode_key_value_store_record_value(run_input, content_type=content_type)

self._http_client.call(
url=self._build_url('validate-input'),
method='POST',
headers={'content-type': content_type},
data=run_input,
params=self._build_params(build=build_tag),
params=self._build_params(build=build),
timeout=timeout,
)

Expand Down Expand Up @@ -1102,9 +1116,10 @@ async def validate_input(
self,
run_input: Any = None,
*,
build_tag: str | None = None,
build: str | None = None,
content_type: str | None = None,
timeout: Timeout = 'short',
build_tag: str | None = None,
) -> bool:
"""Validate an input for the Actor that defines an input schema.

Expand All @@ -1113,21 +1128,33 @@ async def validate_input(
including an `io.IOBase` stream such as an open file, an iterable of byte chunks, or a streamed
`HttpResponse`, which are uploaded in chunks without being held in memory. Streaming is experimental,
and its behavior may change in future versions.
build_tag: The Actor's build tag.
build: The Actor build to validate the input against. It can be either a build tag or build number. By
default, the build specified in the default run configuration for the Actor (typically latest) is used.
content_type: The content type of the input.
timeout: Timeout for the API HTTP request.
build_tag: Deprecated alias of `build`. Will be removed in v4.

Returns:
True if the input is valid, else raise an exception with validation error details.
"""
if build_tag is not None:
warnings.warn(
'The `build_tag` argument is deprecated and will be removed in v4. Use `build` instead.',
DeprecationWarning,
stacklevel=3,
)
if build is not None:
raise ValueError('Pass only one of `build` and `build_tag`.')
build = build_tag

run_input, content_type = encode_key_value_store_record_value(run_input, content_type=content_type)

await self._http_client.call(
url=self._build_url('validate-input'),
method='POST',
headers={'content-type': content_type},
data=run_input,
params=self._build_params(build=build_tag),
params=self._build_params(build=build),
timeout=timeout,
)

Expand Down
81 changes: 81 additions & 0 deletions tests/unit/test_actor_validate_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

from apify_client import ApifyClient, ApifyClientAsync

if TYPE_CHECKING:
from pytest_httpserver import HTTPServer

pytestmark = pytest.mark.usefixtures('http_client_classes')

ACTOR_ID = 'test_actor_id'


def expect_validate_input(httpserver: HTTPServer, build: str) -> str:
httpserver.expect_request(
f'/v2/actors/{ACTOR_ID}/validate-input',
method='POST',
query_string={'build': build},
).respond_with_json({'valid': True})
return httpserver.url_for('/').removesuffix('/')


def test_validate_input_passes_build_sync(httpserver: HTTPServer) -> None:
"""`build` is sent as the `build` query parameter."""
client = ApifyClient(token='test_token', api_url=expect_validate_input(httpserver, '1.2.3'))

assert client.actor(ACTOR_ID).validate_input({}, build='1.2.3') is True
httpserver.check_assertions()


async def test_validate_input_passes_build_async(httpserver: HTTPServer) -> None:
"""`build` is sent as the `build` query parameter."""
client = ApifyClientAsync(token='test_token', api_url=expect_validate_input(httpserver, '1.2.3'))

assert await client.actor(ACTOR_ID).validate_input({}, build='1.2.3') is True
httpserver.check_assertions()


def test_validate_input_build_tag_is_deprecated_alias_sync(httpserver: HTTPServer) -> None:
"""The deprecated `build_tag` emits a `DeprecationWarning` pointing at the caller and is sent as `build`."""
client = ApifyClient(token='test_token', api_url=expect_validate_input(httpserver, 'beta'))

with pytest.warns(DeprecationWarning, match='`build_tag` argument is deprecated') as record:
assert client.actor(ACTOR_ID).validate_input({}, build_tag='beta') is True
assert record[0].filename == __file__
httpserver.check_assertions()


async def test_validate_input_build_tag_is_deprecated_alias_async(httpserver: HTTPServer) -> None:
"""The deprecated `build_tag` emits a `DeprecationWarning` pointing at the caller and is sent as `build`."""
client = ApifyClientAsync(token='test_token', api_url=expect_validate_input(httpserver, 'beta'))

with pytest.warns(DeprecationWarning, match='`build_tag` argument is deprecated') as record:
assert await client.actor(ACTOR_ID).validate_input({}, build_tag='beta') is True
assert record[0].filename == __file__
httpserver.check_assertions()


def test_validate_input_rejects_build_and_build_tag_sync() -> None:
"""Passing both `build` and `build_tag` raises a `ValueError`."""
client = ApifyClient(token='test_token')

with (
pytest.warns(DeprecationWarning, match='`build_tag` argument is deprecated'),
pytest.raises(ValueError, match='only one of'),
):
client.actor(ACTOR_ID).validate_input({}, build='latest', build_tag='beta')


async def test_validate_input_rejects_build_and_build_tag_async() -> None:
"""Passing both `build` and `build_tag` raises a `ValueError`."""
client = ApifyClientAsync(token='test_token')

with (
pytest.warns(DeprecationWarning, match='`build_tag` argument is deprecated'),
pytest.raises(ValueError, match='only one of'),
):
await client.actor(ACTOR_ID).validate_input({}, build='latest', build_tag='beta')
Loading