apify 2.4.0b3__tar.gz → 2.4.0b5__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 (204) hide show
  1. {apify-2.4.0b3 → apify-2.4.0b5}/CHANGELOG.md +4 -3
  2. {apify-2.4.0b3 → apify-2.4.0b5}/PKG-INFO +2 -2
  3. apify-2.4.0b5/docs/03_concepts/12_pay_per_event.mdx +46 -0
  4. apify-2.4.0b5/docs/03_concepts/code/actor_charge.py +30 -0
  5. apify-2.4.0b5/docs/03_concepts/code/conditional_actor_charge.py +18 -0
  6. {apify-2.4.0b3 → apify-2.4.0b5}/pyproject.toml +3 -5
  7. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/_crypto.py +38 -0
  8. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/_platform_event_manager.py +3 -3
  9. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/apify_storage_client/_key_value_store_client.py +17 -2
  10. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/test_actor_key_value_store.py +15 -6
  11. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/actor/test_actor_lifecycle.py +4 -4
  12. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/test_crypto.py +31 -1
  13. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/test_platform_event_manager.py +7 -7
  14. {apify-2.4.0b3 → apify-2.4.0b5}/uv.lock +80 -80
  15. apify-2.4.0b5/website/src/components/ApiLink.jsx +10 -0
  16. apify-2.4.0b5/website/src/components/Gradients.jsx +20 -0
  17. apify-2.4.0b5/website/src/components/Highlights.jsx +104 -0
  18. apify-2.4.0b5/website/src/components/Highlights.module.css +46 -0
  19. apify-2.4.0b5/website/src/components/RunnableCodeBlock.jsx +44 -0
  20. apify-2.4.0b5/website/src/components/RunnableCodeBlock.module.css +39 -0
  21. {apify-2.4.0b3 → apify-2.4.0b5}/.editorconfig +0 -0
  22. {apify-2.4.0b3 → apify-2.4.0b5}/.github/CODEOWNERS +0 -0
  23. {apify-2.4.0b3 → apify-2.4.0b5}/.github/workflows/build_and_deploy_docs.yaml +0 -0
  24. {apify-2.4.0b3 → apify-2.4.0b5}/.github/workflows/check_pr_title.yaml +0 -0
  25. {apify-2.4.0b3 → apify-2.4.0b5}/.github/workflows/pre_release.yaml +0 -0
  26. {apify-2.4.0b3 → apify-2.4.0b5}/.github/workflows/release.yaml +0 -0
  27. {apify-2.4.0b3 → apify-2.4.0b5}/.github/workflows/run_code_checks.yaml +0 -0
  28. {apify-2.4.0b3 → apify-2.4.0b5}/.github/workflows/update_new_issue.yaml +0 -0
  29. {apify-2.4.0b3 → apify-2.4.0b5}/.gitignore +0 -0
  30. {apify-2.4.0b3 → apify-2.4.0b5}/.markdownlint.yaml +0 -0
  31. {apify-2.4.0b3 → apify-2.4.0b5}/.pre-commit-config.yaml +0 -0
  32. {apify-2.4.0b3 → apify-2.4.0b5}/CONTRIBUTING.md +0 -0
  33. {apify-2.4.0b3 → apify-2.4.0b5}/LICENSE +0 -0
  34. {apify-2.4.0b3 → apify-2.4.0b5}/Makefile +0 -0
  35. {apify-2.4.0b3 → apify-2.4.0b5}/README.md +0 -0
  36. {apify-2.4.0b3 → apify-2.4.0b5}/docs/01_overview/01_introduction.mdx +0 -0
  37. {apify-2.4.0b3 → apify-2.4.0b5}/docs/01_overview/02_running_actors_locally.mdx +0 -0
  38. {apify-2.4.0b3 → apify-2.4.0b5}/docs/01_overview/03_actor_structure.mdx +0 -0
  39. {apify-2.4.0b3 → apify-2.4.0b5}/docs/01_overview/code/01_introduction.py +0 -0
  40. {apify-2.4.0b3 → apify-2.4.0b5}/docs/01_overview/code/actor_structure/__init__.py +0 -0
  41. {apify-2.4.0b3 → apify-2.4.0b5}/docs/01_overview/code/actor_structure/__main__.py +0 -0
  42. {apify-2.4.0b3 → apify-2.4.0b5}/docs/01_overview/code/actor_structure/main.py +0 -0
  43. {apify-2.4.0b3 → apify-2.4.0b5}/docs/01_overview/code/actor_structure/py.typed +0 -0
  44. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/01_beautifulsoup_httpx.mdx +0 -0
  45. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/02_crawlee.mdx +0 -0
  46. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/03_playwright.mdx +0 -0
  47. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/04_selenium.mdx +0 -0
  48. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/05_scrapy.mdx +0 -0
  49. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/code/01_beautifulsoup_httpx.py +0 -0
  50. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/code/02_crawlee_beautifulsoup.py +0 -0
  51. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/code/02_crawlee_playwright.py +0 -0
  52. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/code/03_playwright.py +0 -0
  53. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/code/04_selenium.py +0 -0
  54. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/code/scrapy_project/src/__init__.py +0 -0
  55. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/code/scrapy_project/src/__main__.py +0 -0
  56. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/code/scrapy_project/src/items.py +0 -0
  57. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/code/scrapy_project/src/main.py +0 -0
  58. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/code/scrapy_project/src/py.typed +0 -0
  59. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/code/scrapy_project/src/settings.py +0 -0
  60. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/code/scrapy_project/src/spiders/__init__.py +0 -0
  61. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/code/scrapy_project/src/spiders/py.typed +0 -0
  62. {apify-2.4.0b3 → apify-2.4.0b5}/docs/02_guides/code/scrapy_project/src/spiders/title.py +0 -0
  63. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/01_actor_lifecycle.mdx +0 -0
  64. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/02_actor_input.mdx +0 -0
  65. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/03_storages.mdx +0 -0
  66. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/04_actor_events.mdx +0 -0
  67. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/05_proxy_management.mdx +0 -0
  68. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/06_interacting_with_other_actors.mdx +0 -0
  69. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/07_webhooks.mdx +0 -0
  70. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/08_access_apify_api.mdx +0 -0
  71. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/09_running_webserver.mdx +0 -0
  72. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/10_logging.mdx +0 -0
  73. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/11_configuration.mdx +0 -0
  74. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/01_context_manager.py +0 -0
  75. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/01_init_exit.py +0 -0
  76. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/01_reboot.py +0 -0
  77. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/01_status_message.py +0 -0
  78. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/02_input.py +0 -0
  79. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/03_dataset_exports.py +0 -0
  80. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/03_dataset_read_write.py +0 -0
  81. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/03_deleting_storages.py +0 -0
  82. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/03_kvs_iterating.py +0 -0
  83. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/03_kvs_public_url.py +0 -0
  84. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/03_kvs_read_write.py +0 -0
  85. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/03_opening_storages.py +0 -0
  86. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/03_rq.py +0 -0
  87. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/04_actor_events.py +0 -0
  88. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/05_apify_proxy.py +0 -0
  89. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/05_apify_proxy_config.py +0 -0
  90. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/05_custom_proxy.py +0 -0
  91. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/05_custom_proxy_function.py +0 -0
  92. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/05_proxy_actor_input.py +0 -0
  93. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/05_proxy_httpx.py +0 -0
  94. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/05_proxy_rotation.py +0 -0
  95. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/06_interacting_call.py +0 -0
  96. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/06_interacting_call_task.py +0 -0
  97. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/06_interacting_metamorph.py +0 -0
  98. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/06_interacting_start.py +0 -0
  99. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/07_webhook.py +0 -0
  100. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/07_webhook_preventing.py +0 -0
  101. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/08_actor_client.py +0 -0
  102. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/08_actor_new_client.py +0 -0
  103. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/09_webserver.py +0 -0
  104. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/10_log_config.py +0 -0
  105. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/10_logger_usage.py +0 -0
  106. {apify-2.4.0b3 → apify-2.4.0b5}/docs/03_concepts/code/11_config.py +0 -0
  107. {apify-2.4.0b3 → apify-2.4.0b5}/docs/04_upgrading/upgrading_to_v2.md +0 -0
  108. {apify-2.4.0b3 → apify-2.4.0b5}/docs/pyproject.toml +0 -0
  109. {apify-2.4.0b3 → apify-2.4.0b5}/renovate.json +0 -0
  110. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/__init__.py +0 -0
  111. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/_actor.py +0 -0
  112. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/_charging.py +0 -0
  113. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/_configuration.py +0 -0
  114. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/_consts.py +0 -0
  115. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/_models.py +0 -0
  116. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/_proxy_configuration.py +0 -0
  117. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/_utils.py +0 -0
  118. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/apify_storage_client/__init__.py +0 -0
  119. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/apify_storage_client/_apify_storage_client.py +0 -0
  120. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/apify_storage_client/_dataset_client.py +0 -0
  121. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/apify_storage_client/_dataset_collection_client.py +0 -0
  122. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/apify_storage_client/_key_value_store_collection_client.py +0 -0
  123. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/apify_storage_client/_request_queue_client.py +0 -0
  124. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/apify_storage_client/_request_queue_collection_client.py +0 -0
  125. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/apify_storage_client/py.typed +0 -0
  126. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/log.py +0 -0
  127. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/py.typed +0 -0
  128. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/scrapy/__init__.py +0 -0
  129. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/scrapy/_actor_runner.py +0 -0
  130. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/scrapy/_async_thread.py +0 -0
  131. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/scrapy/_logging_config.py +0 -0
  132. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/scrapy/middlewares/__init__.py +0 -0
  133. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/scrapy/middlewares/apify_proxy.py +0 -0
  134. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/scrapy/middlewares/py.typed +0 -0
  135. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/scrapy/pipelines/__init__.py +0 -0
  136. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/scrapy/pipelines/actor_dataset_push.py +0 -0
  137. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/scrapy/pipelines/py.typed +0 -0
  138. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/scrapy/py.typed +0 -0
  139. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/scrapy/requests.py +0 -0
  140. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/scrapy/scheduler.py +0 -0
  141. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/scrapy/utils.py +0 -0
  142. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/storages/__init__.py +0 -0
  143. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/storages/_request_list.py +0 -0
  144. {apify-2.4.0b3 → apify-2.4.0b5}/src/apify/storages/py.typed +0 -0
  145. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/README.md +0 -0
  146. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/__init__.py +0 -0
  147. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/_utils.py +0 -0
  148. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/actor_source_base/Dockerfile +0 -0
  149. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/actor_source_base/requirements.txt +0 -0
  150. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/actor_source_base/src/__init__.py +0 -0
  151. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/actor_source_base/src/__main__.py +0 -0
  152. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/actor_source_base/src/main.py +0 -0
  153. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/conftest.py +0 -0
  154. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/test_actor_api_helpers.py +0 -0
  155. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/test_actor_charge.py +0 -0
  156. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/test_actor_create_proxy_configuration.py +0 -0
  157. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/test_actor_dataset.py +0 -0
  158. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/test_actor_events.py +0 -0
  159. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/test_actor_lifecycle.py +0 -0
  160. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/test_actor_log.py +0 -0
  161. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/test_actor_request_queue.py +0 -0
  162. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/test_actor_scrapy.py +0 -0
  163. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/test_fixtures.py +0 -0
  164. {apify-2.4.0b3 → apify-2.4.0b5}/tests/integration/test_request_queue.py +0 -0
  165. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/__init__.py +0 -0
  166. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/actor/__init__.py +0 -0
  167. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/actor/test_actor_create_proxy_configuration.py +0 -0
  168. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/actor/test_actor_dataset.py +0 -0
  169. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/actor/test_actor_env_helpers.py +0 -0
  170. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/actor/test_actor_helpers.py +0 -0
  171. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/actor/test_actor_key_value_store.py +0 -0
  172. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/actor/test_actor_log.py +0 -0
  173. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/actor/test_actor_non_default_instance.py +0 -0
  174. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/actor/test_actor_request_queue.py +0 -0
  175. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/actor/test_request_list.py +0 -0
  176. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/conftest.py +0 -0
  177. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/scrapy/__init__.py +0 -0
  178. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/scrapy/middlewares/__init__.py +0 -0
  179. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/scrapy/middlewares/test_apify_proxy.py +0 -0
  180. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/scrapy/pipelines/__init__.py +0 -0
  181. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/scrapy/pipelines/test_actor_dataset_push.py +0 -0
  182. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/scrapy/requests/__init__.py +0 -0
  183. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/scrapy/requests/test_to_apify_request.py +0 -0
  184. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/scrapy/requests/test_to_scrapy_request.py +0 -0
  185. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/scrapy/utils/__init__.py +0 -0
  186. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/scrapy/utils/test_apply_apify_settings.py +0 -0
  187. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/scrapy/utils/test_get_basic_auth_header.py +0 -0
  188. {apify-2.4.0b3 → apify-2.4.0b5}/tests/unit/test_proxy_configuration.py +0 -0
  189. {apify-2.4.0b3 → apify-2.4.0b5}/website/.eslintrc.json +0 -0
  190. {apify-2.4.0b3 → apify-2.4.0b5}/website/babel.config.js +0 -0
  191. {apify-2.4.0b3 → apify-2.4.0b5}/website/build_api_reference.sh +0 -0
  192. {apify-2.4.0b3 → apify-2.4.0b5}/website/docusaurus.config.js +0 -0
  193. {apify-2.4.0b3 → apify-2.4.0b5}/website/generate_module_shortcuts.py +0 -0
  194. {apify-2.4.0b3 → apify-2.4.0b5}/website/package-lock.json +0 -0
  195. {apify-2.4.0b3 → apify-2.4.0b5}/website/package.json +0 -0
  196. {apify-2.4.0b3 → apify-2.4.0b5}/website/sidebars.js +0 -0
  197. {apify-2.4.0b3 → apify-2.4.0b5}/website/src/css/custom.css +0 -0
  198. {apify-2.4.0b3 → apify-2.4.0b5}/website/src/pages/home_page_example.py +0 -0
  199. {apify-2.4.0b3 → apify-2.4.0b5}/website/src/pages/index.js +0 -0
  200. {apify-2.4.0b3 → apify-2.4.0b5}/website/src/pages/index.module.css +0 -0
  201. {apify-2.4.0b3 → apify-2.4.0b5}/website/static/.nojekyll +0 -0
  202. {apify-2.4.0b3 → apify-2.4.0b5}/website/static/img/docs-og.png +0 -0
  203. {apify-2.4.0b3 → apify-2.4.0b5}/website/tools/docs-prettier.config.js +0 -0
  204. {apify-2.4.0b3 → apify-2.4.0b5}/website/tools/utils/externalLink.js +0 -0
