apify 2.7.1b8__tar.gz → 2.7.1b10__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.

Files changed (229) hide show
  1. {apify-2.7.1b8 → apify-2.7.1b10}/.github/workflows/build_and_deploy_docs.yaml +1 -1
  2. {apify-2.7.1b8 → apify-2.7.1b10}/.github/workflows/release.yaml +1 -8
  3. {apify-2.7.1b8 → apify-2.7.1b10}/CHANGELOG.md +1 -95
  4. {apify-2.7.1b8 → apify-2.7.1b10}/PKG-INFO +1 -1
  5. {apify-2.7.1b8 → apify-2.7.1b10}/pyproject.toml +3 -3
  6. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/storage_clients/_apify/_request_queue_client.py +70 -72
  7. apify-2.7.1b10/tests/integration/_utils.py +17 -0
  8. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/conftest.py +15 -1
  9. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/test_actor_request_queue.py +87 -7
  10. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/test_request_queue.py +86 -0
  11. {apify-2.7.1b8 → apify-2.7.1b10}/uv.lock +9 -9
  12. {apify-2.7.1b8 → apify-2.7.1b10}/website/package-lock.json +4 -4
  13. {apify-2.7.1b8 → apify-2.7.1b10}/website/package.json +1 -1
  14. apify-2.7.1b8/tests/integration/_utils.py +0 -9
  15. {apify-2.7.1b8 → apify-2.7.1b10}/.editorconfig +0 -0
  16. {apify-2.7.1b8 → apify-2.7.1b10}/.github/CODEOWNERS +0 -0
  17. {apify-2.7.1b8 → apify-2.7.1b10}/.github/workflows/check_pr_title.yaml +0 -0
  18. {apify-2.7.1b8 → apify-2.7.1b10}/.github/workflows/pre_release.yaml +0 -0
  19. {apify-2.7.1b8 → apify-2.7.1b10}/.github/workflows/run_code_checks.yaml +0 -0
  20. {apify-2.7.1b8 → apify-2.7.1b10}/.github/workflows/update_new_issue.yaml +0 -0
  21. {apify-2.7.1b8 → apify-2.7.1b10}/.gitignore +0 -0
  22. {apify-2.7.1b8 → apify-2.7.1b10}/.markdownlint.yaml +0 -0
  23. {apify-2.7.1b8 → apify-2.7.1b10}/.pre-commit-config.yaml +0 -0
  24. {apify-2.7.1b8 → apify-2.7.1b10}/CONTRIBUTING.md +0 -0
  25. {apify-2.7.1b8 → apify-2.7.1b10}/LICENSE +0 -0
  26. {apify-2.7.1b8 → apify-2.7.1b10}/Makefile +0 -0
  27. {apify-2.7.1b8 → apify-2.7.1b10}/README.md +0 -0
  28. {apify-2.7.1b8 → apify-2.7.1b10}/docs/01_overview/01_introduction.mdx +0 -0
  29. {apify-2.7.1b8 → apify-2.7.1b10}/docs/01_overview/02_running_actors_locally.mdx +0 -0
  30. {apify-2.7.1b8 → apify-2.7.1b10}/docs/01_overview/03_actor_structure.mdx +0 -0
  31. {apify-2.7.1b8 → apify-2.7.1b10}/docs/01_overview/code/01_introduction.py +0 -0
  32. {apify-2.7.1b8 → apify-2.7.1b10}/docs/01_overview/code/actor_structure/__init__.py +0 -0
  33. {apify-2.7.1b8 → apify-2.7.1b10}/docs/01_overview/code/actor_structure/__main__.py +0 -0
  34. {apify-2.7.1b8 → apify-2.7.1b10}/docs/01_overview/code/actor_structure/main.py +0 -0
  35. {apify-2.7.1b8 → apify-2.7.1b10}/docs/01_overview/code/actor_structure/py.typed +0 -0
  36. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/01_beautifulsoup_httpx.mdx +0 -0
  37. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/02_crawlee.mdx +0 -0
  38. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/03_playwright.mdx +0 -0
  39. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/04_selenium.mdx +0 -0
  40. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/05_scrapy.mdx +0 -0
  41. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/code/01_beautifulsoup_httpx.py +0 -0
  42. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/code/02_crawlee_beautifulsoup.py +0 -0
  43. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/code/02_crawlee_playwright.py +0 -0
  44. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/code/03_playwright.py +0 -0
  45. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/code/04_selenium.py +0 -0
  46. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/code/scrapy_project/src/__init__.py +0 -0
  47. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/code/scrapy_project/src/__main__.py +0 -0
  48. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/code/scrapy_project/src/items.py +0 -0
  49. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/code/scrapy_project/src/main.py +0 -0
  50. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/code/scrapy_project/src/py.typed +0 -0
  51. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/code/scrapy_project/src/settings.py +0 -0
  52. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/code/scrapy_project/src/spiders/__init__.py +0 -0
  53. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/code/scrapy_project/src/spiders/py.typed +0 -0
  54. {apify-2.7.1b8 → apify-2.7.1b10}/docs/02_guides/code/scrapy_project/src/spiders/title.py +0 -0
  55. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/01_actor_lifecycle.mdx +0 -0
  56. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/02_actor_input.mdx +0 -0
  57. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/03_storages.mdx +0 -0
  58. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/04_actor_events.mdx +0 -0
  59. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/05_proxy_management.mdx +0 -0
  60. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/06_interacting_with_other_actors.mdx +0 -0
  61. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/07_webhooks.mdx +0 -0
  62. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/08_access_apify_api.mdx +0 -0
  63. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/09_running_webserver.mdx +0 -0
  64. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/10_logging.mdx +0 -0
  65. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/11_configuration.mdx +0 -0
  66. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/12_pay_per_event.mdx +0 -0
  67. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/01_context_manager.py +0 -0
  68. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/01_init_exit.py +0 -0
  69. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/01_reboot.py +0 -0
  70. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/01_status_message.py +0 -0
  71. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/02_input.py +0 -0
  72. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/03_dataset_exports.py +0 -0
  73. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/03_dataset_read_write.py +0 -0
  74. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/03_deleting_storages.py +0 -0
  75. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/03_kvs_iterating.py +0 -0
  76. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/03_kvs_public_url.py +0 -0
  77. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/03_kvs_read_write.py +0 -0
  78. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/03_opening_storages.py +0 -0
  79. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/03_rq.py +0 -0
  80. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/04_actor_events.py +0 -0
  81. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/05_apify_proxy.py +0 -0
  82. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/05_apify_proxy_config.py +0 -0
  83. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/05_custom_proxy.py +0 -0
  84. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/05_custom_proxy_function.py +0 -0
  85. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/05_proxy_actor_input.py +0 -0
  86. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/05_proxy_httpx.py +0 -0
  87. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/05_proxy_rotation.py +0 -0
  88. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/06_interacting_call.py +0 -0
  89. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/06_interacting_call_task.py +0 -0
  90. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/06_interacting_metamorph.py +0 -0
  91. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/06_interacting_start.py +0 -0
  92. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/07_webhook.py +0 -0
  93. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/07_webhook_preventing.py +0 -0
  94. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/08_actor_client.py +0 -0
  95. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/08_actor_new_client.py +0 -0
  96. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/09_webserver.py +0 -0
  97. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/10_log_config.py +0 -0
  98. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/10_logger_usage.py +0 -0
  99. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/10_redirect_log.py +0 -0
  100. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/10_redirect_log_existing_run.py +0 -0
  101. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/11_config.py +0 -0
  102. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/actor_charge.py +0 -0
  103. {apify-2.7.1b8 → apify-2.7.1b10}/docs/03_concepts/code/conditional_actor_charge.py +0 -0
  104. {apify-2.7.1b8 → apify-2.7.1b10}/docs/04_upgrading/upgrading_to_v2.md +0 -0
  105. {apify-2.7.1b8 → apify-2.7.1b10}/docs/04_upgrading/upgrading_to_v3.md +0 -0
  106. {apify-2.7.1b8 → apify-2.7.1b10}/docs/pyproject.toml +0 -0
  107. {apify-2.7.1b8 → apify-2.7.1b10}/renovate.json +0 -0
  108. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/__init__.py +0 -0
  109. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/_actor.py +0 -0
  110. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/_charging.py +0 -0
  111. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/_configuration.py +0 -0
  112. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/_consts.py +0 -0
  113. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/_crypto.py +0 -0
  114. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/_models.py +0 -0
  115. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/_proxy_configuration.py +0 -0
  116. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/_utils.py +0 -0
  117. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/events/__init__.py +0 -0
  118. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/events/_apify_event_manager.py +0 -0
  119. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/events/_types.py +0 -0
  120. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/events/py.typed +0 -0
  121. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/log.py +0 -0
  122. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/py.typed +0 -0
  123. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/request_loaders/__init__.py +0 -0
  124. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/request_loaders/_apify_request_list.py +0 -0
  125. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/request_loaders/py.typed +0 -0
  126. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/__init__.py +0 -0
  127. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/_actor_runner.py +0 -0
  128. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/_async_thread.py +0 -0
  129. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/_logging_config.py +0 -0
  130. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/extensions/__init__.py +0 -0
  131. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/extensions/_httpcache.py +0 -0
  132. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/middlewares/__init__.py +0 -0
  133. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/middlewares/apify_proxy.py +0 -0
  134. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/middlewares/py.typed +0 -0
  135. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/pipelines/__init__.py +0 -0
  136. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/pipelines/actor_dataset_push.py +0 -0
  137. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/pipelines/py.typed +0 -0
  138. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/py.typed +0 -0
  139. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/requests.py +0 -0
  140. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/scheduler.py +0 -0
  141. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/scrapy/utils.py +0 -0
  142. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/storage_clients/__init__.py +0 -0
  143. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/storage_clients/_apify/__init__.py +0 -0
  144. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/storage_clients/_apify/_dataset_client.py +0 -0
  145. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/storage_clients/_apify/_key_value_store_client.py +0 -0
  146. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/storage_clients/_apify/_models.py +0 -0
  147. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/storage_clients/_apify/_storage_client.py +0 -0
  148. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/storage_clients/_apify/py.typed +0 -0
  149. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/storage_clients/_file_system/__init__.py +0 -0
  150. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/storage_clients/_file_system/_key_value_store_client.py +0 -0
  151. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/storage_clients/_file_system/_storage_client.py +0 -0
  152. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/storage_clients/py.typed +0 -0
  153. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/storages/__init__.py +0 -0
  154. {apify-2.7.1b8 → apify-2.7.1b10}/src/apify/storages/py.typed +0 -0
  155. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/README.md +0 -0
  156. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/__init__.py +0 -0
  157. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/actor_source_base/Dockerfile +0 -0
  158. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/actor_source_base/requirements.txt +0 -0
  159. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/actor_source_base/server.py +0 -0
  160. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/actor_source_base/src/__init__.py +0 -0
  161. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/actor_source_base/src/__main__.py +0 -0
  162. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/actor_source_base/src/main.py +0 -0
  163. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/test_actor_api_helpers.py +0 -0
  164. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/test_actor_call_timeouts.py +0 -0
  165. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/test_actor_charge.py +0 -0
  166. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/test_actor_create_proxy_configuration.py +0 -0
  167. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/test_actor_dataset.py +0 -0
  168. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/test_actor_events.py +0 -0
  169. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/test_actor_key_value_store.py +0 -0
  170. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/test_actor_lifecycle.py +0 -0
  171. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/test_actor_log.py +0 -0
  172. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/test_actor_scrapy.py +0 -0
  173. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/test_crawlers_with_storages.py +0 -0
  174. {apify-2.7.1b8 → apify-2.7.1b10}/tests/integration/test_fixtures.py +0 -0
  175. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/__init__.py +0 -0
  176. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/actor/__init__.py +0 -0
  177. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/actor/test_actor_create_proxy_configuration.py +0 -0
  178. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/actor/test_actor_dataset.py +0 -0
  179. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/actor/test_actor_env_helpers.py +0 -0
  180. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/actor/test_actor_helpers.py +0 -0
  181. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/actor/test_actor_key_value_store.py +0 -0
  182. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/actor/test_actor_lifecycle.py +0 -0
  183. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/actor/test_actor_log.py +0 -0
  184. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/actor/test_actor_non_default_instance.py +0 -0
  185. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/actor/test_actor_request_queue.py +0 -0
  186. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/actor/test_configuration.py +0 -0
  187. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/actor/test_request_list.py +0 -0
  188. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/conftest.py +0 -0
  189. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/events/__init__.py +0 -0
  190. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/events/test_apify_event_manager.py +0 -0
  191. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/scrapy/__init__.py +0 -0
  192. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/scrapy/extensions/__init__.py +0 -0
  193. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/scrapy/extensions/test_httpcache.py +0 -0
  194. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/scrapy/middlewares/__init__.py +0 -0
  195. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/scrapy/middlewares/test_apify_proxy.py +0 -0
  196. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/scrapy/pipelines/__init__.py +0 -0
  197. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/scrapy/pipelines/test_actor_dataset_push.py +0 -0
  198. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/scrapy/requests/__init__.py +0 -0
  199. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/scrapy/requests/test_to_apify_request.py +0 -0
  200. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/scrapy/requests/test_to_scrapy_request.py +0 -0
  201. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/scrapy/utils/__init__.py +0 -0
  202. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/scrapy/utils/test_apply_apify_settings.py +0 -0
  203. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/scrapy/utils/test_get_basic_auth_header.py +0 -0
  204. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/storage_clients/__init__.py +0 -0
  205. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/storage_clients/test_apify_request_queue_client.py +0 -0
  206. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/storage_clients/test_file_system.py +0 -0
  207. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/test_crypto.py +0 -0
  208. {apify-2.7.1b8 → apify-2.7.1b10}/tests/unit/test_proxy_configuration.py +0 -0
  209. {apify-2.7.1b8 → apify-2.7.1b10}/website/.eslintrc.json +0 -0
  210. {apify-2.7.1b8 → apify-2.7.1b10}/website/babel.config.js +0 -0
  211. {apify-2.7.1b8 → apify-2.7.1b10}/website/build_api_reference.sh +0 -0
  212. {apify-2.7.1b8 → apify-2.7.1b10}/website/docusaurus.config.js +0 -0
  213. {apify-2.7.1b8 → apify-2.7.1b10}/website/generate_module_shortcuts.py +0 -0
  214. {apify-2.7.1b8 → apify-2.7.1b10}/website/sidebars.js +0 -0
  215. {apify-2.7.1b8 → apify-2.7.1b10}/website/src/components/ApiLink.jsx +0 -0
  216. {apify-2.7.1b8 → apify-2.7.1b10}/website/src/components/Gradients.jsx +0 -0
  217. {apify-2.7.1b8 → apify-2.7.1b10}/website/src/components/Highlights.jsx +0 -0
  218. {apify-2.7.1b8 → apify-2.7.1b10}/website/src/components/Highlights.module.css +0 -0
  219. {apify-2.7.1b8 → apify-2.7.1b10}/website/src/components/RunnableCodeBlock.jsx +0 -0
  220. {apify-2.7.1b8 → apify-2.7.1b10}/website/src/components/RunnableCodeBlock.module.css +0 -0
  221. {apify-2.7.1b8 → apify-2.7.1b10}/website/src/css/custom.css +0 -0
  222. {apify-2.7.1b8 → apify-2.7.1b10}/website/src/pages/home_page_example.py +0 -0
  223. {apify-2.7.1b8 → apify-2.7.1b10}/website/src/pages/index.js +0 -0
  224. {apify-2.7.1b8 → apify-2.7.1b10}/website/src/pages/index.module.css +0 -0
  225. {apify-2.7.1b8 → apify-2.7.1b10}/website/static/.nojekyll +0 -0
  226. {apify-2.7.1b8 → apify-2.7.1b10}/website/static/img/docs-og.png +0 -0
  227. {apify-2.7.1b8 → apify-2.7.1b10}/website/static/img/guides/redirected_logs_example.webp +0 -0
  228. {apify-2.7.1b8 → apify-2.7.1b10}/website/tools/docs-prettier.config.js +0 -0
  229. {apify-2.7.1b8 → apify-2.7.1b10}/website/tools/utils/externalLink.js +0 -0
