climate-ref-celery 0.6.3__tar.gz → 0.6.5__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 (25) hide show
  1. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/PKG-INFO +1 -1
  2. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/pyproject.toml +1 -1
  3. climate_ref_celery-0.6.5/src/climate_ref_celery/cli.py +172 -0
  4. climate_ref_celery-0.6.5/tests/unit/test_cli.py +196 -0
  5. climate_ref_celery-0.6.3/src/climate_ref_celery/cli.py +0 -110
  6. climate_ref_celery-0.6.3/tests/unit/test_cli.py +0 -124
  7. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/.gitignore +0 -0
  8. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/LICENCE +0 -0
  9. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/NOTICE +0 -0
  10. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/README.md +0 -0
  11. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/src/climate_ref_celery/__init__.py +0 -0
  12. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/src/climate_ref_celery/app.py +0 -0
  13. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/src/climate_ref_celery/celeryconf/__init__.py +0 -0
  14. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/src/climate_ref_celery/celeryconf/base.py +0 -0
  15. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/src/climate_ref_celery/celeryconf/dev.py +0 -0
  16. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/src/climate_ref_celery/celeryconf/prod.py +0 -0
  17. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/src/climate_ref_celery/executor.py +0 -0
  18. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/src/climate_ref_celery/py.typed +0 -0
  19. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/src/climate_ref_celery/tasks.py +0 -0
  20. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/src/climate_ref_celery/worker_tasks.py +0 -0
  21. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/tests/conftest.py +0 -0
  22. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/tests/unit/test_app.py +0 -0
  23. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/tests/unit/test_executor.py +0 -0
  24. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/tests/unit/test_tasks.py +0 -0
  25. {climate_ref_celery-0.6.3 → climate_ref_celery-0.6.5}/tests/unit/test_worker_tasks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: climate-ref-celery
3
- Version: 0.6.3
3
+ Version: 0.6.5
4
4
  Summary: Celery app for mananging tasks and workers
5
5
  Author-email: Jared Lewis <jared.lewis@climate-resource.com>
6
6
  License-Expression: Apache-2.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "climate-ref-celery"
3
- version = "0.6.3"
3
+ version = "0.6.5"
4
4
  description = "Celery app for mananging tasks and workers"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -0,0 +1,172 @@
