climate-ref-celery 0.6.3__py3-none-any.whl → 0.6.5__py3-none-any.whl

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.
climate_ref_celery/cli.py CHANGED
@@ -3,11 +3,15 @@ Managing remote celery workers
3
3
 
4
4
  This module is used to manage remote execution workers for the Climate REF project.
5
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.
6
8
  """
7
9
 
8
- import importlib
10
+ import importlib.metadata
11
+ from warnings import warn
9
12
 
10
13
  import typer
14
+ from loguru import logger
11
15
 
12
16
  from climate_ref_celery.app import create_celery_app
13
17
  from climate_ref_celery.tasks import register_celery_tasks
@@ -16,14 +20,32 @@ from climate_ref_core.providers import DiagnosticProvider
16
20
  app = typer.Typer(help=__doc__)
17
21
 
18
22
 
19
- def import_provider(provider_package: str) -> DiagnosticProvider:
23
+ def import_provider(provider_name: str) -> DiagnosticProvider:
20
24
  """
21
- Import the provider from a given package.
25
+ Import the provider using the name of a registered provider.
22
26
 
23
27
  Parameters
24
28
  ----------
25
- provider_package:
26
- The package to import the provider from
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.
27
49
 
28
50
  Raises
29
51
  ------
@@ -37,18 +59,37 @@ def import_provider(provider_package: str) -> DiagnosticProvider:
37
59
  :
38
60
  The provider instance
39
61
  """
40
- try:
41
- imp = importlib.import_module(provider_package.replace("-", "_"))
42
- except ModuleNotFoundError:
43
- typer.echo(f"Package '{provider_package}' not found")
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
+ )
44
77
  raise typer.Abort()
45
78
 
46
79
  # Get the provider from the provider_package
47
80
  try:
48
- provider = imp.provider
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()
49
86
  except AttributeError:
50
- typer.echo("The package must define a 'provider' attribute")
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
+ )
51
91
  raise typer.Abort()
92
+
52
93
  if not isinstance(provider, DiagnosticProvider):
53
94
  typer.echo(f"Expected DiagnosticProvider, got {type(provider)}")
54
95
  raise typer.Abort()
@@ -59,40 +100,61 @@ def import_provider(provider_package: str) -> DiagnosticProvider:
59
100
  def start_worker(
60
101
  ctx: typer.Context,
61
102
  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),
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),
63
109
  extra_args: list[str] = typer.Argument(None, help="Additional arguments for the worker"),
64
110
  ) -> None:
65
111
  """
66
- Start a Celery worker for the given package.
112
+ Start a Celery worker for the given provider.
67
113
 
68
114
  A celery worker enables the execution of tasks in the background on multiple different nodes.
69
115
  This worker will register a celery task for each diagnostic in the provider.
70
116
  The worker tasks can be executed by sending a celery task with the name
71
117
  '{package_slug}_{diagnostic_slug}'.
72
118
 
73
- The package must define a 'provider' variable that is an instance of 'ref_core.DiagnosticProvider'.
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).
74
122
  """
75
123
  # Create a new celery app
76
124
  celery_app = create_celery_app("climate_ref_celery")
77
125
 
78
126
  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
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)
89
151
  else:
90
152
  # This might need some tweaking in later PRs to pull in the appropriate tasks
91
153
  import climate_ref_celery.worker_tasks # noqa: F401
92
154
 
93
- queue = "celery"
155
+ queues.append("celery")
94
156
 
95
- argv = ["worker", "-E", f"--loglevel={loglevel}", f"--queues={queue}", *(extra_args or [])]
157
+ argv = ["worker", "-E", f"--loglevel={loglevel}", f"--queues={','.join(queues)}", *(extra_args or [])]
96
158
  celery_app.worker_main(argv=argv)
97
159
 
98
160
 
@@ -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
  climate_ref_celery/__init__.py,sha256=f-dS4JxMLmTCobZtftGDi6bUbtYCv_9iWqxSpC74tWw,129
2
2
  climate_ref_celery/app.py,sha256=KtDX8-6cye9RzJL5rXbJH1Us_pGYONawYAkOnxTcBmI,1527
3
- climate_ref_celery/cli.py,sha256=7eQ-ubng-dGQoZqSmLAUeM04Ucr2lOm772hzAlAeiD8,3376
3
+ climate_ref_celery/cli.py,sha256=7cQjB836xIuQ8flwnh8_gJmoVJUcHhqwcaH5d1N9Yg4,6022
4
4
  climate_ref_celery/executor.py,sha256=Qsx1bvM_-H3eMeg5VsJAV6ghHuGK3WnYyIRAd0-1DG4,4764
5
5
  climate_ref_celery/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  climate_ref_celery/tasks.py,sha256=6Ra2H48edbcklsf6pAj1JNfzi67n6WQCUXJs-tloE80,1941
@@ -9,9 +9,9 @@ climate_ref_celery/celeryconf/__init__.py,sha256=xKcHhnECGZAxrjnMtfaPBU_vgqwRbRL
9
9
  climate_ref_celery/celeryconf/base.py,sha256=uXaz9S_1hCdwKpvyWBDWE69YVIXHD3Ar7egNKLk9Gx8,484
10
10
  climate_ref_celery/celeryconf/dev.py,sha256=um7hvEhlTBfwey9ler_vAHAln_DrpqAP3fbbO2wdmUQ,217
11
11
  climate_ref_celery/celeryconf/prod.py,sha256=HyGTfwTZD8MjIkfkb3qyfzjjTPE9sKAoXNq3fJDFO9Q,231
12
- climate_ref_celery-0.6.3.dist-info/METADATA,sha256=BdXzmcTQzA-VNHUabapu7rLnw5Z-GIUxRqO2NZPTgIk,3693
13
- climate_ref_celery-0.6.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
- climate_ref_celery-0.6.3.dist-info/entry_points.txt,sha256=U9b-T6EpLV3ZXmHUpEzp8x5TZnCjQ1ynncIkFMwDuPE,58
15
- climate_ref_celery-0.6.3.dist-info/licenses/LICENCE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
- climate_ref_celery-0.6.3.dist-info/licenses/NOTICE,sha256=4qTlax9aX2-mswYJuVrLqJ9jK1IkN5kSBqfVvYLF3Ws,128
17
- climate_ref_celery-0.6.3.dist-info/RECORD,,
12
+ climate_ref_celery-0.6.5.dist-info/METADATA,sha256=wcuvkMCp34DO5QlM_RZ9FggGlBsZNcasCTIyer_A_wE,3693
13
+ climate_ref_celery-0.6.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
+ climate_ref_celery-0.6.5.dist-info/entry_points.txt,sha256=U9b-T6EpLV3ZXmHUpEzp8x5TZnCjQ1ynncIkFMwDuPE,58
15
+ climate_ref_celery-0.6.5.dist-info/licenses/LICENCE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
+ climate_ref_celery-0.6.5.dist-info/licenses/NOTICE,sha256=4qTlax9aX2-mswYJuVrLqJ9jK1IkN5kSBqfVvYLF3Ws,128
17
+ climate_ref_celery-0.6.5.dist-info/RECORD,,