apify 3.0.4b1__tar.gz → 3.0.4b3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of apify might be problematic. Click here for more details.
- {apify-3.0.4b1 → apify-3.0.4b3}/CHANGELOG.md +5 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/PKG-INFO +2 -2
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/12_pay_per_event.mdx +1 -1
- {apify-3.0.4b1 → apify-3.0.4b3}/pyproject.toml +2 -2
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/_charging.py +89 -37
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/_configuration.py +30 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/integration/README.md +20 -9
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/conftest.py +8 -103
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_actor_api_helpers.py +1 -1
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_actor_dataset.py +1 -1
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_actor_key_value_store.py +1 -1
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_actor_lifecycle.py +4 -2
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_actor_log.py +8 -10
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_actor_request_queue.py +156 -118
- apify-3.0.4b3/tests/integration/actor/test_apify_storages.py +28 -0
- apify-3.0.4b3/tests/integration/apify_api/conftest.py +70 -0
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/apify_api}/test_apify_storages.py +0 -27
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/apify_api}/test_request_queue.py +102 -147
- apify-3.0.4b3/tests/integration/conftest.py +28 -0
- apify-3.0.4b3/tests/unit/storage_clients/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/uv.lock +118 -103
- {apify-3.0.4b1 → apify-3.0.4b3}/website/package-lock.json +456 -456
- {apify-3.0.4b1 → apify-3.0.4b3}/website/src/pages/index.js +1 -1
- apify-3.0.4b3/website/static/.nojekyll +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/.editorconfig +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/.github/CODEOWNERS +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/.github/workflows/build_and_deploy_docs.yaml +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/.github/workflows/check_pr_title.yaml +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/.github/workflows/pre_release.yaml +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/.github/workflows/release.yaml +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/.github/workflows/run_code_checks.yaml +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/.github/workflows/update_new_issue.yaml +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/.gitignore +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/.markdownlint.yaml +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/.pre-commit-config.yaml +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/CONTRIBUTING.md +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/LICENSE +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/Makefile +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/README.md +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/01_overview/01_introduction.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/01_overview/02_running_actors_locally.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/01_overview/03_actor_structure.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/01_overview/code/01_introduction.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/01_overview/code/actor_structure/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/01_overview/code/actor_structure/__main__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/01_overview/code/actor_structure/main.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/01_overview/code/actor_structure/py.typed +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/01_actor_lifecycle.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/02_actor_input.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/03_storages.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/04_actor_events.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/05_proxy_management.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/06_interacting_with_other_actors.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/07_webhooks.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/08_access_apify_api.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/09_running_webserver.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/10_logging.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/11_configuration.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/01_class_context.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/01_class_manual.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/01_context_manager.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/01_error_handling_context.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/01_error_handling_manual.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/01_init_exit.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/01_instance_context.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/01_instance_manual.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/01_reboot.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/01_status_message.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/02_input.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/03_dataset_exports.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/03_dataset_read_write.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/03_deleting_storages.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/03_kvs_iterating.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/03_kvs_public_url.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/03_kvs_read_write.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/03_opening_storages.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/03_rq.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/04_actor_events.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/05_apify_proxy.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/05_apify_proxy_config.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/05_custom_proxy.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/05_custom_proxy_function.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/05_proxy_actor_input.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/05_proxy_httpx.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/05_proxy_rotation.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/06_interacting_call.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/06_interacting_call_task.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/06_interacting_metamorph.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/06_interacting_start.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/07_webhook.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/07_webhook_preventing.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/08_actor_client.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/08_actor_new_client.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/09_webserver.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/10_log_config.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/10_logger_usage.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/10_redirect_log.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/10_redirect_log_existing_run.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/11_config.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/actor_charge.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/02_concepts/code/conditional_actor_charge.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/01_beautifulsoup_httpx.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/02_parsel_impit.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/03_playwright.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/04_selenium.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/05_crawlee.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/06_scrapy.mdx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/01_beautifulsoup_httpx.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/02_parsel_impit.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/03_playwright.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/04_selenium.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/05_crawlee_beautifulsoup.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/05_crawlee_parsel.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/05_crawlee_playwright.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/scrapy_project/src/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/scrapy_project/src/__main__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/scrapy_project/src/items.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/scrapy_project/src/main.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/scrapy_project/src/py.typed +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/scrapy_project/src/settings.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/scrapy_project/src/spiders/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/scrapy_project/src/spiders/py.typed +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/03_guides/code/scrapy_project/src/spiders/title.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/04_upgrading/upgrading_to_v2.md +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/04_upgrading/upgrading_to_v3.md +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/docs/pyproject.toml +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/renovate.json +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/_actor.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/_consts.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/_crypto.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/_models.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/_proxy_configuration.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/_utils.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/events/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/events/_apify_event_manager.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/events/_types.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/events/py.typed +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/log.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/py.typed +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/request_loaders/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/request_loaders/_apify_request_list.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/request_loaders/py.typed +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/_actor_runner.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/_async_thread.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/_logging_config.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/extensions/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/extensions/_httpcache.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/middlewares/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/middlewares/apify_proxy.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/middlewares/py.typed +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/pipelines/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/pipelines/actor_dataset_push.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/pipelines/py.typed +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/py.typed +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/requests.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/scheduler.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/scrapy/utils.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_apify/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_apify/_dataset_client.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_apify/_key_value_store_client.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_apify/_models.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_apify/_request_queue_client.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_apify/_request_queue_shared_client.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_apify/_request_queue_single_client.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_apify/_storage_client.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_apify/_utils.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_apify/py.typed +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_file_system/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_file_system/_key_value_store_client.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_file_system/_storage_client.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_smart_apify/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/_smart_apify/_storage_client.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storage_clients/py.typed +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storages/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/src/apify/storages/py.typed +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/integration/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/integration/_utils.py +0 -0
- {apify-3.0.4b1/tests/integration/actor_source_base/src → apify-3.0.4b3/tests/integration/actor}/__init__.py +0 -0
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/actor_source_base/Dockerfile +0 -0
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/actor_source_base/requirements.txt +0 -0
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/actor_source_base/server.py +0 -0
- {apify-3.0.4b1/tests/unit → apify-3.0.4b3/tests/integration/actor/actor_source_base/src}/__init__.py +0 -0
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/actor_source_base/src/__main__.py +0 -0
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/actor_source_base/src/main.py +0 -0
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_actor_call_timeouts.py +0 -0
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_actor_charge.py +0 -0
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_actor_create_proxy_configuration.py +0 -0
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_actor_events.py +0 -0
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_actor_scrapy.py +0 -0
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_crawlers_with_storages.py +0 -0
- {apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_fixtures.py +0 -0
- {apify-3.0.4b1/tests/unit/actor → apify-3.0.4b3/tests/integration/apify_api}/__init__.py +0 -0
- {apify-3.0.4b1/tests/unit/events → apify-3.0.4b3/tests/unit}/__init__.py +0 -0
- {apify-3.0.4b1/tests/unit/scrapy → apify-3.0.4b3/tests/unit/actor}/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/actor/test_actor_create_proxy_configuration.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/actor/test_actor_dataset.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/actor/test_actor_env_helpers.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/actor/test_actor_helpers.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/actor/test_actor_key_value_store.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/actor/test_actor_lifecycle.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/actor/test_actor_log.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/actor/test_actor_non_default_instance.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/actor/test_actor_request_queue.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/actor/test_configuration.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/actor/test_request_list.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/conftest.py +0 -0
- {apify-3.0.4b1/tests/unit/scrapy/extensions → apify-3.0.4b3/tests/unit/events}/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/events/test_apify_event_manager.py +0 -0
- {apify-3.0.4b1/tests/unit/scrapy/middlewares → apify-3.0.4b3/tests/unit/scrapy}/__init__.py +0 -0
- {apify-3.0.4b1/tests/unit/scrapy/pipelines → apify-3.0.4b3/tests/unit/scrapy/extensions}/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/scrapy/extensions/test_httpcache.py +0 -0
- {apify-3.0.4b1/tests/unit/scrapy/requests → apify-3.0.4b3/tests/unit/scrapy/middlewares}/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/scrapy/middlewares/test_apify_proxy.py +0 -0
- {apify-3.0.4b1/tests/unit/scrapy/utils → apify-3.0.4b3/tests/unit/scrapy/pipelines}/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/scrapy/pipelines/test_actor_dataset_push.py +0 -0
- {apify-3.0.4b1/tests/unit/storage_clients → apify-3.0.4b3/tests/unit/scrapy/requests}/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/scrapy/requests/test_to_apify_request.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/scrapy/requests/test_to_scrapy_request.py +0 -0
- /apify-3.0.4b1/website/static/.nojekyll → /apify-3.0.4b3/tests/unit/scrapy/utils/__init__.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/scrapy/utils/test_apply_apify_settings.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/scrapy/utils/test_get_basic_auth_header.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/storage_clients/test_apify_request_queue_client.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/storage_clients/test_file_system.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/test_apify_storages.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/test_crypto.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/tests/unit/test_proxy_configuration.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/.eslintrc.json +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/babel.config.js +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/build_api_reference.sh +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/docusaurus.config.js +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/generate_module_shortcuts.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/package.json +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/sidebars.js +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/src/components/ApiLink.jsx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/src/components/Gradients.jsx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/src/components/Highlights.jsx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/src/components/Highlights.module.css +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/src/components/RunnableCodeBlock.jsx +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/src/components/RunnableCodeBlock.module.css +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/src/css/custom.css +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/src/pages/home_page_example.py +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/src/pages/index.module.css +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/src/theme/DocItem/Content/index.js +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/static/img/docs-og.png +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/static/img/guides/redirected_logs_example.webp +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/tools/docs-prettier.config.js +0 -0
- {apify-3.0.4b1 → apify-3.0.4b3}/website/tools/utils/externalLink.js +0 -0
|
@@ -8,6 +8,11 @@ All notable changes to this project will be documented in this file.
|
|
|
8
8
|
### 🐛 Bug Fixes
|
|
9
9
|
|
|
10
10
|
- Fix type of `cloud_storage_client` in `SmartApifyStorageClient` ([#642](https://github.com/apify/apify-sdk-python/pull/642)) ([3bf285d](https://github.com/apify/apify-sdk-python/commit/3bf285d60f507730954986a80c19ed2e27a38f9c)) by [@vdusek](https://github.com/vdusek)
|
|
11
|
+
- Fix local charging log dataset name ([#649](https://github.com/apify/apify-sdk-python/pull/649)) ([fdb1276](https://github.com/apify/apify-sdk-python/commit/fdb1276264aee2687596d87c96d19033fe915823)) by [@vdusek](https://github.com/vdusek), closes [#648](https://github.com/apify/apify-sdk-python/issues/648)
|
|
12
|
+
|
|
13
|
+
### ⚡ Performance
|
|
14
|
+
|
|
15
|
+
- Use Apify-provided environment variables to obtain PPE pricing information ([#644](https://github.com/apify/apify-sdk-python/pull/644)) ([0c32f29](https://github.com/apify/apify-sdk-python/commit/0c32f29d6a316f5bacc931595d694f262c925b2b)) by [@Mantisus](https://github.com/Mantisus), closes [#614](https://github.com/apify/apify-sdk-python/issues/614)
|
|
11
16
|
|
|
12
17
|
|
|
13
18
|
<!-- git-cliff-unreleased-end -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: apify
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.4b3
|
|
4
4
|
Summary: Apify SDK for Python
|
|
5
5
|
Project-URL: Apify Homepage, https://apify.com
|
|
6
6
|
Project-URL: Changelog, https://docs.apify.com/sdk/python/docs/changelog
|
|
@@ -228,7 +228,7 @@ Requires-Python: >=3.10
|
|
|
228
228
|
Requires-Dist: apify-client<3.0.0,>=2.2.0
|
|
229
229
|
Requires-Dist: apify-shared<3.0.0,>=2.0.0
|
|
230
230
|
Requires-Dist: cachetools>=5.5.0
|
|
231
|
-
Requires-Dist: crawlee<2.0.0,>=1.0.
|
|
231
|
+
Requires-Dist: crawlee<2.0.0,>=1.0.4
|
|
232
232
|
Requires-Dist: cryptography>=42.0.0
|
|
233
233
|
Requires-Dist: impit>=0.6.1
|
|
234
234
|
Requires-Dist: lazy-object-proxy>=1.11.0
|
|
@@ -47,6 +47,6 @@ It is encouraged to test your monetization code on your machine before releasing
|
|
|
47
47
|
ACTOR_TEST_PAY_PER_EVENT=true python -m youractor
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
If you also wish to see a log of all the events charged throughout the run, the Apify SDK keeps a log of charged events in a so called charging dataset. Your charging dataset can be found under the `
|
|
50
|
+
If you also wish to see a log of all the events charged throughout the run, the Apify SDK keeps a log of charged events in a so called charging dataset. Your charging dataset can be found under the `charging-log` name (unless you change your storage settings, this dataset is stored in `storage/datasets/charging-log/`). Please note that this log is not available when running the Actor in production on the Apify platform.
|
|
51
51
|
|
|
52
52
|
Because pricing configuration is stored by the Apify platform, all events will have a default price of $1.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "apify"
|
|
7
|
-
version = "3.0.
|
|
7
|
+
version = "3.0.4b3"
|
|
8
8
|
description = "Apify SDK for Python"
|
|
9
9
|
authors = [{ name = "Apify Technologies s.r.o.", email = "support@apify.com" }]
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -36,7 +36,7 @@ keywords = [
|
|
|
36
36
|
dependencies = [
|
|
37
37
|
"apify-client>=2.2.0,<3.0.0",
|
|
38
38
|
"apify-shared>=2.0.0,<3.0.0",
|
|
39
|
-
"crawlee>=1.0.
|
|
39
|
+
"crawlee>=1.0.4,<2.0.0",
|
|
40
40
|
"cachetools>=5.5.0",
|
|
41
41
|
"cryptography>=42.0.0",
|
|
42
42
|
"impit>=0.6.1",
|
|
@@ -4,13 +4,20 @@ import math
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from datetime import datetime, timezone
|
|
6
6
|
from decimal import Decimal
|
|
7
|
-
from typing import TYPE_CHECKING, Protocol
|
|
7
|
+
from typing import TYPE_CHECKING, Protocol, TypedDict
|
|
8
8
|
|
|
9
9
|
from pydantic import TypeAdapter
|
|
10
10
|
|
|
11
11
|
from crawlee._utils.context import ensure_context
|
|
12
12
|
|
|
13
|
-
from apify._models import
|
|
13
|
+
from apify._models import (
|
|
14
|
+
ActorRun,
|
|
15
|
+
FlatPricePerMonthActorPricingInfo,
|
|
16
|
+
FreeActorPricingInfo,
|
|
17
|
+
PayPerEventActorPricingInfo,
|
|
18
|
+
PricePerDatasetItemActorPricingInfo,
|
|
19
|
+
PricingModel,
|
|
20
|
+
)
|
|
14
21
|
from apify._utils import docs_group
|
|
15
22
|
from apify.log import logger
|
|
16
23
|
from apify.storages import Dataset
|
|
@@ -111,24 +118,16 @@ class ActorPricingInfo:
|
|
|
111
118
|
class ChargingManagerImplementation(ChargingManager):
|
|
112
119
|
"""Implementation of the `ChargingManager` Protocol - this is only meant to be instantiated internally."""
|
|
113
120
|
|
|
114
|
-
LOCAL_CHARGING_LOG_DATASET_NAME = '
|
|
121
|
+
LOCAL_CHARGING_LOG_DATASET_NAME = 'charging-log'
|
|
115
122
|
|
|
116
123
|
def __init__(self, configuration: Configuration, client: ApifyClientAsync) -> None:
|
|
117
124
|
self._max_total_charge_usd = configuration.max_total_charge_usd or Decimal('inf')
|
|
125
|
+
self._configuration = configuration
|
|
118
126
|
self._is_at_home = configuration.is_at_home
|
|
119
127
|
self._actor_run_id = configuration.actor_run_id
|
|
120
128
|
self._purge_charging_log_dataset = configuration.purge_on_start
|
|
121
129
|
self._pricing_model: PricingModel | None = None
|
|
122
130
|
|
|
123
|
-
if configuration.test_pay_per_event:
|
|
124
|
-
if self._is_at_home:
|
|
125
|
-
raise ValueError(
|
|
126
|
-
'Using the ACTOR_TEST_PAY_PER_EVENT environment variable is only supported '
|
|
127
|
-
'in a local development environment'
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
self._pricing_model = 'PAY_PER_EVENT'
|
|
131
|
-
|
|
132
131
|
self._client = client
|
|
133
132
|
self._charging_log_dataset: Dataset | None = None
|
|
134
133
|
|
|
@@ -140,37 +139,46 @@ class ChargingManagerImplementation(ChargingManager):
|
|
|
140
139
|
|
|
141
140
|
async def __aenter__(self) -> None:
|
|
142
141
|
"""Initialize the charging manager - this is called by the `Actor` class and shouldn't be invoked manually."""
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
raise RuntimeError('Actor run ID not found even though the Actor is running on Apify')
|
|
142
|
+
# Validate config
|
|
143
|
+
if self._configuration.test_pay_per_event and self._is_at_home:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
'Using the ACTOR_TEST_PAY_PER_EVENT environment variable is only supported '
|
|
146
|
+
'in a local development environment'
|
|
147
|
+
)
|
|
150
148
|
|
|
151
|
-
|
|
152
|
-
if run is None:
|
|
153
|
-
raise RuntimeError('Actor run not found')
|
|
149
|
+
self.active = True
|
|
154
150
|
|
|
155
|
-
|
|
156
|
-
|
|
151
|
+
# Retrieve pricing information from env vars or API
|
|
152
|
+
pricing_data = await self._fetch_pricing_info()
|
|
153
|
+
pricing_info = pricing_data['pricing_info']
|
|
154
|
+
charged_event_counts = pricing_data['charged_event_counts']
|
|
155
|
+
max_total_charge_usd = pricing_data['max_total_charge_usd']
|
|
157
156
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
157
|
+
# Set pricing model
|
|
158
|
+
if self._configuration.test_pay_per_event:
|
|
159
|
+
self._pricing_model = 'PAY_PER_EVENT'
|
|
160
|
+
else:
|
|
161
|
+
self._pricing_model = pricing_info.pricing_model if pricing_info else None
|
|
162
|
+
|
|
163
|
+
# Load per-event pricing information
|
|
164
|
+
if pricing_info and pricing_info.pricing_model == 'PAY_PER_EVENT':
|
|
165
|
+
for event_name, event_pricing in pricing_info.pricing_per_event.actor_charge_events.items():
|
|
166
|
+
self._pricing_info[event_name] = PricingInfoItem(
|
|
167
|
+
price=event_pricing.event_price_usd,
|
|
168
|
+
title=event_pricing.event_title,
|
|
169
|
+
)
|
|
164
170
|
|
|
165
|
-
|
|
171
|
+
self._max_total_charge_usd = max_total_charge_usd
|
|
166
172
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
+
# Load charged event counts
|
|
174
|
+
for event_name, count in charged_event_counts.items():
|
|
175
|
+
price = self._pricing_info.get(event_name, PricingInfoItem(Decimal(), title='')).price
|
|
176
|
+
self._charging_state[event_name] = ChargingStateItem(
|
|
177
|
+
charge_count=count,
|
|
178
|
+
total_charged_amount=count * price,
|
|
179
|
+
)
|
|
173
180
|
|
|
181
|
+
# Set up charging log dataset for local development
|
|
174
182
|
if not self._is_at_home and self._pricing_model == 'PAY_PER_EVENT':
|
|
175
183
|
# We are not running on the Apify platform, but PPE is enabled for testing - open a dataset that
|
|
176
184
|
# will contain a log of all charge calls for debugging purposes.
|
|
@@ -328,6 +336,38 @@ class ChargingManagerImplementation(ChargingManager):
|
|
|
328
336
|
def get_max_total_charge_usd(self) -> Decimal:
|
|
329
337
|
return self._max_total_charge_usd
|
|
330
338
|
|
|
339
|
+
async def _fetch_pricing_info(self) -> _FetchedPricingInfoDict:
|
|
340
|
+
"""Fetch pricing information from environment variables or API."""
|
|
341
|
+
# Check if pricing info is available via environment variables
|
|
342
|
+
if self._configuration.actor_pricing_info is not None and self._configuration.charged_event_counts is not None:
|
|
343
|
+
return _FetchedPricingInfoDict(
|
|
344
|
+
pricing_info=self._configuration.actor_pricing_info,
|
|
345
|
+
charged_event_counts=self._configuration.charged_event_counts,
|
|
346
|
+
max_total_charge_usd=self._configuration.max_total_charge_usd or Decimal('inf'),
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# Fall back to API call
|
|
350
|
+
if self._is_at_home:
|
|
351
|
+
if self._actor_run_id is None:
|
|
352
|
+
raise RuntimeError('Actor run ID not found even though the Actor is running on Apify')
|
|
353
|
+
|
|
354
|
+
run = run_validator.validate_python(await self._client.run(self._actor_run_id).get())
|
|
355
|
+
if run is None:
|
|
356
|
+
raise RuntimeError('Actor run not found')
|
|
357
|
+
|
|
358
|
+
return _FetchedPricingInfoDict(
|
|
359
|
+
pricing_info=run.pricing_info,
|
|
360
|
+
charged_event_counts=run.charged_event_counts or {},
|
|
361
|
+
max_total_charge_usd=run.options.max_total_charge_usd or Decimal('inf'),
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Local development without environment variables
|
|
365
|
+
return _FetchedPricingInfoDict(
|
|
366
|
+
pricing_info=None,
|
|
367
|
+
charged_event_counts={},
|
|
368
|
+
max_total_charge_usd=self._configuration.max_total_charge_usd or Decimal('inf'),
|
|
369
|
+
)
|
|
370
|
+
|
|
331
371
|
|
|
332
372
|
@dataclass
|
|
333
373
|
class ChargingStateItem:
|
|
@@ -339,3 +379,15 @@ class ChargingStateItem:
|
|
|
339
379
|
class PricingInfoItem:
|
|
340
380
|
price: Decimal
|
|
341
381
|
title: str
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class _FetchedPricingInfoDict(TypedDict):
|
|
385
|
+
pricing_info: (
|
|
386
|
+
FreeActorPricingInfo
|
|
387
|
+
| FlatPricePerMonthActorPricingInfo
|
|
388
|
+
| PricePerDatasetItemActorPricingInfo
|
|
389
|
+
| PayPerEventActorPricingInfo
|
|
390
|
+
| None
|
|
391
|
+
)
|
|
392
|
+
charged_event_counts: dict[str, int]
|
|
393
|
+
max_total_charge_usd: Decimal
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from datetime import datetime, timedelta
|
|
4
5
|
from decimal import Decimal
|
|
5
6
|
from logging import getLogger
|
|
@@ -14,6 +15,12 @@ from crawlee._utils.models import timedelta_ms
|
|
|
14
15
|
from crawlee._utils.urls import validate_http_url
|
|
15
16
|
from crawlee.configuration import Configuration as CrawleeConfiguration
|
|
16
17
|
|
|
18
|
+
from apify._models import (
|
|
19
|
+
FlatPricePerMonthActorPricingInfo,
|
|
20
|
+
FreeActorPricingInfo,
|
|
21
|
+
PayPerEventActorPricingInfo,
|
|
22
|
+
PricePerDatasetItemActorPricingInfo,
|
|
23
|
+
)
|
|
17
24
|
from apify._utils import docs_group
|
|
18
25
|
|
|
19
26
|
logger = getLogger(__name__)
|
|
@@ -409,6 +416,29 @@ class Configuration(CrawleeConfiguration):
|
|
|
409
416
|
),
|
|
410
417
|
] = None
|
|
411
418
|
|
|
419
|
+
actor_pricing_info: Annotated[
|
|
420
|
+
FreeActorPricingInfo
|
|
421
|
+
| FlatPricePerMonthActorPricingInfo
|
|
422
|
+
| PricePerDatasetItemActorPricingInfo
|
|
423
|
+
| PayPerEventActorPricingInfo
|
|
424
|
+
| None,
|
|
425
|
+
Field(
|
|
426
|
+
alias='apify_actor_pricing_info',
|
|
427
|
+
description='JSON string with prising info of the actor',
|
|
428
|
+
discriminator='pricing_model',
|
|
429
|
+
),
|
|
430
|
+
BeforeValidator(lambda data: json.loads(data) if isinstance(data, str) else data if data else None),
|
|
431
|
+
] = None
|
|
432
|
+
|
|
433
|
+
charged_event_counts: Annotated[
|
|
434
|
+
dict[str, int] | None,
|
|
435
|
+
Field(
|
|
436
|
+
alias='apify_charged_actor_event_counts',
|
|
437
|
+
description='Counts of events that were charged for the actor',
|
|
438
|
+
),
|
|
439
|
+
BeforeValidator(lambda data: json.loads(data) if isinstance(data, str) else data if data else None),
|
|
440
|
+
] = None
|
|
441
|
+
|
|
412
442
|
@model_validator(mode='after')
|
|
413
443
|
def disable_browser_sandbox_on_platform(self) -> Self:
|
|
414
444
|
"""Disable the browser sandbox mode when running on the Apify platform.
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
# Integration tests
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
There are two different groups of integration tests in this repository:
|
|
4
|
+
- Apify API integration tests. These test that the Apify SDK is correctly communicating with Apify API through Apify client.
|
|
5
|
+
- Actor integration tests. These test that the Apify SDK can be used in Actors deployed to Apify platform. These are very high level tests, and they test communication with the API and correct interaction with the Apify platform.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
To run these tests, you need to set the `APIFY_TEST_USER_API_TOKEN` environment variable to the API token of the Apify user you want to use for the tests, and then start them with `make integration-tests`.
|
|
6
8
|
|
|
7
|
-
##
|
|
9
|
+
## Apify API integration tests
|
|
10
|
+
The tests are making real requests to the Apify API as opposed to the unit tests that are mocking such API calls. On the other hand they are faster than `Actor integration tests` as they do not require building and deploying the Actor. These test can be also fully debugged locally. Preferably try to write integration tests on this level if possible.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Actor integration tests
|
|
14
|
+
We have integration tests which build and run Actors using the Python SDK on the Apify platform. These integration tests are slower than `Apify API integration tests` as they need to build and deploy Actors on the platform. Preferably try to write `Apify API integration tests` first, and only write `Actor integration tests` when you need to test something that can only be tested on the platform.
|
|
15
|
+
|
|
16
|
+
If you want to run the integration tests on a different environment than the main Apify platform, you need to set the `APIFY_INTEGRATION_TESTS_API_URL` environment variable to the right URL to the Apify API you want to use.
|
|
17
|
+
|
|
18
|
+
### How to write tests
|
|
8
19
|
|
|
9
20
|
There are two fixtures which you can use to write tests:
|
|
10
21
|
|
|
11
|
-
|
|
22
|
+
#### `apify_client_async`
|
|
12
23
|
|
|
13
24
|
This fixture just gives you an instance of `ApifyClientAsync` configured with the right token and API URL, so you don't have to do that yourself.
|
|
14
25
|
|
|
@@ -17,15 +28,15 @@ async def test_something(apify_client_async: ApifyClientAsync) -> None:
|
|
|
17
28
|
assert await apify_client_async.user('me').get() is not None
|
|
18
29
|
```
|
|
19
30
|
|
|
20
|
-
|
|
31
|
+
#### `make_actor`
|
|
21
32
|
|
|
22
|
-
This fixture returns a factory function for creating Actors on the Apify
|
|
33
|
+
This fixture returns a factory function for creating Actors on the Apify platform.
|
|
23
34
|
|
|
24
35
|
For the Actor source, the fixture takes the files from `tests/integration/actor_source_base`, builds the Apify SDK wheel from the current codebase, and adds the Actor source you passed to the fixture as an argument. You have to pass exactly one of the `main_func`, `main_py` and `source_files` arguments.
|
|
25
36
|
|
|
26
37
|
The created Actor will be uploaded to the platform, built there, and after the test finishes, it will be automatically deleted. If the Actor build fails, it will not be deleted, so that you can check why the build failed.
|
|
27
38
|
|
|
28
|
-
|
|
39
|
+
#### Creating test Actor straight from a Python function
|
|
29
40
|
|
|
30
41
|
You can create Actors straight from a Python function. This is great because you can have the test Actor source code checked with the linter.
|
|
31
42
|
|
|
@@ -66,7 +77,7 @@ async def test_something(
|
|
|
66
77
|
assert run_result.status == 'SUCCEEDED'
|
|
67
78
|
```
|
|
68
79
|
|
|
69
|
-
|
|
80
|
+
#### Creating Actor from source files
|
|
70
81
|
|
|
71
82
|
You can also pass the source files directly if you need something more complex (e.g. pass some fixed value to the Actor source code or use multiple source files).
|
|
72
83
|
|
|
@@ -127,7 +138,7 @@ async def test_something(
|
|
|
127
138
|
assert actor_run.status == 'SUCCEEDED'
|
|
128
139
|
```
|
|
129
140
|
|
|
130
|
-
|
|
141
|
+
#### Asserts
|
|
131
142
|
|
|
132
143
|
Since test Actors are not executed as standard pytest tests, we don't get introspection of assertion expressions. In case of failure, only a bare `AssertionError` is shown, without the left and right values. This means, we must include explicit assertion messages to aid potential debugging.
|
|
133
144
|
|
|
@@ -13,111 +13,20 @@ import pytest
|
|
|
13
13
|
from filelock import FileLock
|
|
14
14
|
|
|
15
15
|
from apify_client import ApifyClient, ApifyClientAsync
|
|
16
|
-
from apify_shared.consts import ActorJobStatus, ActorSourceType
|
|
17
|
-
from crawlee import service_locator
|
|
16
|
+
from apify_shared.consts import ActorJobStatus, ActorSourceType
|
|
18
17
|
|
|
19
|
-
import
|
|
20
|
-
from ._utils import generate_unique_resource_name
|
|
21
|
-
from apify import Actor
|
|
18
|
+
from .._utils import generate_unique_resource_name
|
|
22
19
|
from apify._models import ActorRun
|
|
23
|
-
from apify.storage_clients import ApifyStorageClient
|
|
24
|
-
from apify.storage_clients._apify._utils import AliasResolver
|
|
25
|
-
from apify.storages import RequestQueue
|
|
26
20
|
|
|
27
21
|
if TYPE_CHECKING:
|
|
28
|
-
from collections.abc import
|
|
22
|
+
from collections.abc import Awaitable, Callable, Coroutine, Iterator, Mapping
|
|
29
23
|
from decimal import Decimal
|
|
30
24
|
|
|
31
25
|
from apify_client.clients.resource_clients import ActorClientAsync
|
|
32
26
|
|
|
33
27
|
_TOKEN_ENV_VAR = 'APIFY_TEST_USER_API_TOKEN'
|
|
34
28
|
_API_URL_ENV_VAR = 'APIFY_INTEGRATION_TESTS_API_URL'
|
|
35
|
-
_SDK_ROOT_PATH = Path(__file__).parent.parent.parent.resolve()
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@pytest.fixture
|
|
39
|
-
def prepare_test_env(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Callable[[], None]:
|
|
40
|
-
"""Prepare the testing environment by resetting the global state before each test.
|
|
41
|
-
|
|
42
|
-
This fixture ensures that the global state of the package is reset to a known baseline before each test runs.
|
|
43
|
-
It also configures a temporary storage directory for test isolation.
|
|
44
|
-
|
|
45
|
-
Args:
|
|
46
|
-
monkeypatch: Test utility provided by pytest for patching.
|
|
47
|
-
tmp_path: A unique temporary directory path provided by pytest for test isolation.
|
|
48
|
-
|
|
49
|
-
Returns:
|
|
50
|
-
A callable that prepares the test environment.
|
|
51
|
-
"""
|
|
52
|
-
|
|
53
|
-
def _prepare_test_env() -> None:
|
|
54
|
-
# Reset the Actor class state.
|
|
55
|
-
apify._actor.Actor.__wrapped__.__class__._is_any_instance_initialized = False # type: ignore[attr-defined]
|
|
56
|
-
apify._actor.Actor.__wrapped__.__class__._is_rebooting = False # type: ignore[attr-defined]
|
|
57
|
-
delattr(apify._actor.Actor, '__wrapped__')
|
|
58
|
-
|
|
59
|
-
# Set the environment variable for the local storage directory to the temporary path.
|
|
60
|
-
monkeypatch.setenv(ApifyEnvVars.LOCAL_STORAGE_DIR, str(tmp_path))
|
|
61
|
-
|
|
62
|
-
# Reset the services in the service locator.
|
|
63
|
-
service_locator._configuration = None
|
|
64
|
-
service_locator._event_manager = None
|
|
65
|
-
service_locator._storage_client = None
|
|
66
|
-
service_locator.storage_instance_manager.clear_cache()
|
|
67
|
-
|
|
68
|
-
# Reset the AliasResolver class state.
|
|
69
|
-
AliasResolver._alias_map = {}
|
|
70
|
-
AliasResolver._alias_init_lock = None
|
|
71
|
-
|
|
72
|
-
# Verify that the test environment was set up correctly.
|
|
73
|
-
assert os.environ.get(ApifyEnvVars.LOCAL_STORAGE_DIR) == str(tmp_path)
|
|
74
|
-
|
|
75
|
-
return _prepare_test_env
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@pytest.fixture(autouse=True)
|
|
79
|
-
def _isolate_test_environment(prepare_test_env: Callable[[], None]) -> None:
|
|
80
|
-
"""Isolate the testing environment by resetting global state before and after each test.
|
|
81
|
-
|
|
82
|
-
This fixture ensures that each test starts with a clean slate and that any modifications during the test
|
|
83
|
-
do not affect subsequent tests. It runs automatically for all tests.
|
|
84
|
-
|
|
85
|
-
Args:
|
|
86
|
-
prepare_test_env: Fixture to prepare the environment before each test.
|
|
87
|
-
"""
|
|
88
|
-
|
|
89
|
-
prepare_test_env()
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
@pytest.fixture(scope='session')
|
|
93
|
-
def apify_token() -> str:
|
|
94
|
-
api_token = os.getenv(_TOKEN_ENV_VAR)
|
|
95
|
-
|
|
96
|
-
if not api_token:
|
|
97
|
-
raise RuntimeError(f'{_TOKEN_ENV_VAR} environment variable is missing, cannot run tests!')
|
|
98
|
-
|
|
99
|
-
return api_token
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@pytest.fixture(scope='session')
|
|
103
|
-
def apify_client_async(apify_token: str) -> ApifyClientAsync:
|
|
104
|
-
"""Create an instance of the ApifyClientAsync."""
|
|
105
|
-
api_url = os.getenv(_API_URL_ENV_VAR)
|
|
106
|
-
|
|
107
|
-
return ApifyClientAsync(apify_token, api_url=api_url)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
@pytest.fixture(params=['single', 'shared'])
|
|
111
|
-
async def request_queue_apify(
|
|
112
|
-
apify_token: str, monkeypatch: pytest.MonkeyPatch, request: pytest.FixtureRequest
|
|
113
|
-
) -> AsyncGenerator[RequestQueue]:
|
|
114
|
-
"""Create an instance of the Apify request queue on the platform and drop it when the test is finished."""
|
|
115
|
-
monkeypatch.setenv(ApifyEnvVars.TOKEN, apify_token)
|
|
116
|
-
|
|
117
|
-
async with Actor:
|
|
118
|
-
rq = await RequestQueue.open(storage_client=ApifyStorageClient(request_queue_access=request.param))
|
|
119
|
-
yield rq
|
|
120
|
-
await rq.drop()
|
|
29
|
+
_SDK_ROOT_PATH = Path(__file__).parent.parent.parent.parent.resolve()
|
|
121
30
|
|
|
122
31
|
|
|
123
32
|
@pytest.fixture(scope='session')
|
|
@@ -167,8 +76,7 @@ def actor_base_source_files(sdk_wheel_path: Path) -> dict[str, str | bytes]:
|
|
|
167
76
|
source_files: dict[str, str | bytes] = {}
|
|
168
77
|
|
|
169
78
|
# First read the actor_source_base files
|
|
170
|
-
|
|
171
|
-
actor_source_base_path = sdk_root_path / 'tests/integration/actor_source_base'
|
|
79
|
+
actor_source_base_path = _SDK_ROOT_PATH / 'tests/integration/actor/actor_source_base'
|
|
172
80
|
|
|
173
81
|
for path in actor_source_base_path.glob('**/*'):
|
|
174
82
|
if not path.is_file():
|
|
@@ -346,11 +254,9 @@ def make_actor(
|
|
|
346
254
|
|
|
347
255
|
yield _make_actor
|
|
348
256
|
|
|
349
|
-
client = ApifyClient(token=apify_token, api_url=os.getenv(_API_URL_ENV_VAR))
|
|
350
|
-
|
|
351
257
|
# Delete all the generated Actors.
|
|
352
258
|
for actor_id in actors_for_cleanup:
|
|
353
|
-
actor_client =
|
|
259
|
+
actor_client = ApifyClient(token=apify_token, api_url=os.getenv(_API_URL_ENV_VAR)).actor(actor_id)
|
|
354
260
|
|
|
355
261
|
if (actor := actor_client.get()) is not None:
|
|
356
262
|
actor_client.update(
|
|
@@ -387,7 +293,7 @@ class RunActorFunction(Protocol):
|
|
|
387
293
|
|
|
388
294
|
|
|
389
295
|
@pytest.fixture(scope='session')
|
|
390
|
-
def run_actor(
|
|
296
|
+
def run_actor(apify_client_async: ApifyClientAsync) -> RunActorFunction:
|
|
391
297
|
"""Fixture for calling an Actor run and waiting for its completion.
|
|
392
298
|
|
|
393
299
|
This fixture returns a function that initiates an Actor run with optional run input, waits for its completion,
|
|
@@ -408,8 +314,7 @@ def run_actor(apify_token: str) -> RunActorFunction:
|
|
|
408
314
|
assert isinstance(call_result, dict), 'The result of ActorClientAsync.call() is not a dictionary.'
|
|
409
315
|
assert 'id' in call_result, 'The result of ActorClientAsync.call() does not contain an ID.'
|
|
410
316
|
|
|
411
|
-
|
|
412
|
-
run_client = client.run(call_result['id'])
|
|
317
|
+
run_client = apify_client_async.run(call_result['id'])
|
|
413
318
|
run_result = await run_client.wait_for_finish(wait_secs=600)
|
|
414
319
|
|
|
415
320
|
return ActorRun.model_validate(run_result)
|
{apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_actor_api_helpers.py
RENAMED
|
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
|
|
7
7
|
from crawlee._utils.crypto import crypto_random_object_id
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from .._utils import generate_unique_resource_name
|
|
10
10
|
from apify import Actor
|
|
11
11
|
from apify._models import ActorRun
|
|
12
12
|
|
{apify-3.0.4b1/tests/integration → apify-3.0.4b3/tests/integration/actor}/test_actor_lifecycle.py
RENAMED
|
@@ -122,7 +122,8 @@ async def test_actor_fails_correctly_with_exception(
|
|
|
122
122
|
async def test_actor_with_crawler_reboot(make_actor: MakeActorFunction, run_actor: RunActorFunction) -> None:
|
|
123
123
|
"""Test that crawler in actor works as expected after reboot.
|
|
124
124
|
|
|
125
|
-
Handle two requests. Reboot in between the two requests.
|
|
125
|
+
Handle two requests. Reboot in between the two requests. The second run should include statistics of the first run.
|
|
126
|
+
"""
|
|
126
127
|
|
|
127
128
|
async def main() -> None:
|
|
128
129
|
from crawlee._types import BasicCrawlingContext, ConcurrencySettings
|
|
@@ -152,7 +153,8 @@ async def test_actor_with_crawler_reboot(make_actor: MakeActorFunction, run_acto
|
|
|
152
153
|
await crawler.run(requests)
|
|
153
154
|
|
|
154
155
|
# Each time one request is finished.
|
|
155
|
-
|
|
156
|
+
expected_requests_finished = 1 if first_run else 2
|
|
157
|
+
assert crawler.statistics.state.requests_finished == expected_requests_finished
|
|
156
158
|
|
|
157
159
|
actor = await make_actor(label='migration', main_func=main)
|
|
158
160
|
run_result = await run_actor(actor)
|
|
@@ -2,17 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
5
|
from apify import Actor, __version__
|
|
8
6
|
|
|
9
7
|
if TYPE_CHECKING:
|
|
10
8
|
from .conftest import MakeActorFunction, RunActorFunction
|
|
11
9
|
|
|
12
10
|
|
|
13
|
-
# TODO: What to do with the `browserforge` output?
|
|
14
|
-
# https://github.com/apify/apify-sdk-python/issues/423
|
|
15
|
-
@pytest.mark.skip
|
|
16
11
|
async def test_actor_logging(
|
|
17
12
|
make_actor: MakeActorFunction,
|
|
18
13
|
run_actor: RunActorFunction,
|
|
@@ -64,12 +59,15 @@ async def test_actor_logging(
|
|
|
64
59
|
run_log_lines = [line[25:] for line in run_log_lines]
|
|
65
60
|
|
|
66
61
|
# This might be way too specific and easy to break, but let's hope not
|
|
67
|
-
assert run_log_lines.pop(0).startswith('ACTOR: Pulling
|
|
68
|
-
assert run_log_lines.pop(0) == 'ACTOR: Creating
|
|
69
|
-
assert run_log_lines.pop(0) == 'ACTOR: Starting
|
|
70
|
-
assert run_log_lines.pop(0) ==
|
|
62
|
+
assert run_log_lines.pop(0).startswith('ACTOR: Pulling container image of build')
|
|
63
|
+
assert run_log_lines.pop(0) == 'ACTOR: Creating container.'
|
|
64
|
+
assert run_log_lines.pop(0) == 'ACTOR: Starting container.'
|
|
65
|
+
assert run_log_lines.pop(0) == (
|
|
66
|
+
'[apify._configuration] WARN Actor is running on the Apify platform,'
|
|
67
|
+
' `disable_browser_sandbox` was changed to True.'
|
|
68
|
+
)
|
|
71
69
|
assert run_log_lines.pop(0).startswith(
|
|
72
|
-
f'[apify] INFO
|
|
70
|
+
f'[apify] INFO Initializing Actor ({{"apify_sdk_version": "{__version__}", "apify_client_version": "'
|
|
73
71
|
)
|
|
74
72
|
assert run_log_lines.pop(0) == '[apify] DEBUG Debug message'
|
|
75
73
|
assert run_log_lines.pop(0) == '[apify] INFO Info message'
|