crawlerflow 0.1.0__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.
Files changed (66) hide show
  1. crawlerflow-0.1.0/.gitignore +10 -0
  2. crawlerflow-0.1.0/LICENSE +21 -0
  3. crawlerflow-0.1.0/PKG-INFO +131 -0
  4. crawlerflow-0.1.0/README.md +93 -0
  5. crawlerflow-0.1.0/crawlerflow/__init__.py +9 -0
  6. crawlerflow-0.1.0/crawlerflow/__main__.py +6 -0
  7. crawlerflow-0.1.0/crawlerflow/browser/__init__.py +8 -0
  8. crawlerflow-0.1.0/crawlerflow/browser/base.py +72 -0
  9. crawlerflow-0.1.0/crawlerflow/browser/factory.py +40 -0
  10. crawlerflow-0.1.0/crawlerflow/browser/pydoll.py +311 -0
  11. crawlerflow-0.1.0/crawlerflow/cli/__init__.py +4 -0
  12. crawlerflow-0.1.0/crawlerflow/cli/app.py +237 -0
  13. crawlerflow-0.1.0/crawlerflow/engine/__init__.py +9 -0
  14. crawlerflow-0.1.0/crawlerflow/engine/context.py +164 -0
  15. crawlerflow-0.1.0/crawlerflow/engine/executor.py +273 -0
  16. crawlerflow-0.1.0/crawlerflow/engine/registry.py +107 -0
  17. crawlerflow-0.1.0/crawlerflow/engine/runner.py +170 -0
  18. crawlerflow-0.1.0/crawlerflow/events/__init__.py +17 -0
  19. crawlerflow-0.1.0/crawlerflow/events/bus.py +38 -0
  20. crawlerflow-0.1.0/crawlerflow/events/console_logger.py +56 -0
  21. crawlerflow-0.1.0/crawlerflow/events/json_logger.py +53 -0
  22. crawlerflow-0.1.0/crawlerflow/events/models.py +31 -0
  23. crawlerflow-0.1.0/crawlerflow/expressions/__init__.py +21 -0
  24. crawlerflow-0.1.0/crawlerflow/expressions/conditions.py +83 -0
  25. crawlerflow-0.1.0/crawlerflow/expressions/engine.py +191 -0
  26. crawlerflow-0.1.0/crawlerflow/plugins/__init__.py +35 -0
  27. crawlerflow-0.1.0/crawlerflow/plugins/base.py +37 -0
  28. crawlerflow-0.1.0/crawlerflow/plugins/manager.py +212 -0
  29. crawlerflow-0.1.0/crawlerflow/py.typed +0 -0
  30. crawlerflow-0.1.0/crawlerflow/steps/__init__.py +20 -0
  31. crawlerflow-0.1.0/crawlerflow/steps/browser.py +572 -0
  32. crawlerflow-0.1.0/crawlerflow/steps/control.py +706 -0
  33. crawlerflow-0.1.0/crawlerflow/steps/utility.py +1487 -0
  34. crawlerflow-0.1.0/crawlerflow/workflow/__init__.py +9 -0
  35. crawlerflow-0.1.0/crawlerflow/workflow/loader.py +34 -0
  36. crawlerflow-0.1.0/crawlerflow/workflow/models.py +171 -0
  37. crawlerflow-0.1.0/docs/adr/0001-core-boundaries.md +21 -0
  38. crawlerflow-0.1.0/docs/architecture.md +47 -0
  39. crawlerflow-0.1.0/docs/control-flow.md +215 -0
  40. crawlerflow-0.1.0/docs/html-output.md +197 -0
  41. crawlerflow-0.1.0/docs/http-requests.md +99 -0
  42. crawlerflow-0.1.0/docs/plugins.md +132 -0
  43. crawlerflow-0.1.0/docs/pydoll.md +61 -0
  44. crawlerflow-0.1.0/docs/retries-and-logging.md +89 -0
  45. crawlerflow-0.1.0/docs/runtime-variables.md +39 -0
  46. crawlerflow-0.1.0/examples/basic.yaml +17 -0
  47. crawlerflow-0.1.0/examples/control-flow.yaml +26 -0
  48. crawlerflow-0.1.0/examples/loops-macros.yaml +20 -0
  49. crawlerflow-0.1.0/examples/plugins/example/README.md +15 -0
  50. crawlerflow-0.1.0/examples/plugins/example/pyproject.toml +17 -0
  51. crawlerflow-0.1.0/examples/plugins/example/src/crawlerflow_example_plugin/__init__.py +66 -0
  52. crawlerflow-0.1.0/examples/plugins/example/workflow.yaml +18 -0
  53. crawlerflow-0.1.0/examples/pydoll.yaml +29 -0
  54. crawlerflow-0.1.0/examples/retry-logging.yaml +32 -0
  55. crawlerflow-0.1.0/pyproject.toml +83 -0
  56. crawlerflow-0.1.0/tests/test_browser_steps.py +753 -0
  57. crawlerflow-0.1.0/tests/test_cli.py +372 -0
  58. crawlerflow-0.1.0/tests/test_conditions.py +26 -0
  59. crawlerflow-0.1.0/tests/test_control_flow.py +328 -0
  60. crawlerflow-0.1.0/tests/test_example_plugin.py +33 -0
  61. crawlerflow-0.1.0/tests/test_expressions.py +78 -0
  62. crawlerflow-0.1.0/tests/test_loops_and_macros.py +443 -0
  63. crawlerflow-0.1.0/tests/test_plugins.py +385 -0
  64. crawlerflow-0.1.0/tests/test_pydoll_adapter.py +221 -0
  65. crawlerflow-0.1.0/tests/test_retry_and_logging.py +277 -0
  66. crawlerflow-0.1.0/tests/test_workflow.py +512 -0
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .venv/
7
+ build/
8
+ dist/
9
+ output/
10
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mehmet Emin Eker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,131 @@
1
+ Metadata-Version: 2.5
2
+ Name: crawlerflow
3
+ Version: 0.1.0
4
+ Summary: Declarative YAML workflow engine for browser automation and web scraping
5
+ Project-URL: Homepage, https://github.com/mehmetemineker/crawlerflow
6
+ Project-URL: Repository, https://github.com/mehmetemineker/crawlerflow
7
+ Project-URL: Issues, https://github.com/mehmetemineker/crawlerflow/issues
8
+ Project-URL: Documentation, https://github.com/mehmetemineker/crawlerflow/tree/main/docs
9
+ Author: Mehmet Emin Eker
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: automation,browser,crawler,scraping,workflow,yaml
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
21
+ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.12
25
+ Requires-Dist: httpx>=0.27
26
+ Requires-Dist: pydantic>=2.8
27
+ Requires-Dist: pyyaml>=6.0
28
+ Requires-Dist: rich>=13.7
29
+ Requires-Dist: typer>=0.12
30
+ Provides-Extra: browser
31
+ Requires-Dist: pydoll-python<3,>=2.23; extra == 'browser'
32
+ Provides-Extra: dev
33
+ Requires-Dist: pydoll-python<3,>=2.23; extra == 'dev'
34
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
35
+ Requires-Dist: pytest>=8.2; extra == 'dev'
36
+ Requires-Dist: ruff>=0.5; extra == 'dev'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # CrawlerFlow
40
+
41
+ CrawlerFlow is a declarative, YAML-based workflow engine for browser automation and web
42
+ scraping. Workflows describe what should happen; adapters and steps decide how it happens.
43
+
44
+ ## Current foundation
45
+
46
+ - Versioned YAML workflow loading and validation
47
+ - Browser-independent adapter contract and a lazy-starting Pydoll implementation
48
+ - Extensible step registry
49
+ - Isolated plugin API with typed YAML settings, lifecycle hooks, steps, filters, and subscribers
50
+ - Async workflow executor and event bus
51
+ - Variable interpolation and a built-in expression engine
52
+ - Per-run `today` and `now` date variables
53
+ - Nested `foreach`, `foreach_date`, `foreach_select`, and declarative `if` control flow
54
+ - Reusable parameterized workflow macros
55
+ - Per-step retry and continue/fail error policies
56
+ - JSON Lines workflow, step, retry, and request event logging
57
+ - Built-in navigation, interaction, cookies, downloads, screenshots, and selective HTML output
58
+ - `run`, `validate`, `list-steps`, `list-plugins`, and `doctor` CLI commands
59
+
60
+ ## Development
61
+
62
+ ```bash
63
+ python -m pip install -e ".[dev]"
64
+ pytest
65
+ crawlerflow validate examples/basic.yaml
66
+ crawlerflow run examples/basic.yaml
67
+ ```
68
+
69
+ Run multiple workflows sequentially by supplying more paths. Execution stops at the first failed
70
+ workflow:
71
+
72
+ ```bash
73
+ crawlerflow run examples/first-site.yaml examples/second-site.yaml
74
+ ```
75
+
76
+ A directory argument discovers its directly contained `.yaml` and `.yml` files and runs them in
77
+ alphabetical order:
78
+
79
+ ```bash
80
+ crawlerflow run examples
81
+ ```
82
+
83
+ Use asynchronous mode to run every supplied workflow in parallel. All workflows are allowed to
84
+ finish; the command exits with code `1` if any workflow fails:
85
+
86
+ ```bash
87
+ crawlerflow run --mode async examples/first-site.yaml examples/second-site.yaml
88
+ ```
89
+
90
+ Directory discovery can also be combined with parallel execution:
91
+
92
+ ```bash
93
+ crawlerflow run --mode async examples
94
+ ```
95
+
96
+ Add `--progress` to display a live progress bar based on the total workflow count. The bar advances
97
+ as each workflow succeeds or fails in both sequential and asynchronous modes:
98
+
99
+ ```bash
100
+ crawlerflow run --mode async --progress examples
101
+ ```
102
+
103
+ Use `--concurrency` (or `-c`) to limit how many workflows run at the same time in asynchronous
104
+ mode. This avoids starting every HTTP client or browser session simultaneously:
105
+
106
+ ```bash
107
+ crawlerflow run --mode async --concurrency 8 --progress examples
108
+ ```
109
+
110
+ Omitting the option preserves unlimited parallel execution. `--concurrency` accepts positive
111
+ integers and can only be used with `--mode async`.
112
+
113
+ Install browser support and select Pydoll in a workflow:
114
+
115
+ ```bash
116
+ python -m pip install -e ".[browser,dev]"
117
+ ```
118
+
119
+ ```yaml
120
+ browser:
121
+ engine: pydoll
122
+ headless: true
123
+ ```
124
+
125
+ Browser-free workflows omit the `browser` section. Applications can also inject another
126
+ `BrowserAdapter` into `WorkflowRunner`. See `docs/http-requests.md` for direct HTTP requests and
127
+ shortened map URL coordinate resolution.
128
+
129
+ External extensions can register entry points under `crawlerflow.plugins`; workflows activate only
130
+ the plugins they list. See `docs/plugins.md` and the installable `examples/plugins/example` package
131
+ for the plugin contract, discovery command, and packaging example.
@@ -0,0 +1,93 @@
1
+ # CrawlerFlow
2
+
3
+ CrawlerFlow is a declarative, YAML-based workflow engine for browser automation and web
4
+ scraping. Workflows describe what should happen; adapters and steps decide how it happens.
5
+
6
+ ## Current foundation
7
+
8
+ - Versioned YAML workflow loading and validation
9
+ - Browser-independent adapter contract and a lazy-starting Pydoll implementation
10
+ - Extensible step registry
11
+ - Isolated plugin API with typed YAML settings, lifecycle hooks, steps, filters, and subscribers
12
+ - Async workflow executor and event bus
13
+ - Variable interpolation and a built-in expression engine
14
+ - Per-run `today` and `now` date variables
15
+ - Nested `foreach`, `foreach_date`, `foreach_select`, and declarative `if` control flow
16
+ - Reusable parameterized workflow macros
17
+ - Per-step retry and continue/fail error policies
18
+ - JSON Lines workflow, step, retry, and request event logging
19
+ - Built-in navigation, interaction, cookies, downloads, screenshots, and selective HTML output
20
+ - `run`, `validate`, `list-steps`, `list-plugins`, and `doctor` CLI commands
21
+
22
+ ## Development
23
+
24
+ ```bash
25
+ python -m pip install -e ".[dev]"
26
+ pytest
27
+ crawlerflow validate examples/basic.yaml
28
+ crawlerflow run examples/basic.yaml
29
+ ```
30
+
31
+ Run multiple workflows sequentially by supplying more paths. Execution stops at the first failed
32
+ workflow:
33
+
34
+ ```bash
35
+ crawlerflow run examples/first-site.yaml examples/second-site.yaml
36
+ ```
37
+
38
+ A directory argument discovers its directly contained `.yaml` and `.yml` files and runs them in
39
+ alphabetical order:
40
+
41
+ ```bash
42
+ crawlerflow run examples
43
+ ```
44
+
45
+ Use asynchronous mode to run every supplied workflow in parallel. All workflows are allowed to
46
+ finish; the command exits with code `1` if any workflow fails:
47
+
48
+ ```bash
49
+ crawlerflow run --mode async examples/first-site.yaml examples/second-site.yaml
50
+ ```
51
+
52
+ Directory discovery can also be combined with parallel execution:
53
+
54
+ ```bash
55
+ crawlerflow run --mode async examples
56
+ ```
57
+
58
+ Add `--progress` to display a live progress bar based on the total workflow count. The bar advances
59
+ as each workflow succeeds or fails in both sequential and asynchronous modes:
60
+
61
+ ```bash
62
+ crawlerflow run --mode async --progress examples
63
+ ```
64
+
65
+ Use `--concurrency` (or `-c`) to limit how many workflows run at the same time in asynchronous
66
+ mode. This avoids starting every HTTP client or browser session simultaneously:
67
+
68
+ ```bash
69
+ crawlerflow run --mode async --concurrency 8 --progress examples
70
+ ```
71
+
72
+ Omitting the option preserves unlimited parallel execution. `--concurrency` accepts positive
73
+ integers and can only be used with `--mode async`.
74
+
75
+ Install browser support and select Pydoll in a workflow:
76
+
77
+ ```bash
78
+ python -m pip install -e ".[browser,dev]"
79
+ ```
80
+
81
+ ```yaml
82
+ browser:
83
+ engine: pydoll
84
+ headless: true
85
+ ```
86
+
87
+ Browser-free workflows omit the `browser` section. Applications can also inject another
88
+ `BrowserAdapter` into `WorkflowRunner`. See `docs/http-requests.md` for direct HTTP requests and
89
+ shortened map URL coordinate resolution.
90
+
91
+ External extensions can register entry points under `crawlerflow.plugins`; workflows activate only
92
+ the plugins they list. See `docs/plugins.md` and the installable `examples/plugins/example` package
93
+ for the plugin contract, discovery command, and packaging example.
@@ -0,0 +1,9 @@
1
+ """CrawlerFlow public package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from crawlerflow.engine.runner import WorkflowRunner
6
+
7
+ __all__ = ["WorkflowRunner"]
8
+ __version__ = "0.1.0"
9
+
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ from crawlerflow.cli.app import app
4
+
5
+ app()
6
+
@@ -0,0 +1,8 @@
1
+ """Browser adapter contracts and implementations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from crawlerflow.browser.base import BrowserAdapter, BrowserResponse
6
+ from crawlerflow.browser.factory import create_browser_adapter
7
+
8
+ __all__ = ["BrowserAdapter", "BrowserResponse", "create_browser_adapter"]
@@ -0,0 +1,72 @@
1
+ """Browser abstraction used by workflow steps."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from dataclasses import dataclass, field
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+
11
+ @dataclass(slots=True, frozen=True)
12
+ class BrowserResponse:
13
+ """Serializable response returned by browser-session requests."""
14
+
15
+ status_code: int
16
+ headers: dict[str, str] = field(default_factory=dict)
17
+ body: str | bytes | None = None
18
+
19
+
20
+ class BrowserAdapter(ABC):
21
+ """Backend-neutral asynchronous browser contract."""
22
+
23
+ @abstractmethod
24
+ async def goto(self, url: str) -> None: ...
25
+
26
+ @abstractmethod
27
+ async def click(self, selector: str) -> None: ...
28
+
29
+ @abstractmethod
30
+ async def fill(self, selector: str, value: str) -> None: ...
31
+
32
+ @abstractmethod
33
+ async def select(self, selector: str, value: str) -> None: ...
34
+
35
+ @abstractmethod
36
+ async def wait(self, selector: str, timeout_seconds: float | None = None) -> None: ...
37
+
38
+ @abstractmethod
39
+ async def wait_network(self, timeout_seconds: float | None = None) -> None: ...
40
+
41
+ @abstractmethod
42
+ async def html(self) -> str: ...
43
+
44
+ @abstractmethod
45
+ async def evaluate(self, script: str) -> Any: ...
46
+
47
+ @abstractmethod
48
+ async def cookies(self) -> dict[str, str]: ...
49
+
50
+ @abstractmethod
51
+ async def set_cookies(self, cookies: dict[str, str]) -> None: ...
52
+
53
+ @abstractmethod
54
+ async def request(
55
+ self,
56
+ method: str,
57
+ url: str,
58
+ *,
59
+ headers: dict[str, str] | None = None,
60
+ data: Any = None,
61
+ ) -> BrowserResponse: ...
62
+
63
+ @abstractmethod
64
+ async def download(self, url: str, path: Path) -> Path: ...
65
+
66
+ @abstractmethod
67
+ async def screenshot(self, path: Path) -> Path: ...
68
+
69
+ async def close(self) -> None:
70
+ """Release browser resources when an adapter owns them."""
71
+
72
+ return None
@@ -0,0 +1,40 @@
1
+ """Create browser adapters from workflow configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from crawlerflow.browser.base import BrowserAdapter
8
+ from crawlerflow.workflow.models import BrowserSettings
9
+
10
+
11
+ def create_browser_adapter(
12
+ settings: BrowserSettings,
13
+ *,
14
+ base_path: Path,
15
+ ) -> BrowserAdapter | None:
16
+ """Create the configured browser adapter, if browser execution is enabled."""
17
+
18
+ if settings.engine is None:
19
+ return None
20
+ if settings.engine == "pydoll":
21
+ from crawlerflow.browser.pydoll import PydollBrowserAdapter, PydollBrowserConfig
22
+
23
+ binary_location = settings.binary_location
24
+ if binary_location is not None and not binary_location.is_absolute():
25
+ binary_location = base_path / binary_location
26
+ download_directory = settings.download_directory
27
+ if download_directory is not None and not download_directory.is_absolute():
28
+ download_directory = base_path / download_directory
29
+ return PydollBrowserAdapter(
30
+ PydollBrowserConfig(
31
+ headless=settings.headless,
32
+ binary_location=binary_location,
33
+ arguments=tuple(settings.arguments),
34
+ start_timeout=settings.start_timeout,
35
+ default_wait_timeout=settings.default_wait_timeout,
36
+ network_idle_period=settings.network_idle_period,
37
+ download_directory=download_directory,
38
+ )
39
+ )
40
+ raise ValueError(f"Unsupported browser engine: {settings.engine}")