apify 2.7.1b8__tar.gz → 2.7.1b9__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-2.7.1b8 → apify-2.7.1b9}/.github/workflows/build_and_deploy_docs.yaml +1 -1
- {apify-2.7.1b8 → apify-2.7.1b9}/.github/workflows/release.yaml +1 -8
- {apify-2.7.1b8 → apify-2.7.1b9}/CHANGELOG.md +1 -107
- {apify-2.7.1b8 → apify-2.7.1b9}/PKG-INFO +1 -1
- {apify-2.7.1b8 → apify-2.7.1b9}/pyproject.toml +2 -2
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/storage_clients/_apify/_request_queue_client.py +70 -72
- apify-2.7.1b9/tests/integration/_utils.py +17 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/conftest.py +15 -1
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/test_actor_request_queue.py +87 -7
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/test_request_queue.py +86 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/uv.lock +5 -5
- {apify-2.7.1b8 → apify-2.7.1b9}/website/package-lock.json +4 -4
- {apify-2.7.1b8 → apify-2.7.1b9}/website/package.json +1 -1
- apify-2.7.1b8/tests/integration/_utils.py +0 -9
- {apify-2.7.1b8 → apify-2.7.1b9}/.editorconfig +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/.github/CODEOWNERS +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/.github/workflows/check_pr_title.yaml +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/.github/workflows/pre_release.yaml +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/.github/workflows/run_code_checks.yaml +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/.github/workflows/update_new_issue.yaml +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/.gitignore +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/.markdownlint.yaml +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/.pre-commit-config.yaml +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/CONTRIBUTING.md +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/LICENSE +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/Makefile +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/README.md +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/01_overview/01_introduction.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/01_overview/02_running_actors_locally.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/01_overview/03_actor_structure.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/01_overview/code/01_introduction.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/01_overview/code/actor_structure/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/01_overview/code/actor_structure/__main__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/01_overview/code/actor_structure/main.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/01_overview/code/actor_structure/py.typed +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/01_beautifulsoup_httpx.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/02_crawlee.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/03_playwright.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/04_selenium.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/05_scrapy.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/code/01_beautifulsoup_httpx.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/code/02_crawlee_beautifulsoup.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/code/02_crawlee_playwright.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/code/03_playwright.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/code/04_selenium.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/code/scrapy_project/src/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/code/scrapy_project/src/__main__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/code/scrapy_project/src/items.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/code/scrapy_project/src/main.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/code/scrapy_project/src/py.typed +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/code/scrapy_project/src/settings.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/code/scrapy_project/src/spiders/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/code/scrapy_project/src/spiders/py.typed +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/02_guides/code/scrapy_project/src/spiders/title.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/01_actor_lifecycle.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/02_actor_input.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/03_storages.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/04_actor_events.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/05_proxy_management.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/06_interacting_with_other_actors.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/07_webhooks.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/08_access_apify_api.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/09_running_webserver.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/10_logging.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/11_configuration.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/12_pay_per_event.mdx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/01_context_manager.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/01_init_exit.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/01_reboot.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/01_status_message.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/02_input.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/03_dataset_exports.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/03_dataset_read_write.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/03_deleting_storages.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/03_kvs_iterating.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/03_kvs_public_url.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/03_kvs_read_write.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/03_opening_storages.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/03_rq.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/04_actor_events.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/05_apify_proxy.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/05_apify_proxy_config.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/05_custom_proxy.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/05_custom_proxy_function.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/05_proxy_actor_input.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/05_proxy_httpx.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/05_proxy_rotation.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/06_interacting_call.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/06_interacting_call_task.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/06_interacting_metamorph.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/06_interacting_start.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/07_webhook.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/07_webhook_preventing.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/08_actor_client.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/08_actor_new_client.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/09_webserver.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/10_log_config.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/10_logger_usage.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/10_redirect_log.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/10_redirect_log_existing_run.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/11_config.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/actor_charge.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/03_concepts/code/conditional_actor_charge.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/04_upgrading/upgrading_to_v2.md +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/04_upgrading/upgrading_to_v3.md +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/docs/pyproject.toml +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/renovate.json +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/_actor.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/_charging.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/_configuration.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/_consts.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/_crypto.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/_models.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/_proxy_configuration.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/_utils.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/events/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/events/_apify_event_manager.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/events/_types.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/events/py.typed +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/log.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/py.typed +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/request_loaders/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/request_loaders/_apify_request_list.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/request_loaders/py.typed +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/_actor_runner.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/_async_thread.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/_logging_config.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/extensions/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/extensions/_httpcache.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/middlewares/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/middlewares/apify_proxy.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/middlewares/py.typed +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/pipelines/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/pipelines/actor_dataset_push.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/pipelines/py.typed +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/py.typed +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/requests.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/scheduler.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/scrapy/utils.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/storage_clients/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/storage_clients/_apify/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/storage_clients/_apify/_dataset_client.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/storage_clients/_apify/_key_value_store_client.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/storage_clients/_apify/_models.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/storage_clients/_apify/_storage_client.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/storage_clients/_apify/py.typed +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/storage_clients/_file_system/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/storage_clients/_file_system/_key_value_store_client.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/storage_clients/_file_system/_storage_client.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/storage_clients/py.typed +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/storages/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/src/apify/storages/py.typed +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/README.md +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/actor_source_base/Dockerfile +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/actor_source_base/requirements.txt +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/actor_source_base/server.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/actor_source_base/src/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/actor_source_base/src/__main__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/actor_source_base/src/main.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/test_actor_api_helpers.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/test_actor_call_timeouts.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/test_actor_charge.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/test_actor_create_proxy_configuration.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/test_actor_dataset.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/test_actor_events.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/test_actor_key_value_store.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/test_actor_lifecycle.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/test_actor_log.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/test_actor_scrapy.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/test_crawlers_with_storages.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/integration/test_fixtures.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/actor/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/actor/test_actor_create_proxy_configuration.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/actor/test_actor_dataset.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/actor/test_actor_env_helpers.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/actor/test_actor_helpers.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/actor/test_actor_key_value_store.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/actor/test_actor_lifecycle.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/actor/test_actor_log.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/actor/test_actor_non_default_instance.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/actor/test_actor_request_queue.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/actor/test_configuration.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/actor/test_request_list.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/conftest.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/events/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/events/test_apify_event_manager.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/scrapy/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/scrapy/extensions/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/scrapy/extensions/test_httpcache.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/scrapy/middlewares/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/scrapy/middlewares/test_apify_proxy.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/scrapy/pipelines/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/scrapy/pipelines/test_actor_dataset_push.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/scrapy/requests/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/scrapy/requests/test_to_apify_request.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/scrapy/requests/test_to_scrapy_request.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/scrapy/utils/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/scrapy/utils/test_apply_apify_settings.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/scrapy/utils/test_get_basic_auth_header.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/storage_clients/__init__.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/storage_clients/test_apify_request_queue_client.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/storage_clients/test_file_system.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/test_crypto.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/tests/unit/test_proxy_configuration.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/.eslintrc.json +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/babel.config.js +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/build_api_reference.sh +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/docusaurus.config.js +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/generate_module_shortcuts.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/sidebars.js +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/src/components/ApiLink.jsx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/src/components/Gradients.jsx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/src/components/Highlights.jsx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/src/components/Highlights.module.css +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/src/components/RunnableCodeBlock.jsx +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/src/components/RunnableCodeBlock.module.css +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/src/css/custom.css +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/src/pages/home_page_example.py +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/src/pages/index.js +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/src/pages/index.module.css +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/static/.nojekyll +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/static/img/docs-og.png +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/static/img/guides/redirected_logs_example.webp +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/tools/docs-prettier.config.js +0 -0
- {apify-2.7.1b8 → apify-2.7.1b9}/website/tools/utils/externalLink.js +0 -0
|
@@ -61,16 +61,9 @@ jobs:
|
|
|
61
61
|
with:
|
|
62
62
|
python-versions: '["3.10", "3.11", "3.12", "3.13"]'
|
|
63
63
|
|
|
64
|
-
integration_tests:
|
|
65
|
-
name: Integration tests
|
|
66
|
-
uses: apify/workflows/.github/workflows/python_integration_tests.yaml@main
|
|
67
|
-
secrets: inherit
|
|
68
|
-
with:
|
|
69
|
-
python-versions: '["3.10", "3.13"]'
|
|
70
|
-
|
|
71
64
|
update_changelog:
|
|
72
65
|
name: Update changelog
|
|
73
|
-
needs: [release_metadata, lint_check, type_check, unit_tests
|
|
66
|
+
needs: [release_metadata, lint_check, type_check, unit_tests]
|
|
74
67
|
uses: apify/workflows/.github/workflows/python_bump_and_update_changelog.yaml@main
|
|
75
68
|
with:
|
|
76
69
|
version_number: ${{ needs.release_metadata.outputs.version_number }}
|
|
@@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file.
|
|
|
14
14
|
- Restrict apify-shared and apify-client versions ([#523](https://github.com/apify/apify-sdk-python/pull/523)) ([b3ae5a9](https://github.com/apify/apify-sdk-python/commit/b3ae5a972a65454a4998eda59c9fcc3f6b7e8579)) by [@vdusek](https://github.com/vdusek)
|
|
15
15
|
- Expose `APIFY_USER_IS_PAYING` env var to the configuration ([#507](https://github.com/apify/apify-sdk-python/pull/507)) ([0801e54](https://github.com/apify/apify-sdk-python/commit/0801e54887317c1280cc6828ecd3f2cc53287e76)) by [@stepskop](https://github.com/stepskop)
|
|
16
16
|
- Resolve DeprecationWarning in ApifyEventManager ([#555](https://github.com/apify/apify-sdk-python/pull/555)) ([0c5111d](https://github.com/apify/apify-sdk-python/commit/0c5111dafe19796ec1fb9652a44c031bed9758df)) by [@vdusek](https://github.com/vdusek), closes [#343](https://github.com/apify/apify-sdk-python/issues/343)
|
|
17
|
+
- Use same `client_key` for `Actor` created `request_queue` and improve its metadata estimation ([#552](https://github.com/apify/apify-sdk-python/pull/552)) ([7e4e5da](https://github.com/apify/apify-sdk-python/commit/7e4e5da81dd87e84ebeef2bd336c6c1d422cb9a7)) by [@Pijukatel](https://github.com/Pijukatel), closes [#536](https://github.com/apify/apify-sdk-python/issues/536)
|
|
17
18
|
|
|
18
19
|
### Chore
|
|
19
20
|
|
|
@@ -39,113 +40,6 @@ All notable changes to this project will be documented in this file.
|
|
|
39
40
|
- Tagline overlap ([#501](https://github.com/apify/apify-sdk-python/pull/501)) ([bae8340](https://github.com/apify/apify-sdk-python/commit/bae8340c46fea756ea35ea4d591da84c09d478e2)) by [@katzino](https://github.com/katzino)
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
## [2.7.0](https://github.com/apify/apify-sdk-python/releases/tag/v2.7.0) (2025-07-14)
|
|
43
|
-
|
|
44
|
-
### 🚀 Features
|
|
45
|
-
|
|
46
|
-
- **crypto:** Decrypt secret objects ([#482](https://github.com/apify/apify-sdk-python/pull/482)) ([ce9daf7](https://github.com/apify/apify-sdk-python/commit/ce9daf7381212b8dc194e8a643e5ca0dedbc0078)) by [@MFori](https://github.com/MFori)
|
|
47
|
-
|
|
48
|
-
### 🐛 Bug Fixes
|
|
49
|
-
|
|
50
|
-
- Sync `@docusaurus` theme version [internal] ([#500](https://github.com/apify/apify-sdk-python/pull/500)) ([a7485e7](https://github.com/apify/apify-sdk-python/commit/a7485e7d2276fde464ce862573d5b95e7d4d836a)) by [@katzino](https://github.com/katzino)
|
|
51
|
-
- Tagline overlap ([#501](https://github.com/apify/apify-sdk-python/pull/501)) ([bae8340](https://github.com/apify/apify-sdk-python/commit/bae8340c46fea756ea35ea4d591da84c09d478e2)) by [@katzino](https://github.com/katzino)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
## [2.7.0](https://github.com/apify/apify-sdk-python/releases/tag/v2.7.0) (2025-07-14)
|
|
55
|
-
|
|
56
|
-
### 🚀 Features
|
|
57
|
-
|
|
58
|
-
- **crypto:** Decrypt secret objects ([#482](https://github.com/apify/apify-sdk-python/pull/482)) ([ce9daf7](https://github.com/apify/apify-sdk-python/commit/ce9daf7381212b8dc194e8a643e5ca0dedbc0078)) by [@MFori](https://github.com/MFori)
|
|
59
|
-
|
|
60
|
-
### 🐛 Bug Fixes
|
|
61
|
-
|
|
62
|
-
- Sync `@docusaurus` theme version [internal] ([#500](https://github.com/apify/apify-sdk-python/pull/500)) ([a7485e7](https://github.com/apify/apify-sdk-python/commit/a7485e7d2276fde464ce862573d5b95e7d4d836a)) by [@katzino](https://github.com/katzino)
|
|
63
|
-
- Tagline overlap ([#501](https://github.com/apify/apify-sdk-python/pull/501)) ([bae8340](https://github.com/apify/apify-sdk-python/commit/bae8340c46fea756ea35ea4d591da84c09d478e2)) by [@katzino](https://github.com/katzino)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
## [2.7.0](https://github.com/apify/apify-sdk-python/releases/tag/v2.7.0) (2025-07-14)
|
|
67
|
-
|
|
68
|
-
### 🚀 Features
|
|
69
|
-
|
|
70
|
-
- **crypto:** Decrypt secret objects ([#482](https://github.com/apify/apify-sdk-python/pull/482)) ([ce9daf7](https://github.com/apify/apify-sdk-python/commit/ce9daf7381212b8dc194e8a643e5ca0dedbc0078)) by [@MFori](https://github.com/MFori)
|
|
71
|
-
|
|
72
|
-
### 🐛 Bug Fixes
|
|
73
|
-
|
|
74
|
-
- Sync `@docusaurus` theme version [internal] ([#500](https://github.com/apify/apify-sdk-python/pull/500)) ([a7485e7](https://github.com/apify/apify-sdk-python/commit/a7485e7d2276fde464ce862573d5b95e7d4d836a)) by [@katzino](https://github.com/katzino)
|
|
75
|
-
- Tagline overlap ([#501](https://github.com/apify/apify-sdk-python/pull/501)) ([bae8340](https://github.com/apify/apify-sdk-python/commit/bae8340c46fea756ea35ea4d591da84c09d478e2)) by [@katzino](https://github.com/katzino)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
## [2.7.0](https://github.com/apify/apify-sdk-python/releases/tag/v2.7.0) (2025-07-14)
|
|
79
|
-
|
|
80
|
-
### 🚀 Features
|
|
81
|
-
|
|
82
|
-
- **crypto:** Decrypt secret objects ([#482](https://github.com/apify/apify-sdk-python/pull/482)) ([ce9daf7](https://github.com/apify/apify-sdk-python/commit/ce9daf7381212b8dc194e8a643e5ca0dedbc0078)) by [@MFori](https://github.com/MFori)
|
|
83
|
-
|
|
84
|
-
### 🐛 Bug Fixes
|
|
85
|
-
|
|
86
|
-
- Sync `@docusaurus` theme version [internal] ([#500](https://github.com/apify/apify-sdk-python/pull/500)) ([a7485e7](https://github.com/apify/apify-sdk-python/commit/a7485e7d2276fde464ce862573d5b95e7d4d836a)) by [@katzino](https://github.com/katzino)
|
|
87
|
-
- Tagline overlap ([#501](https://github.com/apify/apify-sdk-python/pull/501)) ([bae8340](https://github.com/apify/apify-sdk-python/commit/bae8340c46fea756ea35ea4d591da84c09d478e2)) by [@katzino](https://github.com/katzino)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
## [2.7.0](https://github.com/apify/apify-sdk-python/releases/tag/v2.7.0) (2025-07-14)
|
|
91
|
-
|
|
92
|
-
### 🚀 Features
|
|
93
|
-
|
|
94
|
-
- **crypto:** Decrypt secret objects ([#482](https://github.com/apify/apify-sdk-python/pull/482)) ([ce9daf7](https://github.com/apify/apify-sdk-python/commit/ce9daf7381212b8dc194e8a643e5ca0dedbc0078)) by [@MFori](https://github.com/MFori)
|
|
95
|
-
|
|
96
|
-
### 🐛 Bug Fixes
|
|
97
|
-
|
|
98
|
-
- Sync `@docusaurus` theme version [internal] ([#500](https://github.com/apify/apify-sdk-python/pull/500)) ([a7485e7](https://github.com/apify/apify-sdk-python/commit/a7485e7d2276fde464ce862573d5b95e7d4d836a)) by [@katzino](https://github.com/katzino)
|
|
99
|
-
- Tagline overlap ([#501](https://github.com/apify/apify-sdk-python/pull/501)) ([bae8340](https://github.com/apify/apify-sdk-python/commit/bae8340c46fea756ea35ea4d591da84c09d478e2)) by [@katzino](https://github.com/katzino)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
## [2.7.0](https://github.com/apify/apify-sdk-python/releases/tag/v2.7.0) (2025-07-14)
|
|
103
|
-
|
|
104
|
-
### 🚀 Features
|
|
105
|
-
|
|
106
|
-
- **crypto:** Decrypt secret objects ([#482](https://github.com/apify/apify-sdk-python/pull/482)) ([ce9daf7](https://github.com/apify/apify-sdk-python/commit/ce9daf7381212b8dc194e8a643e5ca0dedbc0078)) by [@MFori](https://github.com/MFori)
|
|
107
|
-
|
|
108
|
-
### 🐛 Bug Fixes
|
|
109
|
-
|
|
110
|
-
- Sync `@docusaurus` theme version [internal] ([#500](https://github.com/apify/apify-sdk-python/pull/500)) ([a7485e7](https://github.com/apify/apify-sdk-python/commit/a7485e7d2276fde464ce862573d5b95e7d4d836a)) by [@katzino](https://github.com/katzino)
|
|
111
|
-
- Tagline overlap ([#501](https://github.com/apify/apify-sdk-python/pull/501)) ([bae8340](https://github.com/apify/apify-sdk-python/commit/bae8340c46fea756ea35ea4d591da84c09d478e2)) by [@katzino](https://github.com/katzino)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
## [2.7.0](https://github.com/apify/apify-sdk-python/releases/tag/v2.7.0) (2025-07-14)
|
|
115
|
-
|
|
116
|
-
### 🚀 Features
|
|
117
|
-
|
|
118
|
-
- **crypto:** Decrypt secret objects ([#482](https://github.com/apify/apify-sdk-python/pull/482)) ([ce9daf7](https://github.com/apify/apify-sdk-python/commit/ce9daf7381212b8dc194e8a643e5ca0dedbc0078)) by [@MFori](https://github.com/MFori)
|
|
119
|
-
|
|
120
|
-
### 🐛 Bug Fixes
|
|
121
|
-
|
|
122
|
-
- Sync `@docusaurus` theme version [internal] ([#500](https://github.com/apify/apify-sdk-python/pull/500)) ([a7485e7](https://github.com/apify/apify-sdk-python/commit/a7485e7d2276fde464ce862573d5b95e7d4d836a)) by [@katzino](https://github.com/katzino)
|
|
123
|
-
- Tagline overlap ([#501](https://github.com/apify/apify-sdk-python/pull/501)) ([bae8340](https://github.com/apify/apify-sdk-python/commit/bae8340c46fea756ea35ea4d591da84c09d478e2)) by [@katzino](https://github.com/katzino)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
## [2.7.0](https://github.com/apify/apify-sdk-python/releases/tag/v2.7.0) (2025-07-14)
|
|
127
|
-
|
|
128
|
-
### 🚀 Features
|
|
129
|
-
|
|
130
|
-
- **crypto:** Decrypt secret objects ([#482](https://github.com/apify/apify-sdk-python/pull/482)) ([ce9daf7](https://github.com/apify/apify-sdk-python/commit/ce9daf7381212b8dc194e8a643e5ca0dedbc0078)) by [@MFori](https://github.com/MFori)
|
|
131
|
-
|
|
132
|
-
### 🐛 Bug Fixes
|
|
133
|
-
|
|
134
|
-
- Sync `@docusaurus` theme version [internal] ([#500](https://github.com/apify/apify-sdk-python/pull/500)) ([a7485e7](https://github.com/apify/apify-sdk-python/commit/a7485e7d2276fde464ce862573d5b95e7d4d836a)) by [@katzino](https://github.com/katzino)
|
|
135
|
-
- Tagline overlap ([#501](https://github.com/apify/apify-sdk-python/pull/501)) ([bae8340](https://github.com/apify/apify-sdk-python/commit/bae8340c46fea756ea35ea4d591da84c09d478e2)) by [@katzino](https://github.com/katzino)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
## [2.7.0](https://github.com/apify/apify-sdk-python/releases/tag/v2.7.0) (2025-07-14)
|
|
139
|
-
|
|
140
|
-
### 🚀 Features
|
|
141
|
-
|
|
142
|
-
- **crypto:** Decrypt secret objects ([#482](https://github.com/apify/apify-sdk-python/pull/482)) ([ce9daf7](https://github.com/apify/apify-sdk-python/commit/ce9daf7381212b8dc194e8a643e5ca0dedbc0078)) by [@MFori](https://github.com/MFori)
|
|
143
|
-
|
|
144
|
-
### 🐛 Bug Fixes
|
|
145
|
-
|
|
146
|
-
- Sync `@docusaurus` theme version [internal] ([#500](https://github.com/apify/apify-sdk-python/pull/500)) ([a7485e7](https://github.com/apify/apify-sdk-python/commit/a7485e7d2276fde464ce862573d5b95e7d4d836a)) by [@katzino](https://github.com/katzino)
|
|
147
|
-
- Tagline overlap ([#501](https://github.com/apify/apify-sdk-python/pull/501)) ([bae8340](https://github.com/apify/apify-sdk-python/commit/bae8340c46fea756ea35ea4d591da84c09d478e2)) by [@katzino](https://github.com/katzino)
|
|
148
|
-
|
|
149
43
|
|
|
150
44
|
## [2.7.3](https://github.com/apify/apify-sdk-python/releases/tag/v2.7.3) (2025-08-11)
|
|
151
45
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "apify"
|
|
7
|
-
version = "2.7.
|
|
7
|
+
version = "2.7.1b9"
|
|
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" }
|
|
@@ -79,7 +79,7 @@ dev = [
|
|
|
79
79
|
"pytest~=8.4.0",
|
|
80
80
|
"ruff~=0.12.0",
|
|
81
81
|
"setuptools", # setuptools are used by pytest but not explicitly required
|
|
82
|
-
"types-cachetools~=6.
|
|
82
|
+
"types-cachetools~=6.1.0.20250717",
|
|
83
83
|
"uvicorn[standard]",
|
|
84
84
|
"werkzeug~=3.1.0", # Werkzeug is used by httpserver
|
|
85
85
|
]
|
|
@@ -13,6 +13,7 @@ from cachetools import LRUCache
|
|
|
13
13
|
from typing_extensions import override
|
|
14
14
|
|
|
15
15
|
from apify_client import ApifyClientAsync
|
|
16
|
+
from crawlee._utils.crypto import crypto_random_object_id
|
|
16
17
|
from crawlee.storage_clients._base import RequestQueueClient
|
|
17
18
|
from crawlee.storage_clients.models import AddRequestsResponse, ProcessedRequest, RequestQueueMetadata
|
|
18
19
|
|
|
@@ -65,10 +66,7 @@ class ApifyRequestQueueClient(RequestQueueClient):
|
|
|
65
66
|
self,
|
|
66
67
|
*,
|
|
67
68
|
api_client: RequestQueueClientAsync,
|
|
68
|
-
|
|
69
|
-
name: str | None,
|
|
70
|
-
total_request_count: int,
|
|
71
|
-
handled_request_count: int,
|
|
69
|
+
metadata: RequestQueueMetadata,
|
|
72
70
|
) -> None:
|
|
73
71
|
"""Initialize a new instance.
|
|
74
72
|
|
|
@@ -77,11 +75,8 @@ class ApifyRequestQueueClient(RequestQueueClient):
|
|
|
77
75
|
self._api_client = api_client
|
|
78
76
|
"""The Apify request queue client for API operations."""
|
|
79
77
|
|
|
80
|
-
self.
|
|
81
|
-
"""
|
|
82
|
-
|
|
83
|
-
self._name = name
|
|
84
|
-
"""The name of the request queue."""
|
|
78
|
+
self._metadata = metadata
|
|
79
|
+
"""Additional data related to the RequestQueue."""
|
|
85
80
|
|
|
86
81
|
self._queue_head = deque[str]()
|
|
87
82
|
"""A deque to store request unique keys in the queue head."""
|
|
@@ -95,40 +90,43 @@ class ApifyRequestQueueClient(RequestQueueClient):
|
|
|
95
90
|
self._should_check_for_forefront_requests = False
|
|
96
91
|
"""Whether to check for forefront requests in the next list_head call."""
|
|
97
92
|
|
|
98
|
-
self._had_multiple_clients = False
|
|
99
|
-
"""Whether the request queue has been accessed by multiple clients."""
|
|
100
|
-
|
|
101
|
-
self._initial_total_count = total_request_count
|
|
102
|
-
"""The initial total request count (from the API) when the queue was opened."""
|
|
103
|
-
|
|
104
|
-
self._initial_handled_count = handled_request_count
|
|
105
|
-
"""The initial handled request count (from the API) when the queue was opened."""
|
|
106
|
-
|
|
107
|
-
self._assumed_total_count = 0
|
|
108
|
-
"""The number of requests we assume are in the queue (tracked manually for this instance)."""
|
|
109
|
-
|
|
110
|
-
self._assumed_handled_count = 0
|
|
111
|
-
"""The number of requests we assume have been handled (tracked manually for this instance)."""
|
|
112
|
-
|
|
113
93
|
self._fetch_lock = asyncio.Lock()
|
|
114
94
|
"""Fetch lock to minimize race conditions when communicating with API."""
|
|
115
95
|
|
|
96
|
+
async def _get_metadata_estimate(self) -> RequestQueueMetadata:
|
|
97
|
+
"""Try to get cached metadata first. If multiple clients, fuse with global metadata.
|
|
98
|
+
|
|
99
|
+
This method is used internally to avoid unnecessary API call unless needed (multiple clients).
|
|
100
|
+
Local estimation of metadata is without delay, unlike metadata from API. In situation where there is only one
|
|
101
|
+
client, it is the better choice.
|
|
102
|
+
"""
|
|
103
|
+
if self._metadata.had_multiple_clients:
|
|
104
|
+
return await self.get_metadata()
|
|
105
|
+
# Get local estimation (will not include changes done bo another client)
|
|
106
|
+
return self._metadata
|
|
107
|
+
|
|
116
108
|
@override
|
|
117
109
|
async def get_metadata(self) -> RequestQueueMetadata:
|
|
118
|
-
|
|
119
|
-
handled_count = self._initial_handled_count + self._assumed_handled_count
|
|
120
|
-
pending_count = total_count - handled_count
|
|
110
|
+
"""Get metadata about the request queue.
|
|
121
111
|
|
|
112
|
+
Returns:
|
|
113
|
+
Metadata from the API, merged with local estimation, because in some cases, the data from the API can
|
|
114
|
+
be delayed.
|
|
115
|
+
"""
|
|
116
|
+
response = await self._api_client.get()
|
|
117
|
+
if response is None:
|
|
118
|
+
raise ValueError('Failed to fetch request queue metadata from the API.')
|
|
119
|
+
# Enhance API response by local estimations (API can be delayed few seconds, while local estimation not.)
|
|
122
120
|
return RequestQueueMetadata(
|
|
123
|
-
id=
|
|
124
|
-
name=
|
|
125
|
-
total_request_count=
|
|
126
|
-
handled_request_count=
|
|
127
|
-
pending_request_count=
|
|
128
|
-
created_at=
|
|
129
|
-
modified_at=
|
|
130
|
-
accessed_at=
|
|
131
|
-
had_multiple_clients=self.
|
|
121
|
+
id=response['id'],
|
|
122
|
+
name=response['name'],
|
|
123
|
+
total_request_count=max(response['totalRequestCount'], self._metadata.total_request_count),
|
|
124
|
+
handled_request_count=max(response['handledRequestCount'], self._metadata.handled_request_count),
|
|
125
|
+
pending_request_count=response['pendingRequestCount'],
|
|
126
|
+
created_at=min(response['createdAt'], self._metadata.created_at),
|
|
127
|
+
modified_at=max(response['modifiedAt'], self._metadata.modified_at),
|
|
128
|
+
accessed_at=max(response['accessedAt'], self._metadata.accessed_at),
|
|
129
|
+
had_multiple_clients=response['hadMultipleClients'] or self._metadata.had_multiple_clients,
|
|
132
130
|
)
|
|
133
131
|
|
|
134
132
|
@classmethod
|
|
@@ -187,27 +185,34 @@ class ApifyRequestQueueClient(RequestQueueClient):
|
|
|
187
185
|
)
|
|
188
186
|
apify_rqs_client = apify_client_async.request_queues()
|
|
189
187
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
188
|
+
match (id, name):
|
|
189
|
+
case (None, None):
|
|
190
|
+
# If both id and name are None, try to get the default storage ID from environment variables.
|
|
191
|
+
# The default storage ID environment variable is set by the Apify platform. It also contains
|
|
192
|
+
# a new storage ID after Actor's reboot or migration.
|
|
193
|
+
id = configuration.default_request_queue_id
|
|
194
|
+
case (None, name):
|
|
195
|
+
# If only name is provided, get or create the storage by name.
|
|
196
|
+
id = RequestQueueMetadata.model_validate(
|
|
197
|
+
await apify_rqs_client.get_or_create(name=name),
|
|
198
|
+
).id
|
|
199
|
+
case (_, None):
|
|
200
|
+
# If only id is provided, use it.
|
|
201
|
+
pass
|
|
202
|
+
case (_, _):
|
|
203
|
+
# If both id and name are provided, raise an error.
|
|
204
|
+
raise ValueError('Only one of "id" or "name" can be specified, not both.')
|
|
205
|
+
if id is None:
|
|
206
|
+
raise RuntimeError('Unreachable code')
|
|
197
207
|
|
|
198
|
-
#
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
208
|
+
# Use suitable client_key to make `hadMultipleClients` response of Apify API useful.
|
|
209
|
+
# It should persist across migrated or resurrected Actor runs on the Apify platform.
|
|
210
|
+
_api_max_client_key_length = 32
|
|
211
|
+
client_key = (configuration.actor_run_id or crypto_random_object_id(length=_api_max_client_key_length))[
|
|
212
|
+
:_api_max_client_key_length
|
|
213
|
+
]
|
|
204
214
|
|
|
205
|
-
|
|
206
|
-
# The default storage ID environment variable is set by the Apify platform. It also contains
|
|
207
|
-
# a new storage ID after Actor's reboot or migration.
|
|
208
|
-
if id is None and name is None:
|
|
209
|
-
id = configuration.default_request_queue_id
|
|
210
|
-
apify_rq_client = apify_client_async.request_queue(request_queue_id=id)
|
|
215
|
+
apify_rq_client = apify_client_async.request_queue(request_queue_id=id, client_key=client_key)
|
|
211
216
|
|
|
212
217
|
# Fetch its metadata.
|
|
213
218
|
metadata = await apify_rq_client.get()
|
|
@@ -217,27 +222,18 @@ class ApifyRequestQueueClient(RequestQueueClient):
|
|
|
217
222
|
id = RequestQueueMetadata.model_validate(
|
|
218
223
|
await apify_rqs_client.get_or_create(),
|
|
219
224
|
).id
|
|
220
|
-
apify_rq_client = apify_client_async.request_queue(request_queue_id=id)
|
|
225
|
+
apify_rq_client = apify_client_async.request_queue(request_queue_id=id, client_key=client_key)
|
|
221
226
|
|
|
222
227
|
# Verify that the storage exists by fetching its metadata again.
|
|
223
228
|
metadata = await apify_rq_client.get()
|
|
224
229
|
if metadata is None:
|
|
225
230
|
raise ValueError(f'Opening request queue with id={id} and name={name} failed.')
|
|
226
231
|
|
|
227
|
-
metadata_model = RequestQueueMetadata.model_validate(
|
|
228
|
-
await apify_rqs_client.get_or_create(),
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
# Ensure we have a valid ID.
|
|
232
|
-
if id is None:
|
|
233
|
-
raise ValueError('Request queue ID cannot be None.')
|
|
232
|
+
metadata_model = RequestQueueMetadata.model_validate(metadata)
|
|
234
233
|
|
|
235
234
|
return cls(
|
|
236
235
|
api_client=apify_rq_client,
|
|
237
|
-
|
|
238
|
-
name=name,
|
|
239
|
-
total_request_count=metadata_model.total_request_count,
|
|
240
|
-
handled_request_count=metadata_model.handled_request_count,
|
|
236
|
+
metadata=metadata_model,
|
|
241
237
|
)
|
|
242
238
|
|
|
243
239
|
@override
|
|
@@ -341,7 +337,7 @@ class ApifyRequestQueueClient(RequestQueueClient):
|
|
|
341
337
|
if not processed_request.was_already_present and not processed_request.was_already_handled:
|
|
342
338
|
new_request_count += 1
|
|
343
339
|
|
|
344
|
-
self.
|
|
340
|
+
self._metadata.total_request_count += new_request_count
|
|
345
341
|
|
|
346
342
|
return api_response
|
|
347
343
|
|
|
@@ -439,7 +435,7 @@ class ApifyRequestQueueClient(RequestQueueClient):
|
|
|
439
435
|
|
|
440
436
|
# Update assumed handled count if this wasn't already handled
|
|
441
437
|
if not processed_request.was_already_handled:
|
|
442
|
-
self.
|
|
438
|
+
self._metadata.handled_request_count += 1
|
|
443
439
|
|
|
444
440
|
# Update the cache with the handled request
|
|
445
441
|
cache_key = request.unique_key
|
|
@@ -487,7 +483,7 @@ class ApifyRequestQueueClient(RequestQueueClient):
|
|
|
487
483
|
# If the request was previously handled, decrement our handled count since
|
|
488
484
|
# we're putting it back for processing.
|
|
489
485
|
if request.was_already_handled and not processed_request.was_already_handled:
|
|
490
|
-
self.
|
|
486
|
+
self._metadata.handled_request_count -= 1
|
|
491
487
|
|
|
492
488
|
# Update the cache
|
|
493
489
|
cache_key = request.unique_key
|
|
@@ -539,7 +535,7 @@ class ApifyRequestQueueClient(RequestQueueClient):
|
|
|
539
535
|
"""Get a request by unique key, either from cache or by fetching from API.
|
|
540
536
|
|
|
541
537
|
Args:
|
|
542
|
-
unique_key: Unique
|
|
538
|
+
unique_key: Unique key of the request to get.
|
|
543
539
|
|
|
544
540
|
Returns:
|
|
545
541
|
The request if found and valid, otherwise None.
|
|
@@ -645,7 +641,7 @@ class ApifyRequestQueueClient(RequestQueueClient):
|
|
|
645
641
|
if cached_request and cached_request.hydrated:
|
|
646
642
|
items.append(cached_request.hydrated)
|
|
647
643
|
|
|
648
|
-
metadata = await self.
|
|
644
|
+
metadata = await self._get_metadata_estimate()
|
|
649
645
|
|
|
650
646
|
return RequestQueueHead(
|
|
651
647
|
limit=limit,
|
|
@@ -672,6 +668,8 @@ class ApifyRequestQueueClient(RequestQueueClient):
|
|
|
672
668
|
|
|
673
669
|
# Update the queue head cache
|
|
674
670
|
self._queue_has_locked_requests = response.get('queueHasLockedRequests', False)
|
|
671
|
+
# Check if there is another client working with the RequestQueue
|
|
672
|
+
self._metadata.had_multiple_clients = response.get('hadMultipleClients', False)
|
|
675
673
|
|
|
676
674
|
for request_data in response.get('items', []):
|
|
677
675
|
request = Request.model_validate(request_data)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from crawlee._utils.crypto import crypto_random_object_id
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def generate_unique_resource_name(label: str) -> str:
|
|
7
|
+
"""Generates a unique resource name, which will contain the given label."""
|
|
8
|
+
name_template = 'python-sdk-tests-{}-generated-{}'
|
|
9
|
+
template_length = len(name_template.format('', ''))
|
|
10
|
+
api_name_limit = 63
|
|
11
|
+
generated_random_id_length = 8
|
|
12
|
+
label_length_limit = api_name_limit - template_length - generated_random_id_length
|
|
13
|
+
|
|
14
|
+
label = label.replace('_', '-')
|
|
15
|
+
assert len(label) <= label_length_limit, f'Max label length is {label_length_limit}, but got {len(label)}'
|
|
16
|
+
|
|
17
|
+
return name_template.format(label, crypto_random_object_id(generated_random_id_length))
|
|
@@ -18,13 +18,15 @@ from crawlee import service_locator
|
|
|
18
18
|
|
|
19
19
|
import apify._actor
|
|
20
20
|
from ._utils import generate_unique_resource_name
|
|
21
|
+
from apify import Actor
|
|
21
22
|
from apify._models import ActorRun
|
|
22
23
|
|
|
23
24
|
if TYPE_CHECKING:
|
|
24
|
-
from collections.abc import Awaitable, Callable, Coroutine, Iterator, Mapping
|
|
25
|
+
from collections.abc import AsyncGenerator, Awaitable, Callable, Coroutine, Iterator, Mapping
|
|
25
26
|
from decimal import Decimal
|
|
26
27
|
|
|
27
28
|
from apify_client.clients.resource_clients import ActorClientAsync
|
|
29
|
+
from crawlee.storages import RequestQueue
|
|
28
30
|
|
|
29
31
|
_TOKEN_ENV_VAR = 'APIFY_TEST_USER_API_TOKEN'
|
|
30
32
|
_API_URL_ENV_VAR = 'APIFY_INTEGRATION_TESTS_API_URL'
|
|
@@ -104,6 +106,18 @@ def apify_client_async(apify_token: str) -> ApifyClientAsync:
|
|
|
104
106
|
return ApifyClientAsync(apify_token, api_url=api_url)
|
|
105
107
|
|
|
106
108
|
|
|
109
|
+
@pytest.fixture
|
|
110
|
+
async def request_queue_force_cloud(apify_token: str, monkeypatch: pytest.MonkeyPatch) -> AsyncGenerator[RequestQueue]:
|
|
111
|
+
"""Create an instance of the Apify request queue on the platform and drop it when the test is finished."""
|
|
112
|
+
request_queue_name = generate_unique_resource_name('request_queue')
|
|
113
|
+
monkeypatch.setenv(ApifyEnvVars.TOKEN, apify_token)
|
|
114
|
+
|
|
115
|
+
async with Actor:
|
|
116
|
+
rq = await Actor.open_request_queue(name=request_queue_name, force_cloud=True)
|
|
117
|
+
yield rq
|
|
118
|
+
await rq.drop()
|
|
119
|
+
|
|
120
|
+
|
|
107
121
|
@pytest.fixture(scope='session')
|
|
108
122
|
def sdk_wheel_path(tmp_path_factory: pytest.TempPathFactory, testrun_uid: str) -> Path:
|
|
109
123
|
"""Build the package wheel if it hasn't been built yet, and return the path to the wheel."""
|
|
@@ -11,6 +11,7 @@ from apify_shared.consts import ApifyEnvVars
|
|
|
11
11
|
|
|
12
12
|
from ._utils import generate_unique_resource_name
|
|
13
13
|
from apify import Actor, Request
|
|
14
|
+
from apify._models import ActorRun
|
|
14
15
|
|
|
15
16
|
if TYPE_CHECKING:
|
|
16
17
|
from collections.abc import AsyncGenerator
|
|
@@ -100,18 +101,17 @@ async def test_force_cloud(
|
|
|
100
101
|
async def test_request_queue_is_finished(
|
|
101
102
|
apify_named_rq: RequestQueue,
|
|
102
103
|
) -> None:
|
|
103
|
-
|
|
104
|
-
await
|
|
105
|
-
assert not await request_queue.is_finished()
|
|
104
|
+
await apify_named_rq.add_request(Request.from_url('http://example.com'))
|
|
105
|
+
assert not await apify_named_rq.is_finished()
|
|
106
106
|
|
|
107
|
-
request = await
|
|
107
|
+
request = await apify_named_rq.fetch_next_request()
|
|
108
108
|
assert request is not None
|
|
109
|
-
assert not await
|
|
109
|
+
assert not await apify_named_rq.is_finished(), (
|
|
110
110
|
'RequestQueue should not be finished unless the request is marked as handled.'
|
|
111
111
|
)
|
|
112
112
|
|
|
113
|
-
await
|
|
114
|
-
assert await
|
|
113
|
+
await apify_named_rq.mark_request_as_handled(request)
|
|
114
|
+
assert await apify_named_rq.is_finished()
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
async def test_request_queue_deduplication(
|
|
@@ -318,3 +318,83 @@ async def test_request_queue_deduplication_unprocessed_requests(
|
|
|
318
318
|
Actor.log.info(stats_after)
|
|
319
319
|
|
|
320
320
|
assert (stats_after['writeCount'] - stats_before['writeCount']) == 1
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
async def test_request_queue_had_multiple_clients_platform(
|
|
324
|
+
make_actor: MakeActorFunction,
|
|
325
|
+
run_actor: RunActorFunction,
|
|
326
|
+
) -> None:
|
|
327
|
+
"""Test that `RequestQueue` clients created with different `client_key` appear as distinct clients."""
|
|
328
|
+
|
|
329
|
+
async def main() -> None:
|
|
330
|
+
from apify_client import ApifyClientAsync
|
|
331
|
+
|
|
332
|
+
async with Actor:
|
|
333
|
+
rq_1 = await Actor.open_request_queue()
|
|
334
|
+
await rq_1.fetch_next_request()
|
|
335
|
+
|
|
336
|
+
# Accessed with client created explicitly with `client_key=None` should appear as distinct client
|
|
337
|
+
api_client = ApifyClientAsync(token=Actor.configuration.token).request_queue(
|
|
338
|
+
request_queue_id=rq_1.id, client_key=None
|
|
339
|
+
)
|
|
340
|
+
await api_client.list_head()
|
|
341
|
+
|
|
342
|
+
assert (await rq_1.get_metadata()).had_multiple_clients is True
|
|
343
|
+
|
|
344
|
+
actor = await make_actor(label='rq-had-multiple-clients', main_func=main)
|
|
345
|
+
run_result = await run_actor(actor)
|
|
346
|
+
|
|
347
|
+
assert run_result.status == 'SUCCEEDED'
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
async def test_request_queue_not_had_multiple_clients_platform(
|
|
351
|
+
make_actor: MakeActorFunction,
|
|
352
|
+
run_actor: RunActorFunction,
|
|
353
|
+
) -> None:
|
|
354
|
+
"""Test that same `RequestQueue` created from Actor does not act as multiple clients."""
|
|
355
|
+
|
|
356
|
+
async def main() -> None:
|
|
357
|
+
async with Actor:
|
|
358
|
+
rq_1 = await Actor.open_request_queue()
|
|
359
|
+
# Two calls to API to create situation where unset `client_key` can cause `had_multiple_clients` to True
|
|
360
|
+
await rq_1.fetch_next_request()
|
|
361
|
+
await rq_1.fetch_next_request()
|
|
362
|
+
|
|
363
|
+
assert (await rq_1.get_metadata()).had_multiple_clients is False
|
|
364
|
+
|
|
365
|
+
actor = await make_actor(label='rq-not-had-multiple-clients', main_func=main)
|
|
366
|
+
run_result = await run_actor(actor)
|
|
367
|
+
|
|
368
|
+
assert run_result.status == 'SUCCEEDED'
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
async def test_request_queue_not_had_multiple_clients_platform_resurrection(
|
|
372
|
+
make_actor: MakeActorFunction,
|
|
373
|
+
run_actor: RunActorFunction,
|
|
374
|
+
apify_client_async: ApifyClientAsync,
|
|
375
|
+
) -> None:
|
|
376
|
+
"""Test `RequestQueue` created from Actor does not act as multiple clients even after resurrection."""
|
|
377
|
+
|
|
378
|
+
async def main() -> None:
|
|
379
|
+
async with Actor:
|
|
380
|
+
rq_1 = await Actor.open_request_queue()
|
|
381
|
+
assert (await rq_1.get_metadata()).had_multiple_clients is False, 'Not accessed yet, should be False'
|
|
382
|
+
|
|
383
|
+
await rq_1.fetch_next_request()
|
|
384
|
+
|
|
385
|
+
assert (await rq_1.get_metadata()).had_multiple_clients is False, (
|
|
386
|
+
'Accessed with the same client, should be False'
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
actor = await make_actor(label='rq-clients-resurrection', main_func=main)
|
|
390
|
+
run_result = await run_actor(actor)
|
|
391
|
+
assert run_result.status == 'SUCCEEDED'
|
|
392
|
+
|
|
393
|
+
# Resurrect the run, the RequestQueue should still use same client key and thus not have multiple clients.
|
|
394
|
+
run_client = apify_client_async.run(run_id=run_result.id)
|
|
395
|
+
# Redirect logs even from the resurrected run
|
|
396
|
+
streamed_log = await run_client.get_streamed_log(from_start=False)
|
|
397
|
+
await run_client.resurrect()
|
|
398
|
+
async with streamed_log:
|
|
399
|
+
run_result = ActorRun.model_validate(await run_client.wait_for_finish(wait_secs=600))
|
|
400
|
+
assert run_result.status == 'SUCCEEDED'
|