@@ -2,15 +2,16 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- <!-- git-cliff-unreleased-start -->
6
- ## 2.4.0 - **not yet released**
5
+ ## [2.4.0](https://github.com/apify/apify-sdk-python/releases/tag/v2.4.0) (2025-03-07)
7
6
 
8
7
  ### 🚀 Features
9
8
 
10
9
  - Update to Crawlee v0.6 ([#420](https://github.com/apify/apify-sdk-python/pull/420)) ([9be4336](https://github.com/apify/apify-sdk-python/commit/9be433667231cc5739861fa693d7a726860d6aca)) by [@vdusek](https://github.com/vdusek)
10
+ - Add Actor `exit_process` option ([#424](https://github.com/apify/apify-sdk-python/pull/424)) ([994c832](https://github.com/apify/apify-sdk-python/commit/994c8323b994e009db0ccdcb624891a2fef97070)) by [@vdusek](https://github.com/vdusek), closes [#396](https://github.com/apify/apify-sdk-python/issues/396), [#401](https://github.com/apify/apify-sdk-python/issues/401)
11
+ - Upgrade websockets to v14 to adapt to library API changes ([#425](https://github.com/apify/apify-sdk-python/pull/425)) ([5f49275](https://github.com/apify/apify-sdk-python/commit/5f49275ca1177e5ba56856ffe3860f6b97bee9ee)) by [@Mantisus](https://github.com/Mantisus), closes [#325](https://github.com/apify/apify-sdk-python/issues/325)
12
+ - Add signing of public URL ([#407](https://github.com/apify/apify-sdk-python/pull/407)) ([a865461](https://github.com/apify/apify-sdk-python/commit/a865461c703aea01d91317f4fdf38c1bedd35f00)) by [@danpoletaev](https://github.com/danpoletaev)
11
13
 
12
14
 
13
- <!-- git-cliff-unreleased-end -->
14
15
  ## [2.3.1](https://github.com/apify/apify-sdk-python/releases/tag/v2.3.1) (2025-02-25)
15
16
 
16
17
  ### 🐛 Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apify
3
- Version: 2.4.0b3
3
+ Version: 2.4.0b5
4
4
  Summary: Apify SDK for Python
5
5
  Project-URL: Homepage, https://docs.apify.com/sdk/python/
6
6
  Project-URL: Apify homepage, https://apify.com
@@ -231,7 +231,7 @@ Requires-Dist: httpx>=0.27.0
231
231
  Requires-Dist: lazy-object-proxy>=1.10.0
232
232
  Requires-Dist: more-itertools>=10.2.0
233
233
  Requires-Dist: typing-extensions>=4.1.0
234
- Requires-Dist: websockets<14.0.0,>=10.0
234
+ Requires-Dist: websockets>=14.0
235
235
  Provides-Extra: scrapy
236
236
  Requires-Dist: scrapy>=2.11.0; extra == 'scrapy'
237
237
  Description-Content-Type: text/markdown
@@ -0,0 +1,46 @@
1
+ ---
2
+ id: pay-per-event
3
+ title: Pay-per-event monetization
4
+ description: Monetize your Actors using the pay-per-event pricing model
5
+ ---
6
+
7
+ import ActorChargeSource from '!!raw-loader!./code/actor_charge.py';
8
+ import ConditionalActorChargeSource from '!!raw-loader!./code/conditional_actor_charge.py';
9
+ import ApiLink from '@site/src/components/ApiLink';
10
+ import CodeBlock from '@theme/CodeBlock';
11
+
12
+ Apify provides several [pricing models](https://docs.apify.com/platform/actors/publishing/monetize) for monetizing your Actors. The most recent and most flexible one is [pay-per-event](https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event), which lets you charge your users programmatically directly from your Actor. As the name suggests, you may charge the users each time a specific event occurs, for example a call to an external API or when you return a result.
13
+
14
+ To use the pay-per-event pricing model, you first need to [set it up](https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event) for your Actor in the Apify console. After that, you're free to start charging for events.
15
+
16
+ ## Charging for events
17
+
18
+ After monetization is set in the Apify console, you can add <ApiLink to="class/Actor#charge">`Actor.charge`</ApiLink> calls to your code and start monetizing!
19
+
20
+ <CodeBlock language="python">
21
+ {ActorChargeSource}
22
+ </CodeBlock>
23
+
24
+ Then you just push your code to Apify and that's it! The SDK will even keep track of the max total charge setting for you, so you will not provide more value than what the user chose to pay for.
25
+
26
+ If you need finer control over charging, you can access call <ApiLink to="class/Actor#get_charging_manager">`Actor.get_charging_manager()`</ApiLink> to access the <ApiLink to="class/ChargingManager">`ChargingManager`</ApiLink>, which can provide more detailed information - for example how many events of each type can be charged before reaching the configured limit.
27
+
28
+ ## Transitioning from a different pricing model
29
+
30
+ When you plan to start using the pay-per-event pricing model for an Actor that is already monetized with a different pricing model, your source code will need support both pricing models during the transition period enforced by the Apify platform. Arguably the most frequent case is the transition from the pay-per-result model which utilizes the `ACTOR_MAX_PAID_DATASET_ITEMS` environment variable to prevent returning unpaid dataset items. The following is an example how to handle such scenarios. The key part is the <ApiLink to="class/ChargingManager#get_pricing_info">`ChargingManager.get_pricing_info()`</ApiLink> method which returns information about the current pricing model.
31
+
32
+ <CodeBlock language="python">
33
+ {ConditionalActorChargeSource}
34
+ </CodeBlock>
35
+
36
+ ## Local development
37
+
38
+ It is encouraged to test your monetization code on your machine before releasing it to the public. To tell your Actor that it should work in pay-per-event mode, pass it the `ACTOR_TEST_PAY_PER_EVENT` environment variable:
39
+
40
+ ```shell
41
+ ACTOR_TEST_PAY_PER_EVENT=true python -m youractor
42
+ ```
43
+
44
+ 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.
45
+
46
+ Because pricing configuration is stored by the Apify platform, all events will have a default price of $1.
@@ -0,0 +1,30 @@
1
+ from apify import Actor
2
+
3
+
4
+ async def main() -> None:
5
+ async with Actor:
6
+ # highlight-start
7
+ # Charge for a single occurence of an event
8
+ await Actor.charge(event_name='init')
9
+ # highlight-end
10
+
11
+ # Prepare some mock results
12
+ result = [
13
+ {'word': 'Lorem'},
14
+ {'word': 'Ipsum'},
15
+ {'word': 'Dolor'},
16
+ {'word': 'Sit'},
17
+ {'word': 'Amet'},
18
+ ]
19
+ # highlight-start
20
+ # Shortcut for charging for each pushed dataset item
21
+ await Actor.push_data(result, 'result-item')
22
+ # highlight-end
23
+
24
+ # highlight-start
25
+ # Or you can charge for a given number of events manually
26
+ await Actor.charge(
27
+ event_name='result-item',
28
+ count=len(result),
29
+ )
30
+ # highlight-end
@@ -0,0 +1,18 @@
1
+ from apify import Actor
2
+
3
+
4
+ async def main() -> None:
5
+ async with Actor:
6
+ # Check the dataset because there might already be items
7
+ # if the run migrated or was restarted
8
+ default_dataset = await Actor.open_dataset()
9
+ dataset_info = await default_dataset.get_info()
10
+ charged_items = dataset_info.item_count if dataset_info else 0
11
+
12
+ # highlight-start
13
+ if Actor.get_charging_manager().get_pricing_info().is_pay_per_event:
14
+ # highlight-end
15
+ await Actor.push_data({'hello': 'world'}, 'dataset-item')
16
+ elif charged_items < (Actor.config.max_paid_dataset_items or 0):
17
+ await Actor.push_data({'hello': 'world'})
18
+ charged_items += 1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "apify"
7
- version = "2.4.0b3"
7
+ version = "2.4.0b5"
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" }
@@ -42,9 +42,7 @@ dependencies = [
42
42
  "lazy-object-proxy>=1.10.0",
43
43
  "more_itertools>=10.2.0",
44
44
  "typing-extensions>=4.1.0",
45
- # TODO: Relax the upper bound once the issue is resolved:
46
- # https://github.com/apify/apify-sdk-python/issues/325
47
- "websockets>=10.0,<14.0.0",
45
+ "websockets>=14.0",
48
46
  ]
49
47
 
50
48
  [project.optional-dependencies]
@@ -74,7 +72,7 @@ dev = [
74
72
  "pytest-xdist~=3.6.0",
75
73
  "respx~=0.22.0",
76
74
  "ruff~=0.9.0",
77
- "setuptools~=75.8.0", # setuptools are used by pytest but not explicitly required
75
+ "setuptools~=76.0.0", # setuptools are used by pytest but not explicitly required
78
76
  ]
79
77
 
80
78
  [tool.hatch.build.targets.wheel]
@@ -1,6 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import base64
4
+ import hashlib
5
+ import hmac
6
+ import string
4
7
  from typing import Any
5
8
 
6
9
  from cryptography.exceptions import InvalidTag as InvalidTagException
@@ -153,3 +156,38 @@ def decrypt_input_secrets(private_key: rsa.RSAPrivateKey, input_data: Any) -> An
153
156
  )
154
157
 
155
158
  return input_data
159
+
160
+
161
+ CHARSET = string.digits + string.ascii_letters
162
+
163
+
164
+ def encode_base62(num: int) -> str:
165
+ """Encode the given number to base62."""
166
+ if num == 0:
167
+ return CHARSET[0]
168
+
169
+ res = ''
170
+ while num > 0:
171
+ num, remainder = divmod(num, 62)
172
+ res = CHARSET[remainder] + res
173
+ return res
174
+
175
+
176
+ @ignore_docs
177
+ def create_hmac_signature(secret_key: str, message: str) -> str:
178
+ """Generate an HMAC signature and encodes it using Base62. Base62 encoding reduces the signature length.
179
+
180
+ HMAC signature is truncated to 30 characters to make it shorter.
181
+
182
+ Args:
183
+ secret_key: Secret key used for signing signatures.
184
+ message: Message to be signed.
185
+
186
+ Returns:
187
+ Base62 encoded signature.
188
+ """
189
+ signature = hmac.new(secret_key.encode('utf-8'), message.encode('utf-8'), hashlib.sha256).hexdigest()[:30]
190
+
191
+ decimal_signature = int(signature, 16)
192
+
193
+ return encode_base62(decimal_signature)
@@ -4,7 +4,7 @@ import asyncio
4
4
  from datetime import datetime
5
5
  from typing import TYPE_CHECKING, Annotated, Any, Literal, Union
6
6
 
7
- import websockets.client
7
+ import websockets.asyncio.client
8
8
  from pydantic import BaseModel, Discriminator, Field, TypeAdapter
9
9
  from typing_extensions import Self, Unpack, override
10
10
 
@@ -143,7 +143,7 @@ class PlatformEventManager(EventManager):
143
143
  but instead use it via the `Actor.on()` and `Actor.off()` methods.
144
144
  """
145
145
 
146
- _platform_events_websocket: websockets.client.WebSocketClientProtocol | None = None
146
+ _platform_events_websocket: websockets.asyncio.client.ClientConnection | None = None
147
147
  _process_platform_messages_task: asyncio.Task | None = None
148
148
  _send_system_info_interval_task: asyncio.Task | None = None
149
149
  _connected_to_platform_websocket: asyncio.Future = asyncio.Future()
@@ -196,7 +196,7 @@ class PlatformEventManager(EventManager):
196
196
 
197
197
  async def _process_platform_messages(self, ws_url: str) -> None:
198
198
  try:
199
- async with websockets.client.connect(ws_url) as websocket:
199
+ async with websockets.asyncio.client.connect(ws_url) as websocket:
200
200
  self._platform_events_websocket = websocket
201
201
  self._connected_to_platform_websocket.set_result(True)
202
202
 
@@ -4,10 +4,13 @@ from contextlib import asynccontextmanager
4
4
  from typing import TYPE_CHECKING, Any
5
5
 
6
6
  from typing_extensions import override
7
+ from yarl import URL
7
8
 
8
9
  from crawlee.storage_clients._base import KeyValueStoreClient as BaseKeyValueStoreClient
9
10
  from crawlee.storage_clients.models import KeyValueStoreListKeysPage, KeyValueStoreMetadata, KeyValueStoreRecord
10
11
 
12
+ from apify._crypto import create_hmac_signature
13
+
11
14
  if TYPE_CHECKING:
12
15
  from collections.abc import AsyncIterator
13
16
  from contextlib import AbstractAsyncContextManager
@@ -89,6 +92,18 @@ class KeyValueStoreClient(BaseKeyValueStoreClient):
89
92
  Args:
90
93
  key: The key for which the URL should be generated.
91
94
  """
92
- public_api_url = self._api_public_base_url
95
+ if self._client.resource_id is None:
96
+ raise ValueError('resource_id cannot be None when generating a public URL')
97
+
98
+ public_url = (
99
+ URL(self._api_public_base_url) / 'v2' / 'key-value-stores' / self._client.resource_id / 'records' / key
100
+ )
101
+
102
+ key_value_store = await self.get()
103
+
104
+ if key_value_store is not None and isinstance(key_value_store.model_extra, dict):
105
+ url_signing_secret_key = key_value_store.model_extra.get('urlSigningSecretKey')
106
+ if url_signing_secret_key:
107
+ public_url = public_url.with_query(signature=create_hmac_signature(url_signing_secret_key, key))
93
108
 
94
- return f'{public_api_url}/v2/key-value-stores/{self._client.resource_id}/records/{key}'
109
+ return str(public_url)
@@ -201,19 +201,28 @@ async def test_generate_public_url_for_kvs_record(
201
201
  run_actor: RunActorFunction,
202
202
  ) -> None:
203
203
  async def main() -> None:
204
- from typing import cast
205
-
206
- from apify.apify_storage_client._key_value_store_client import KeyValueStoreClient
204
+ from apify._crypto import create_hmac_signature
207
205
 
208
206
  async with Actor:
209
207
  public_api_url = Actor.config.api_public_base_url
210
208
  default_store_id = Actor.config.default_key_value_store_id
209
+ record_key = 'public-record-key'
211
210
 
212
211
  store = await Actor.open_key_value_store()
213
- record_url = await cast(KeyValueStoreClient, store._resource_client).get_public_url('dummy')
214
- print(record_url)
215
212
 
216
- assert record_url == f'{public_api_url}/v2/key-value-stores/{default_store_id}/records/dummy'
213
+ assert isinstance(store.storage_object.model_extra, dict)
214
+ url_signing_secret_key = store.storage_object.model_extra.get('urlSigningSecretKey')
215
+ assert url_signing_secret_key is not None
216
+
217
+ await store.set_value(record_key, {'exposedData': 'test'}, 'application/json')
218
+
219
+ record_url = await store.get_public_url(record_key)
220
+
221
+ signature = create_hmac_signature(url_signing_secret_key, record_key)
222
+ assert (
223
+ record_url
224
+ == f'{public_api_url}/v2/key-value-stores/{default_store_id}/records/{record_key}?signature={signature}'
225
+ )
217
226
 
218
227
  actor = await make_actor(label='kvs-get-public-url', main_func=main)
219
228
  run_result = await run_actor(actor)
@@ -9,7 +9,7 @@ from typing import Any, Callable, cast
9
9
  from unittest.mock import AsyncMock, Mock
10
10
 
11
11
  import pytest
12
- import websockets.server
12
+ import websockets.asyncio.server
13
13
  from lazy_object_proxy import Proxy
14
14
 
15
15
  from apify_shared.consts import ActorEnvVars, ApifyEnvVars
@@ -139,10 +139,10 @@ async def test_actor_handles_migrating_event_correctly(monkeypatch: pytest.Monke
139
139
  nonlocal persist_state_events_data
140
140
  persist_state_events_data.append(data)
141
141
 
142
- async def handler(websocket: websockets.server.WebSocketServerProtocol) -> None:
142
+ async def handler(websocket: websockets.asyncio.server.ServerConnection) -> None:
143
143
  await websocket.wait_closed()
144
144
 
145
- async with websockets.server.serve(handler, host='localhost') as ws_server:
145
+ async with websockets.asyncio.server.serve(handler, host='localhost') as ws_server:
146
146
  port: int = ws_server.sockets[0].getsockname()[1] # type: ignore[index]
147
147
  monkeypatch.setenv(ApifyEnvVars.ACTOR_EVENTS_WS_URL, f'ws://localhost:{port}')
148
148
 
@@ -181,7 +181,7 @@ async def test_actor_handles_migrating_event_correctly(monkeypatch: pytest.Monke
181
181
  Actor.on(Event.PERSIST_STATE, log_persist_state)
182
182
  await asyncio.sleep(2)
183
183
 
184
- for socket in ws_server.websockets:
184
+ for socket in ws_server.connections:
185
185
  await socket.send(
186
186
  json.dumps(
187
187
  {
@@ -4,7 +4,15 @@ import base64
4
4
 
5
5
  import pytest
6
6
 
7
- from apify._crypto import _load_public_key, crypto_random_object_id, load_private_key, private_decrypt, public_encrypt
7
+ from apify._crypto import (
8
+ _load_public_key,
9
+ create_hmac_signature,
10
+ crypto_random_object_id,
11
+ encode_base62,
12
+ load_private_key,
13
+ private_decrypt,
14
+ public_encrypt,
15
+ )
8
16
 
9
17
  # NOTE: Uses the same keys as in:
10
18
  # https://github.com/apify/apify-shared-js/blob/master/test/crypto.test.ts
@@ -105,3 +113,25 @@ def test_crypto_random_object_id_length_and_charset() -> None:
105
113
  long_random_object_id = crypto_random_object_id(1000)
106
114
  for char in long_random_object_id:
107
115
  assert char in 'abcdefghijklmnopqrstuvwxyzABCEDFGHIJKLMNOPQRSTUVWXYZ0123456789'
116
+
117
+
118
+ @pytest.mark.parametrize(('test_input', 'expected'), [(0, '0'), (10, 'a'), (999999999, '15FTGf')])
119
+ def test_encode_base62(test_input: int, expected: str) -> None:
120
+ assert encode_base62(test_input) == expected
121
+
122
+
123
+ # This test ensures compatibility with the JavaScript version of the same method.
124
+ # https://github.com/apify/apify-shared-js/blob/master/packages/utilities/src/hmac.ts
125
+ def test_create_valid_hmac_signature() -> None:
126
+ # This test uses the same secret key and message as in JS tests.
127
+ secret_key = 'hmac-secret-key'
128
+ message = 'hmac-message-to-be-authenticated'
129
+ assert create_hmac_signature(secret_key, message) == 'pcVagAsudj8dFqdlg7mG'
130
+
131
+
132
+ def test_create_same_hmac() -> None:
133
+ # This test uses the same secret key and message as in JS tests.
134
+ secret_key = 'hmac-same-secret-key'
135
+ message = 'hmac-same-message-to-be-authenticated'
136
+ assert create_hmac_signature(secret_key, message) == 'FYMcmTIm3idXqleF1Sw5'
137
+ assert create_hmac_signature(secret_key, message) == 'FYMcmTIm3idXqleF1Sw5'
@@ -9,7 +9,7 @@ from unittest.mock import Mock
9
9
 
10
10
  import pytest
11
11
  import websockets
12
- import websockets.server
12
+ import websockets.asyncio.server
13
13
 
14
14
  from apify_shared.consts import ActorEnvVars
15
15
  from crawlee.events._types import Event
@@ -133,16 +133,16 @@ async def test_lifecycle_on_platform_without_websocket(monkeypatch: pytest.Monke
133
133
 
134
134
 
135
135
  async def test_lifecycle_on_platform(monkeypatch: pytest.MonkeyPatch) -> None:
136
- connected_ws_clients: set[websockets.server.WebSocketServerProtocol] = set()
136
+ connected_ws_clients: set[websockets.asyncio.server.ServerConnection] = set()
137
137
 
138
- async def handler(websocket: websockets.server.WebSocketServerProtocol) -> None:
138
+ async def handler(websocket: websockets.asyncio.server.ServerConnection) -> None:
139
139
  connected_ws_clients.add(websocket)
140
140
  try:
141
141
  await websocket.wait_closed()
142
142
  finally:
143
143
  connected_ws_clients.remove(websocket)
144
144
 
145
- async with websockets.server.serve(handler, host='localhost') as ws_server:
145
+ async with websockets.asyncio.server.serve(handler, host='localhost') as ws_server:
146
146
  # When you don't specify a port explicitly, the websocket connection is opened on a random free port.
147
147
  # We need to find out which port is that.
148
148
  port: int = ws_server.sockets[0].getsockname()[1] # type: ignore[index]
@@ -153,9 +153,9 @@ async def test_lifecycle_on_platform(monkeypatch: pytest.MonkeyPatch) -> None:
153
153
 
154
154
 
155
155
  async def test_event_handling_on_platform(monkeypatch: pytest.MonkeyPatch) -> None:
156
- connected_ws_clients: set[websockets.server.WebSocketServerProtocol] = set()
156
+ connected_ws_clients: set[websockets.asyncio.server.ServerConnection] = set()
157
157
 
158
- async def handler(websocket: websockets.server.WebSocketServerProtocol) -> None:
158
+ async def handler(websocket: websockets.asyncio.server.ServerConnection) -> None:
159
159
  connected_ws_clients.add(websocket)
160
160
  try:
161
161
  await websocket.wait_closed()
@@ -169,7 +169,7 @@ async def test_event_handling_on_platform(monkeypatch: pytest.MonkeyPatch) -> No
169
169
 
170
170
  websockets.broadcast(connected_ws_clients, json.dumps(message))
171
171
 
172
- async with websockets.server.serve(handler, host='localhost') as ws_server:
172
+ async with websockets.asyncio.server.serve(handler, host='localhost') as ws_server:
173
173
  # When you don't specify a port explicitly, the websocket connection is opened on a random free port.
174
174
  # We need to find out which port is that.
175
175
  port: int = ws_server.sockets[0].getsockname()[1] # type: ignore[index]