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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ __pycache__
.mypy_cache
.pytest_cache
.ruff_cache
.ty_cache
.uv-cache

# Virtual envs
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ make format

### Type checking

Type checking is handled by [mypy](https://mypy.readthedocs.io/), verifying code against type annotations. Configuration settings can be found in `pyproject.toml`.
Type checking is handled by [ty](https://docs.astral.sh/ty/), verifying code against type annotations. Configuration settings can be found in `pyproject.toml`.

To run type checking:

Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
INTEGRATION_TESTS_CONCURRENCY = 1

clean:
rm -rf .mypy_cache .pytest_cache .ruff_cache build dist htmlcov .coverage
rm -rf .ty_cache .pytest_cache .ruff_cache build dist htmlcov .coverage

install-dev:
uv sync --all-extras
Expand All @@ -24,7 +24,7 @@ lint:
uv run ruff check

type-check:
uv run mypy
uv run ty check

unit-tests:
uv run pytest \
Expand Down
2 changes: 1 addition & 1 deletion docs/02_concepts/code/03_nested_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ async def main() -> None:
actor_runs = (await runs_client.list(limit=10, desc=True)).items

# Select the last run of the Actor that finished with a SUCCEEDED status
last_succeeded_run_client = actor_client.last_run(status='SUCCEEDED') # type: ignore[arg-type]
last_succeeded_run_client = actor_client.last_run(status='SUCCEEDED') # ty: ignore[invalid-argument-type]

# Get dataset
actor_run_dataset_client = last_succeeded_run_client.dataset()
Expand Down
2 changes: 1 addition & 1 deletion docs/02_concepts/code/03_nested_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def main() -> None:
actor_runs = runs_client.list(limit=10, desc=True).items

# Select the last run of the Actor that finished with a SUCCEEDED status
last_succeeded_run_client = actor_client.last_run(status='SUCCEEDED') # type: ignore[arg-type]
last_succeeded_run_client = actor_client.last_run(status='SUCCEEDED') # ty: ignore[invalid-argument-type]

# Get dataset
actor_run_dataset_client = last_succeeded_run_client.dataset()
Expand Down
37 changes: 14 additions & 23 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ dependencies = [
dev = [
"dycw-pytest-only<3.0.0",
"griffe",
"mypy~=1.19.0",
"pre-commit<5.0.0",
"pydoc-markdown<5.0.0",
"pytest-asyncio<2.0.0",
Expand All @@ -56,6 +55,7 @@ dev = [
"redbaron<1.0.0",
"ruff~=0.14.0",
"setuptools", # setuptools are used by pytest but not explicitly required
"ty~=0.0.0",
"types-colorama<0.5.0",
"werkzeug<4.0.0", # Werkzeug is used by pytest-httpserver
]
Expand Down Expand Up @@ -158,30 +158,21 @@ asyncio_default_fixture_loop_scope = "function"
asyncio_mode = "auto"
timeout = 1200

[tool.mypy]
python_version = "3.10"
files = ["src", "tests", "scripts", "docs", "website"]
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
no_implicit_optional = true
warn_redundant_casts = true
warn_return_any = true
warn_unreachable = true
warn_unused_ignores = true
exclude = []

[[tool.mypy.overrides]]
module = ["pandas"]
ignore_missing_imports = true

[tool.basedpyright]
pythonVersion = "3.10"
typeCheckingMode = "standard"
[tool.ty.environment]
python-version = "3.10"

[tool.ty.src]
include = ["src", "tests", "scripts", "docs", "website"]

[[tool.ty.overrides]]
include = [
"docs/**/*.py",
"website/**/*.py",
]

[tool.ty.overrides.rules]
unresolved-import = "ignore"

[tool.coverage.report]
exclude_lines = ["pragma: no cover", "if TYPE_CHECKING:", "assert_never()"]

Expand Down
2 changes: 1 addition & 1 deletion scripts/check_async_docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import sys
from pathlib import Path

from redbaron import RedBaron # type: ignore[import-untyped]
from redbaron import RedBaron
from utils import sync_to_async_docstring

found_issues = False
Expand Down
2 changes: 1 addition & 1 deletion scripts/fix_async_docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
from pathlib import Path

from redbaron import RedBaron # type: ignore[import-untyped]
from redbaron import RedBaron
from utils import sync_to_async_docstring

# Get the directory of the source files
Expand Down
6 changes: 3 additions & 3 deletions src/apify_client/_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def _injects_client_details_to_log_context(fun: Callable) -> Callable:

@functools.wraps(fun)
async def async_wrapper(resource_client: _BaseBaseClient, *args: Any, **kwargs: Any) -> Any:
log_context.client_method.set(fun.__qualname__)
log_context.client_method.set(fun.__qualname__) # ty: ignore[unresolved-attribute]
log_context.resource_id.set(resource_client.resource_id)

return await fun(resource_client, *args, **kwargs)
Expand All @@ -69,7 +69,7 @@ async def async_wrapper(resource_client: _BaseBaseClient, *args: Any, **kwargs:

@functools.wraps(fun)
async def async_generator_wrapper(resource_client: _BaseBaseClient, *args: Any, **kwargs: Any) -> Any:
log_context.client_method.set(fun.__qualname__)
log_context.client_method.set(fun.__qualname__) # ty: ignore[unresolved-attribute]
log_context.resource_id.set(resource_client.resource_id)

async for item in fun(resource_client, *args, **kwargs):
Expand All @@ -80,7 +80,7 @@ async def async_generator_wrapper(resource_client: _BaseBaseClient, *args: Any,

@functools.wraps(fun)
def wrapper(resource_client: _BaseBaseClient, *args: Any, **kwargs: Any) -> Any:
log_context.client_method.set(fun.__qualname__)
log_context.client_method.set(fun.__qualname__) # ty: ignore[unresolved-attribute]
log_context.resource_id.set(resource_client.resource_id)

return fun(resource_client, *args, **kwargs)
Expand Down
5 changes: 2 additions & 3 deletions src/apify_client/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
from typing import Any, Generic, TypeVar

JSONSerializable = str | int | float | bool | None | dict[str, Any] | list[Any]
"""Type for representing json-serializable values. It's close enough to the real thing supported
by json.parse, and the best we can do until mypy supports recursive types. It was suggested in
a discussion with (and approved by) Guido van Rossum, so I'd consider it correct enough.
"""Type for representing json-serializable values. It's close enough to the real thing supported by json.parse.
It was suggested in a discussion with (and approved by) Guido van Rossum, so I'd consider it correct enough.
"""

T = TypeVar('T')
Expand Down
13 changes: 10 additions & 3 deletions src/apify_client/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from datetime import datetime, timezone
from enum import Enum
from http import HTTPStatus
from typing import TYPE_CHECKING, Any, TypeVar, cast
from typing import TYPE_CHECKING, Any, TypeVar, cast, overload

import impit

Expand All @@ -32,7 +32,6 @@

T = TypeVar('T')
StopRetryingType = Callable[[], None]
ListOrDict = TypeVar('ListOrDict', list, dict)


def filter_out_none_values_recursively(dictionary: dict) -> dict:
Expand Down Expand Up @@ -63,7 +62,15 @@ def filter_out_none_values_recursively_internal(
return result


def parse_date_fields(data: ListOrDict, max_depth: int = PARSE_DATE_FIELDS_MAX_DEPTH) -> ListOrDict:
@overload
def parse_date_fields(data: list, max_depth: int = PARSE_DATE_FIELDS_MAX_DEPTH) -> list: ...


@overload
def parse_date_fields(data: dict, max_depth: int = PARSE_DATE_FIELDS_MAX_DEPTH) -> dict: ...


def parse_date_fields(data: list | dict, max_depth: int = PARSE_DATE_FIELDS_MAX_DEPTH) -> list | dict:
"""Recursively parse date fields in a list or dictionary up to the specified depth."""
if max_depth < 0:
return data
Expand Down
8 changes: 4 additions & 4 deletions src/apify_client/clients/resource_clients/actor_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ def create(
description: str | None = None,
seo_title: str | None = None,
seo_description: str | None = None,
versions: list[dict] | None = None, # type: ignore[valid-type]
versions: list[dict] | None = None, # ty: ignore[invalid-type-form]
restart_on_error: bool | None = None,
is_public: bool | None = None,
is_deprecated: bool | None = None,
is_anonymously_runnable: bool | None = None,
categories: list[str] | None = None, # type: ignore[valid-type]
categories: list[str] | None = None, # ty: ignore[invalid-type-form]
default_run_build: str | None = None,
default_run_max_items: int | None = None,
default_run_memory_mbytes: int | None = None,
Expand Down Expand Up @@ -175,12 +175,12 @@ async def create(
description: str | None = None,
seo_title: str | None = None,
seo_description: str | None = None,
versions: list[dict] | None = None, # type: ignore[valid-type]
versions: list[dict] | None = None, # ty: ignore[invalid-type-form]
restart_on_error: bool | None = None,
is_public: bool | None = None,
is_deprecated: bool | None = None,
is_anonymously_runnable: bool | None = None,
categories: list[str] | None = None, # type: ignore[valid-type]
categories: list[str] | None = None, # ty: ignore[invalid-type-form]
default_run_build: str | None = None,
default_run_max_items: int | None = None,
default_run_memory_mbytes: int | None = None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ def create(
*,
version_number: str,
build_tag: str | None = None,
env_vars: list[dict] | None = None, # type: ignore[valid-type]
env_vars: list[dict] | None = None, # ty: ignore[invalid-type-form]
apply_env_vars_to_build: bool | None = None,
source_type: ActorSourceType,
source_files: list[dict] | None = None, # type: ignore[valid-type]
source_files: list[dict] | None = None, # ty: ignore[invalid-type-form]
git_repo_url: str | None = None,
tarball_url: str | None = None,
github_gist_url: str | None = None,
Expand Down Expand Up @@ -103,10 +103,10 @@ async def create(
*,
version_number: str,
build_tag: str | None = None,
env_vars: list[dict] | None = None, # type: ignore[valid-type]
env_vars: list[dict] | None = None, # ty: ignore[invalid-type-form]
apply_env_vars_to_build: bool | None = None,
source_type: ActorSourceType,
source_files: list[dict] | None = None, # type: ignore[valid-type]
source_files: list[dict] | None = None, # ty: ignore[invalid-type-form]
git_repo_url: str | None = None,
tarball_url: str | None = None,
github_gist_url: str | None = None,
Expand Down
4 changes: 2 additions & 2 deletions src/apify_client/clients/resource_clients/run_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def list(
limit: int | None = None,
offset: int | None = None,
desc: bool | None = None,
status: ActorJobStatus | list[ActorJobStatus] | None = None,
status: ActorJobStatus | list[ActorJobStatus] | None = None, # ty: ignore[invalid-type-form]
started_before: str | datetime | None = None,
started_after: str | datetime | None = None,
) -> ListPage[dict]:
Expand Down Expand Up @@ -77,7 +77,7 @@ async def list(
limit: int | None = None,
offset: int | None = None,
desc: bool | None = None,
status: ActorJobStatus | list[ActorJobStatus] | None = None,
status: ActorJobStatus | list[ActorJobStatus] | None = None, # ty: ignore[invalid-type-form]
started_before: str | datetime | None = None,
started_after: str | datetime | None = None,
) -> ListPage[dict]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def create(
is_enabled: bool,
is_exclusive: bool,
name: str | None = None,
actions: list[dict] | None = None, # type: ignore[valid-type]
actions: list[dict] | None = None, # ty: ignore[invalid-type-form]
description: str | None = None,
timezone: str | None = None,
title: str | None = None,
Expand Down Expand Up @@ -121,7 +121,7 @@ async def create(
is_enabled: bool,
is_exclusive: bool,
name: str | None = None,
actions: list[dict] | None = None, # type: ignore[valid-type]
actions: list[dict] | None = None, # ty: ignore[invalid-type-form]
description: str | None = None,
timezone: str | None = None,
title: str | None = None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def list(
def create(
self,
*,
event_types: list[WebhookEventType], # type: ignore[valid-type]
event_types: list[WebhookEventType], # ty: ignore[invalid-type-form]
request_url: str,
payload_template: str | None = None,
headers_template: str | None = None,
Expand Down Expand Up @@ -127,7 +127,7 @@ async def list(
async def create(
self,
*,
event_types: list[WebhookEventType], # type: ignore[valid-type]
event_types: list[WebhookEventType], # ty: ignore[invalid-type-form]
request_url: str,
payload_template: str | None = None,
headers_template: str | None = None,
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ def make_httpserver() -> Iterable[HTTPServer]:
server = HTTPServer(threaded=True, host='127.0.0.1')
server.start()
yield server
server.clear() # type: ignore[no-untyped-call]
server.clear()
if server.is_running():
server.stop() # type: ignore[no-untyped-call]
server.stop()


@pytest.fixture
def httpserver(make_httpserver: HTTPServer) -> Iterable[HTTPServer]:
server = make_httpserver
yield server
server.clear() # type: ignore[no-untyped-call]
server.clear()


@pytest.fixture
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/test_client_request_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

import pytest

import apify_client
from apify_client import ApifyClient, ApifyClientAsync
from apify_client.errors import ApifyApiError

if TYPE_CHECKING:
from pytest_httpserver import HTTPServer
Expand Down Expand Up @@ -43,7 +43,7 @@ async def test_batch_not_processed_raises_exception_async(httpserver: HTTPServer
]
rq_client = client.request_queue(request_queue_id='whatever')

with pytest.raises(apify_client.errors.ApifyApiError):
with pytest.raises(ApifyApiError):
await rq_client.batch_add_requests(requests=requests)


Expand Down Expand Up @@ -77,7 +77,7 @@ def test_batch_not_processed_raises_exception_sync(httpserver: HTTPServer) -> No
]
rq_client = client.request_queue(request_queue_id='whatever')

with pytest.raises(apify_client.errors.ApifyApiError):
with pytest.raises(ApifyApiError):
rq_client.batch_add_requests(requests=requests)


Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ def test_add_rate_limit_error_type_validation() -> None:
"""Test type validation in add_rate_limit_error."""
stats = Statistics()
with pytest.raises(TypeError):
stats.add_rate_limit_error('1') # type: ignore[arg-type]
stats.add_rate_limit_error('1') # ty: ignore[invalid-argument-type]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any sensible way we could keep both directives so that mypy still works?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, the only way would be to use general type: ignore - ignoring all type issues for the line. I would rather not do that.

Loading