Skip to content
Open
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
10 changes: 6 additions & 4 deletions src/apify_client/_resource_clients/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,8 @@ def charge(

Args:
event_name: The name of the event to charge for.
count: The number of events to charge. Defaults to 1 if not provided.
count: The number of events to charge. Defaults to 1 when `None`; other values,
including 0, are sent to the server as-is.
idempotency_key: A unique key to ensure idempotent charging. If not provided,
one will be auto-generated.
timeout: Timeout for the API HTTP request.
Expand All @@ -411,7 +412,7 @@ def charge(
data=json.dumps(
{
'eventName': event_name,
'count': count or 1,
'count': count if count is not None else 1,
}
),
timeout=timeout,
Expand Down Expand Up @@ -811,7 +812,8 @@ async def charge(

Args:
event_name: The name of the event to charge for.
count: The number of events to charge. Defaults to 1 if not provided.
count: The number of events to charge. Defaults to 1 when `None`; other values,
including 0, are sent to the server as-is.
idempotency_key: A unique key to ensure idempotent charging. If not provided,
one will be auto-generated.
timeout: Timeout for the API HTTP request.
Expand All @@ -837,7 +839,7 @@ async def charge(
data=json.dumps(
{
'eventName': event_name,
'count': count or 1,
'count': count if count is not None else 1,
}
),
timeout=timeout,
Expand Down
89 changes: 89 additions & 0 deletions tests/unit/test_run_charge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from __future__ import annotations

import gzip
import json
from typing import TYPE_CHECKING

import pytest
from werkzeug import Request, Response

from apify_client import ApifyClient, ApifyClientAsync

if TYPE_CHECKING:
from pytest_httpserver import HTTPServer

_MOCKED_RUN_ID = 'test_run_id'
_CHARGE_PATH = f'/v2/actor-runs/{_MOCKED_RUN_ID}/charge'


def _decode_body(request: Request) -> dict:
raw = request.get_data()
if request.headers.get('Content-Encoding') == 'gzip':
raw = gzip.decompress(raw)
return json.loads(raw)


@pytest.mark.parametrize(
('count', 'expected'),
[
(None, 1),
(0, 0),
(1, 1),
(5, 5),
],
)
def test_run_charge_preserves_count_sync(
httpserver: HTTPServer,
count: int | None,
expected: int,
) -> None:
"""Ensure `count` is sent as-is; only `None` falls back to 1 (in particular, `0` is preserved)."""
captured_requests: list[Request] = []

def capture_request(request: Request) -> Response:
captured_requests.append(request)
return Response(status=200, mimetype='application/json')

httpserver.expect_request(_CHARGE_PATH, method='POST').respond_with_handler(capture_request)

api_url = httpserver.url_for('/').removesuffix('/')
client = ApifyClient(token='test_token', api_url=api_url)

client.run(_MOCKED_RUN_ID).charge(event_name='test-event', count=count)

assert len(captured_requests) == 1
body = _decode_body(captured_requests[0])
assert body['count'] == expected


@pytest.mark.parametrize(
('count', 'expected'),
[
(None, 1),
(0, 0),
(1, 1),
(5, 5),
],
)
async def test_run_charge_preserves_count_async(
httpserver: HTTPServer,
count: int | None,
expected: int,
) -> None:
"""Async variant of `test_run_charge_preserves_count_sync`."""
captured_requests: list[Request] = []

def capture_request(request: Request) -> Response:
captured_requests.append(request)
return Response(status=200, mimetype='application/json')

httpserver.expect_request(_CHARGE_PATH, method='POST').respond_with_handler(capture_request)

api_url = httpserver.url_for('/').removesuffix('/')
client = ApifyClientAsync(token='test_token', api_url=api_url)

await client.run(_MOCKED_RUN_ID).charge(event_name='test-event', count=count)

assert len(captured_requests) == 1
body = _decode_body(captured_requests[0])
assert body['count'] == expected