@@ -72,7 +72,7 @@ jobs:
72
72
  uses: actions/configure-pages@v5
73
73
 
74
74
  - name: Upload GitHub Pages artifact
75
- uses: actions/upload-pages-artifact@v3
75
+ uses: actions/upload-pages-artifact@v4
76
76
  with:
77
77
  path: ./website/build
78
78
 
@@ -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, integration_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
 
@@ -51,101 +52,6 @@ All notable changes to this project will be documented in this file.
51
52
  - 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
 
53
54
 
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
55
 
150
56
  ## [2.7.3](https://github.com/apify/apify-sdk-python/releases/tag/v2.7.3) (2025-08-11)
151
57
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apify
3
- Version: 2.7.1b8
3
+ Version: 2.7.1b10
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "apify"
7
- version = "2.7.1b8"
7
+ version = "2.7.1b10"
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" }
@@ -67,7 +67,7 @@ dev = [
67
67
  "build~=1.3.0",
68
68
  "crawlee[parsel]",
69
69
  "dycw-pytest-only~=2.1.0",
70
- "griffe~=1.12.0",
70
+ "griffe~=1.13.0",
71
71
  "mypy~=1.17.0",
72
72
  "pre-commit~=4.3.0",
73
73
  "pydoc-markdown~=4.8.0",
@@ -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.0.0.20250525",
82
+ "types-cachetools~=6.2.0.20250827",
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
- id: str,
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._id = id
81
- """The ID of the request queue."""
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
- total_count = self._initial_total_count + self._assumed_total_count
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=self._id,
124
- name=self._name,
125
- total_request_count=total_count,
126
- handled_request_count=handled_count,
127
- pending_request_count=pending_count,
128
- created_at=datetime.now(timezone.utc),
129
- modified_at=datetime.now(timezone.utc),
130
- accessed_at=datetime.now(timezone.utc),
131
- had_multiple_clients=self._had_multiple_clients,
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
- # If both id and name are provided, raise an error.
191
- if id and name:
192
- raise ValueError('Only one of "id" or "name" can be specified, not both.')
193
-
194
- # If id is provided, get the storage by ID.
195
- if id and name is None:
196
- apify_rq_client = apify_client_async.request_queue(request_queue_id=id)
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
- # If name is provided, get or create the storage by name.
199
- if name and id is None:
200
- id = RequestQueueMetadata.model_validate(
201
- await apify_rqs_client.get_or_create(name=name),
202
- ).id
203
- apify_rq_client = apify_client_async.request_queue(request_queue_id=id)
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
- # If both id and name are None, try to get the default storage ID from environment variables.
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
- id=id,
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._assumed_total_count += new_request_count
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._assumed_handled_count += 1
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._assumed_handled_count -= 1
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 keu of the request to get.
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.get_metadata()
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
- request_queue = await Actor.open_request_queue(name=apify_named_rq.name, force_cloud=True)
104
- await request_queue.add_request(Request.from_url('http://example.com'))
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 request_queue.fetch_next_request()
107
+ request = await apify_named_rq.fetch_next_request()
108
108
  assert request is not None
109
- assert not await request_queue.is_finished(), (
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 request_queue.mark_request_as_handled(request)
114
- assert await request_queue.is_finished()
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'