1
+ """
2
+ Managing remote celery workers
3
+
4
+ This module is used to manage remote execution workers for the Climate REF project.
5
+ It is added to the `ref` command line interface if the `climate-ref-celery` package is installed.
6
+
7
+ A celery worker should be run for each diagnostic provider.
8
+ """
9
+
10
+ import importlib.metadata
11
+ from warnings import warn
12
+
13
+ import typer
14
+ from loguru import logger
15
+
16
+ from climate_ref_celery.app import create_celery_app
17
+ from climate_ref_celery.tasks import register_celery_tasks
18
+ from climate_ref_core.providers import DiagnosticProvider
19
+
20
+ app = typer.Typer(help=__doc__)
21
+
22
+
23
+ def import_provider(provider_name: str) -> DiagnosticProvider:
24
+ """
25
+ Import the provider using the name of a registered provider.
26
+
27
+ Parameters
28
+ ----------
29
+ provider_name:
30
+ The name of a registered provider.
31
+
32
+ Packages can register a provider by defining an
33
+ [entry point](https://packaging.python.org/en/latest/specifications/entry-points/)
34
+ in its `pyproject.toml` file under the group `"climate-ref.providers"`.
35
+
36
+ Example: 'climate_ref_esmvaltool:provider' would require a section in the `pyproject.toml` for the
37
+ `climate_ref_esmvaltool` package like this:
38
+
39
+ ```
40
+ [project.entry-points."climate-ref.providers"]
41
+ esmvaltool = "climate_ref_esmvaltool:provider"
42
+ ```
43
+
44
+ `"esmvaltool"` or ("climate_ref_esmvaltool:provider")
45
+ can then be used as the `provider_name` argument.
46
+
47
+ If the entry point is not found, an error will be raised
48
+ and the list of available providers will be shown.
49
+
50
+ Raises
51
+ ------
52
+ typer.Abort
53
+ If the provider_package does not define a 'provider' variable
54
+
55
+ If the provider_package is not found
56
+
57
+ Returns
58
+ -------
59
+ :
60
+ The provider instance
61
+ """
62
+ provider_entry_points = importlib.metadata.entry_points(group="climate-ref.providers")
63
+ for entry_point in provider_entry_points:
64
+ logger.debug(f"found entry point: {entry_point}")
65
+
66
+ # Also support the case where the entrypoint definition ('name:provider') is supplied
67
+ if entry_point.name == provider_name or entry_point.value == provider_name: # noqa: PLR1714
68
+ break
69
+ else:
70
+ found_entry_points = ", ".join(f"{ep.name} ({ep.value})" for ep in provider_entry_points)
71
+ if len(found_entry_points) == 0:
72
+ found_entry_points = "[]"
73
+
74
+ typer.echo(
75
+ f"No entry point named {provider_name!r} was found. Found entry points: {found_entry_points}."
76
+ )
77
+ raise typer.Abort()
78
+
79
+ # Get the provider from the provider_package
80
+ try:
81
+ provider = entry_point.load()
82
+ except ModuleNotFoundError:
83
+ _split = entry_point.value.split(":", 1)
84
+ typer.echo(f"Invalid entrypoint {entry_point}: Package {_split[0]!r} not found.")
85
+ raise typer.Abort()
86
+ except AttributeError:
87
+ _split = entry_point.value.split(":", 1)
88
+ typer.echo(
89
+ f"Invalid entrypoint {entry_point}: {_split[0]!r} does not define a {_split[1]!r} attribute."
90
+ )
91
+ raise typer.Abort()
92
+
93
+ if not isinstance(provider, DiagnosticProvider):
94
+ typer.echo(f"Expected DiagnosticProvider, got {type(provider)}")
95
+ raise typer.Abort()
96
+ return provider
97
+
98
+
99
+ @app.command()
100
+ def start_worker(
101
+ ctx: typer.Context,
102
+ loglevel: str = typer.Option("info", help="Log level for the worker"),
103
+ provider: list[str] | None = typer.Option(
104
+ help="Name of the provider to start a worker for. This argument may be supplied multiple times. "
105
+ "If no provider is given, the worker will consume the default queue.",
106
+ default=None,
107
+ ),
108
+ package: str | None = typer.Option(help="Deprecated. Use provider instead", default=None),
109
+ extra_args: list[str] = typer.Argument(None, help="Additional arguments for the worker"),
110
+ ) -> None:
111
+ """
112
+ Start a Celery worker for the given provider.
113
+
114
+ A celery worker enables the execution of tasks in the background on multiple different nodes.
115
+ This worker will register a celery task for each diagnostic in the provider.
116
+ The worker tasks can be executed by sending a celery task with the name
117
+ '{package_slug}_{diagnostic_slug}'.
118
+
119
+ Providers must be registered as entry points in the `pyproject.toml` file of the package.
120
+ The entry point should be defined under the group `climate-ref.providers`
121
+ (See `import_provider` for details).
122
+ """
123
+ # Create a new celery app
124
+ celery_app = create_celery_app("climate_ref_celery")
125
+
126
+ if package:
127
+ msg = "The '--package' argument is deprecated. Use '--provider' instead."
128
+ # Deprecation warning for package argument
129
+ warn(
130
+ msg,
131
+ DeprecationWarning,
132
+ stacklevel=2,
133
+ )
134
+ typer.echo(msg)
135
+ # Assume the package is the provider
136
+ provider = [package + ":provider"]
137
+
138
+ queues = []
139
+ if provider:
140
+ for p in provider:
141
+ # Attempt to import the provider
142
+ provider_instance = import_provider(p)
143
+
144
+ if hasattr(ctx.obj, "config"):
145
+ # Configure the provider so that it knows where the conda environments are
146
+ provider_instance.configure(ctx.obj.config)
147
+
148
+ # Wrap each diagnostics in the provider with a celery tasks
149
+ register_celery_tasks(celery_app, provider_instance)
150
+ queues.append(provider_instance.slug)
151
+ else:
152
+ # This might need some tweaking in later PRs to pull in the appropriate tasks
153
+ import climate_ref_celery.worker_tasks # noqa: F401
154
+
155
+ queues.append("celery")
156
+
157
+ argv = ["worker", "-E", f"--loglevel={loglevel}", f"--queues={','.join(queues)}", *(extra_args or [])]
158
+ celery_app.worker_main(argv=argv)
159
+
160
+
161
+ @app.command()
162
+ def list_config() -> None:
163
+ """
164
+ List the celery configuration
165
+ """
166
+ celery_app = create_celery_app("climate_ref_celery")
167
+
168
+ print(celery_app.conf.humanize())
169
+
170
+
171
+ if __name__ == "__main__": # pragma: no cover
172
+ app()
@@ -0,0 +1,196 @@
1
+ import importlib.metadata
2
+
3
+ import pytest
4
+ from climate_ref_celery.cli import app
5
+ from typer.testing import CliRunner
6
+
7
+ from climate_ref_core.providers import DiagnosticProvider
8
+
9
+ runner = CliRunner()
10
+
11
+
12
+ def test_cli_help():
13
+ result = runner.invoke(app, ["--help"])
14
+ assert result.exit_code == 0
15
+
16
+
17
+ @pytest.fixture
18
+ def mock_create_celery_app(mocker):
19
+ return mocker.patch("climate_ref_celery.cli.create_celery_app")
20
+
21
+
22
+ @pytest.fixture
23
+ def mock_register_celery_tasks(mocker):
24
+ return mocker.patch("climate_ref_celery.cli.register_celery_tasks")
25
+
26
+
27
+ @pytest.mark.parametrize("provider", ["test_package", "test_package:provider"])
28
+ def test_start_worker_success(mocker, mock_create_celery_app, mock_register_celery_tasks, provider):
29
+ mock_celery_app = mock_create_celery_app.return_value
30
+ mock_provider = mocker.MagicMock(spec=DiagnosticProvider)
31
+ mock_provider.slug = "example"
32
+
33
+ mock_entry_point = mocker.Mock(spec=importlib.metadata.EntryPoint)
34
+ mock_entry_point.name = "test_package"
35
+ mock_entry_point.value = "test_package:provider"
36
+ mock_entry_point.load.return_value = mock_provider
37
+ mock_entry_points = mocker.patch("importlib.metadata.entry_points", return_value=[mock_entry_point])
38
+
39
+ result = runner.invoke(app, ["start-worker", "--provider", provider])
40
+
41
+ assert result.exit_code == 0
42
+ mock_entry_points.assert_called_once_with(group="climate-ref.providers")
43
+ mock_register_celery_tasks.assert_called_once_with(mock_create_celery_app.return_value, mock_provider)
44
+ mock_celery_app.worker_main.assert_called_once_with(
45
+ argv=["worker", "-E", "--loglevel=info", "--queues=example"]
46
+ )
47
+
48
+
49
+ def test_start_worker_multiple(mocker, mock_create_celery_app, mock_register_celery_tasks):
50
+ mock_celery_app = mock_create_celery_app.return_value
51
+
52
+ mock_provider_a = mocker.MagicMock(spec=DiagnosticProvider)
53
+ mock_provider_a.slug = "example"
54
+ mock_entry_point_a = mocker.Mock(spec=importlib.metadata.EntryPoint)
55
+ mock_entry_point_a.name = "test_package"
56
+ mock_entry_point_a.value = "test_package:provider"
57
+ mock_entry_point_a.load.return_value = mock_provider_a
58
+
59
+ mock_provider_b = mocker.MagicMock(spec=DiagnosticProvider)
60
+ mock_provider_b.slug = "other"
61
+ mock_entry_point_b = mocker.Mock(spec=importlib.metadata.EntryPoint)
62
+ mock_entry_point_b.name = "other_package"
63
+ mock_entry_point_b.value = "other_package:provider"
64
+ mock_entry_point_b.load.return_value = mock_provider_b
65
+
66
+ mock_entry_points = mocker.patch(
67
+ "importlib.metadata.entry_points", return_value=[mock_entry_point_a, mock_entry_point_b]
68
+ )
69
+
70
+ result = runner.invoke(app, ["start-worker", "--provider", "test_package", "--provider", "other_package"])
71
+
72
+ assert result.exit_code == 0
73
+ mock_entry_points.assert_called_with(group="climate-ref.providers")
74
+ mock_register_celery_tasks.assert_any_call(mock_create_celery_app.return_value, mock_provider_a)
75
+ mock_register_celery_tasks.assert_any_call(mock_create_celery_app.return_value, mock_provider_b)
76
+ mock_celery_app.worker_main.assert_called_once_with(
77
+ argv=["worker", "-E", "--loglevel=info", "--queues=example,other"]
78
+ )
79
+
80
+
81
+ def test_start_core_worker_success(mock_create_celery_app, mock_register_celery_tasks):
82
+ mock_celery_app = mock_create_celery_app.return_value
83
+
84
+ result = runner.invoke(app, ["start-worker"])
85
+
86
+ assert result.exit_code == 0
87
+ mock_celery_app.worker_main.assert_called_once_with(
88
+ argv=["worker", "-E", "--loglevel=info", "--queues=celery"]
89
+ )
90
+
91
+
92
+ def test_start_worker_success_extra_args(mocker, mock_create_celery_app, mock_register_celery_tasks):
93
+ mock_worker_main = mock_create_celery_app.return_value
94
+ mock_provider = mocker.MagicMock(spec=DiagnosticProvider)
95
+ mock_provider.slug = "example"
96
+
97
+ mock_entry_point = mocker.Mock(spec=importlib.metadata.EntryPoint)
98
+ mock_entry_point.name = "test_package"
99
+ mock_entry_point.load.return_value = mock_provider
100
+ mocker.patch("importlib.metadata.entry_points", return_value=[mock_entry_point])
101
+
102
+ result = runner.invoke(
103
+ app,
104
+ [
105
+ "start-worker",
106
+ "--loglevel",
107
+ "error",
108
+ "--provider",
109
+ "test_package",
110
+ "--",
111
+ "--extra-args",
112
+ "--concurrency=2",
113
+ ],
114
+ )
115
+
116
+ assert result.exit_code == 0, result.output
117
+ mock_worker_main.worker_main.assert_called_once_with(
118
+ argv=["worker", "-E", "--loglevel=error", "--queues=example", "--extra-args", "--concurrency=2"]
119
+ )
120
+
121
+
122
+ def test_start_worker_package_not_registered(mocker, mock_create_celery_app):
123
+ mocker.patch("importlib.metadata.entry_points", return_value=[])
124
+
125
+ result = runner.invoke(app, ["start-worker", "--provider", "unregistered_package"])
126
+
127
+ assert result.exit_code == 1
128
+ assert "No entry point named 'unregistered_package' was found" in result.output
129
+ assert "Found entry points: []" in result.output
130
+ mock_create_celery_app.assert_called_once_with("climate_ref_celery")
131
+
132
+
133
+ def test_start_worker_package_not_found(mocker, mock_create_celery_app):
134
+ mock_entry_point = mocker.Mock(spec=importlib.metadata.EntryPoint)
135
+ mock_entry_point.name = "missing_package"
136
+ mock_entry_point.value = "missing_package:provider"
137
+ mock_entry_point.load.side_effect = ModuleNotFoundError
138
+ mock_entry_points = mocker.patch("importlib.metadata.entry_points", return_value=[mock_entry_point])
139
+
140
+ result = runner.invoke(app, ["start-worker", "--provider", "missing_package"])
141
+
142
+ assert result.exit_code == 1
143
+ assert "Package 'missing_package' not found" in result.output
144
+ mock_create_celery_app.assert_called_once_with("climate_ref_celery")
145
+ mock_entry_points.assert_called_once_with(group="climate-ref.providers")
146
+
147
+
148
+ def test_start_worker_missing_provider(mocker, mock_create_celery_app):
149
+ mock_entry_point = mocker.Mock(spec=importlib.metadata.EntryPoint)
150
+ mock_entry_point.name = "test_package"
151
+ mock_entry_point.value = "test_package:provider"
152
+ mock_entry_point.load.side_effect = AttributeError
153
+ mocker.patch("importlib.metadata.entry_points", return_value=[mock_entry_point])
154
+
155
+ result = runner.invoke(app, ["start-worker", "--provider", "test_package"])
156
+
157
+ assert result.exit_code == 1, result.output
158
+ assert "'test_package' does not define a 'provider' attribute" in result.output
159
+
160
+
161
+ def test_start_worker_incorrect_provider(mocker, mock_create_celery_app):
162
+ # Not a DiagnosticProvider
163
+ mock_provider = mocker.Mock()
164
+
165
+ mock_entry_point = mocker.Mock(spec=importlib.metadata.EntryPoint)
166
+ mock_entry_point.name = "test_package"
167
+ mock_entry_point.load.return_value = mock_provider
168
+ mocker.patch("importlib.metadata.entry_points", return_value=[mock_entry_point])
169
+
170
+ result = runner.invoke(app, ["start-worker", "--provider", "test_package"])
171
+
172
+ assert result.exit_code == 1, result.output
173
+ assert "Expected DiagnosticProvider, got <class 'unittest.mock.Mock'>" in result.output
174
+
175
+
176
+ def test_start_worker_deprecated_package(mocker, mock_create_celery_app):
177
+ mock_provider = mocker.MagicMock(spec=DiagnosticProvider)
178
+ mock_provider.slug = "example"
179
+
180
+ mock_entry_point = mocker.Mock(spec=importlib.metadata.EntryPoint)
181
+ mock_entry_point.name = "test_package"
182
+ mock_entry_point.value = "test_package:provider"
183
+ mock_entry_point.load.return_value = mock_provider
184
+ mocker.patch("importlib.metadata.entry_points", return_value=[mock_entry_point])
185
+
186
+ result = runner.invoke(app, ["start-worker", "--package", "test_package"])
187
+
188
+ assert result.exit_code == 0, result.output
189
+ assert "The '--package' argument is deprecated. Use '--provider' instead." in result.output
190
+
191
+
192
+ def test_list_config():
193
+ result = runner.invoke(app, ["list-config"])
194
+
195
+ assert result.exit_code == 0, result.output
196
+ assert "broker_url: 'redis://localhost:6379/1'" in result.stdout
@@ -1,110 +0,0 @@
1
- """
2
- Managing remote celery workers
3
-
4
- This module is used to manage remote execution workers for the Climate REF project.
5
- It is added to the `ref` command line interface if the `climate-ref-celery` package is installed.
6
- """
7
-
8
- import importlib
9
-
10
- import typer
11
-
12
- from climate_ref_celery.app import create_celery_app
13
- from climate_ref_celery.tasks import register_celery_tasks
14
- from climate_ref_core.providers import DiagnosticProvider
15
-
16
- app = typer.Typer(help=__doc__)
17
-
18
-
19
- def import_provider(provider_package: str) -> DiagnosticProvider:
20
- """
21
- Import the provider from a given package.
22
-
23
- Parameters
24
- ----------
25
- provider_package:
26
- The package to import the provider from
27
-
28
- Raises
29
- ------
30
- typer.Abort
31
- If the provider_package does not define a 'provider' variable
32
-
33
- If the provider_package is not found
34
-
35
- Returns
36
- -------
37
- :
38
- The provider instance
39
- """
40
- try:
41
- imp = importlib.import_module(provider_package.replace("-", "_"))
42
- except ModuleNotFoundError:
43
- typer.echo(f"Package '{provider_package}' not found")
44
- raise typer.Abort()
45
-
46
- # Get the provider from the provider_package
47
- try:
48
- provider = imp.provider
49
- except AttributeError:
50
- typer.echo("The package must define a 'provider' attribute")
51
- raise typer.Abort()
52
- if not isinstance(provider, DiagnosticProvider):
53
- typer.echo(f"Expected DiagnosticProvider, got {type(provider)}")
54
- raise typer.Abort()
55
- return provider
56
-
57
-
58
- @app.command()
59
- def start_worker(
60
- ctx: typer.Context,
61
- loglevel: str = typer.Option("info", help="Log level for the worker"),
62
- package: str | None = typer.Option(help="Package to import tasks from", default=None),
63
- extra_args: list[str] = typer.Argument(None, help="Additional arguments for the worker"),
64
- ) -> None:
65
- """
66
- Start a Celery worker for the given package.
67
-
68
- A celery worker enables the execution of tasks in the background on multiple different nodes.
69
- This worker will register a celery task for each diagnostic in the provider.
70
- The worker tasks can be executed by sending a celery task with the name
71
- '{package_slug}_{diagnostic_slug}'.
72
-
73
- The package must define a 'provider' variable that is an instance of 'ref_core.DiagnosticProvider'.
74
- """
75
- # Create a new celery app
76
- celery_app = create_celery_app("climate_ref_celery")
77
-
78
- if package:
79
- # Attempt to import the provider
80
- provider = import_provider(package)
81
-
82
- if hasattr(ctx.obj, "config"):
83
- # Configure the provider so that it knows where the conda environments are
84
- provider.configure(ctx.obj.config)
85
-
86
- # Wrap each diagnostics in the provider with a celery tasks
87
- register_celery_tasks(celery_app, provider)
88
- queue = provider.slug
89
- else:
90
- # This might need some tweaking in later PRs to pull in the appropriate tasks
91
- import climate_ref_celery.worker_tasks # noqa: F401
92
-
93
- queue = "celery"
94
-
95
- argv = ["worker", "-E", f"--loglevel={loglevel}", f"--queues={queue}", *(extra_args or [])]
96
- celery_app.worker_main(argv=argv)
97
-
98
-
99
- @app.command()
100
- def list_config() -> None:
101
- """
102
- List the celery configuration
103
- """
104
- celery_app = create_celery_app("climate_ref_celery")
105
-
106
- print(celery_app.conf.humanize())
107
-
108
-
109
- if __name__ == "__main__": # pragma: no cover
110
- app()
@@ -1,124 +0,0 @@
1
- import pytest
2
- from climate_ref_celery.cli import app
3
- from typer.testing import CliRunner
4
-
5
- from climate_ref_core.providers import DiagnosticProvider
6
-
7
- runner = CliRunner()
8
-
9
-
10
- def test_cli_help():
11
- result = runner.invoke(app, ["--help"])
12
- assert result.exit_code == 0
13
-
14
-
15
- @pytest.fixture
16
- def mock_create_celery_app(mocker):
17
- return mocker.patch("climate_ref_celery.cli.create_celery_app")
18
-
19
-
20
- @pytest.fixture
21
- def mock_register_celery_tasks(mocker):
22
- return mocker.patch("climate_ref_celery.cli.register_celery_tasks")
23
-
24
-
25
- def test_start_worker_success(mocker, mock_create_celery_app, mock_register_celery_tasks):
26
- mock_celery_app = mock_create_celery_app.return_value
27
- mock_provider = mocker.MagicMock(spec=DiagnosticProvider)
28
- mock_provider.slug = "example"
29
-
30
- mock_import_module = mocker.patch(
31
- "importlib.import_module", return_value=mocker.Mock(provider=mock_provider)
32
- )
33
-
34
- result = runner.invoke(app, ["start-worker", "--package", "test_package"])
35
-
36
- assert result.exit_code == 0
37
- mock_import_module.assert_called_once_with("test_package")
38
- mock_register_celery_tasks.assert_called_once_with(mock_create_celery_app.return_value, mock_provider)
39
- mock_celery_app.worker_main.assert_called_once_with(
40
- argv=["worker", "-E", "--loglevel=info", "--queues=example"]
41
- )
42
-
43
-
44
- def test_start_core_worker_success(mock_create_celery_app, mock_register_celery_tasks):
45
- mock_celery_app = mock_create_celery_app.return_value
46
-
47
- result = runner.invoke(app, ["start-worker"])
48
-
49
- assert result.exit_code == 0
50
- mock_celery_app.worker_main.assert_called_once_with(
51
- argv=["worker", "-E", "--loglevel=info", "--queues=celery"]
52
- )
53
-
54
-
55
- def test_start_worker_success_extra_args(mocker, mock_create_celery_app, mock_register_celery_tasks):
56
- mock_worker_main = mock_create_celery_app.return_value
57
- mock_provider = mocker.MagicMock(spec=DiagnosticProvider)
58
- mock_provider.slug = "example"
59
-
60
- mocker.patch("importlib.import_module", return_value=mocker.Mock(provider=mock_provider))
61
-
62
- result = runner.invoke(
63
- app,
64
- [
65
- "start-worker",
66
- "--loglevel",
67
- "error",
68
- "--package",
69
- "test_package",
70
- "--",
71
- "--extra-args",
72
- "--concurrency=2",
73
- ],
74
- )
75
-
76
- assert result.exit_code == 0, result.output
77
- mock_worker_main.worker_main.assert_called_once_with(
78
- argv=["worker", "-E", "--loglevel=error", "--queues=example", "--extra-args", "--concurrency=2"]
79
- )
80
-
81
-
82
- def test_start_worker_package_not_found(mocker, mock_create_celery_app):
83
- mock_import_module = mocker.patch("importlib.import_module", side_effect=ModuleNotFoundError)
84
-
85
- result = runner.invoke(app, ["start-worker", "--package", "missing_package"])
86
-
87
- assert result.exit_code == 1
88
- assert "Package 'missing_package' not found" in result.output
89
- mock_create_celery_app.assert_called_once_with("climate_ref_celery")
90
- mock_import_module.assert_called_once_with("missing_package")
91
-
92
-
93
- def test_start_worker_missing_provider(mocker, mock_create_celery_app):
94
- mock_module = mocker.Mock()
95
- del mock_module.provider
96
- mock_import_module = mocker.patch("importlib.import_module", return_value=mock_module)
97
-
98
- result = runner.invoke(app, ["start-worker", "--package", "test_package"])
99
-
100
- assert result.exit_code == 1, result.output
101
- assert "The package must define a 'provider' attribute" in result.output
102
- mock_import_module.assert_called_once_with("test_package")
103
-
104
-
105
- def test_start_worker_incorrect_provider(mocker, mock_create_celery_app):
106
- # Not a DiagnosticProvider
107
- mock_provider = mocker.Mock()
108
-
109
- mock_import_module = mocker.patch(
110
- "importlib.import_module", return_value=mocker.Mock(provider=mock_provider)
111
- )
112
-
113
- result = runner.invoke(app, ["start-worker", "--package", "test_package"])
114
-
115
- assert result.exit_code == 1, result.output
116
- assert "Expected DiagnosticProvider, got <class 'unittest.mock.Mock'>" in result.output
117
- mock_import_module.assert_called_once_with("test_package")
118
-
119
-
120
- def test_list_config():
121
- result = runner.invoke(app, ["list-config"])
122
-
123
- assert result.exit_code == 0, result.output
124
- assert "broker_url: 'redis://localhost:6379/1'" in result.stdout