apify 3.0.4b1__tar.gz → 3.0.4b3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of apify might be problematic. Click here for more details.

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