anemoi-datasets 0.5.16__py3-none-any.whl → 0.5.17__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.
Files changed (155) hide show
  1. anemoi/datasets/__init__.py +4 -1
  2. anemoi/datasets/__main__.py +12 -2
  3. anemoi/datasets/_version.py +9 -4
  4. anemoi/datasets/commands/cleanup.py +17 -2
  5. anemoi/datasets/commands/compare.py +18 -2
  6. anemoi/datasets/commands/copy.py +196 -14
  7. anemoi/datasets/commands/create.py +50 -7
  8. anemoi/datasets/commands/finalise-additions.py +17 -2
  9. anemoi/datasets/commands/finalise.py +17 -2
  10. anemoi/datasets/commands/init-additions.py +17 -2
  11. anemoi/datasets/commands/init.py +16 -2
  12. anemoi/datasets/commands/inspect.py +283 -62
  13. anemoi/datasets/commands/load-additions.py +16 -2
  14. anemoi/datasets/commands/load.py +16 -2
  15. anemoi/datasets/commands/patch.py +17 -2
  16. anemoi/datasets/commands/publish.py +17 -2
  17. anemoi/datasets/commands/scan.py +31 -3
  18. anemoi/datasets/compute/recentre.py +47 -11
  19. anemoi/datasets/create/__init__.py +612 -85
  20. anemoi/datasets/create/check.py +142 -20
  21. anemoi/datasets/create/chunks.py +64 -4
  22. anemoi/datasets/create/config.py +185 -21
  23. anemoi/datasets/create/filter.py +50 -0
  24. anemoi/datasets/create/filters/__init__.py +33 -0
  25. anemoi/datasets/create/filters/empty.py +37 -0
  26. anemoi/datasets/create/filters/legacy.py +93 -0
  27. anemoi/datasets/create/filters/noop.py +37 -0
  28. anemoi/datasets/create/filters/orog_to_z.py +58 -0
  29. anemoi/datasets/create/{functions/filters → filters}/pressure_level_relative_humidity_to_specific_humidity.py +33 -10
  30. anemoi/datasets/create/{functions/filters → filters}/pressure_level_specific_humidity_to_relative_humidity.py +32 -8
  31. anemoi/datasets/create/filters/rename.py +205 -0
  32. anemoi/datasets/create/{functions/filters → filters}/rotate_winds.py +43 -28
  33. anemoi/datasets/create/{functions/filters → filters}/single_level_dewpoint_to_relative_humidity.py +32 -9
  34. anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_dewpoint.py +33 -9
  35. anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_specific_humidity.py +55 -7
  36. anemoi/datasets/create/{functions/filters → filters}/single_level_specific_humidity_to_relative_humidity.py +98 -37
  37. anemoi/datasets/create/filters/speeddir_to_uv.py +95 -0
  38. anemoi/datasets/create/{functions/filters → filters}/sum.py +24 -27
  39. anemoi/datasets/create/filters/transform.py +53 -0
  40. anemoi/datasets/create/{functions/filters → filters}/unrotate_winds.py +27 -18
  41. anemoi/datasets/create/filters/uv_to_speeddir.py +94 -0
  42. anemoi/datasets/create/{functions/filters → filters}/wz_to_w.py +51 -33
  43. anemoi/datasets/create/input/__init__.py +76 -5
  44. anemoi/datasets/create/input/action.py +149 -13
  45. anemoi/datasets/create/input/concat.py +81 -10
  46. anemoi/datasets/create/input/context.py +39 -4
  47. anemoi/datasets/create/input/data_sources.py +72 -6
  48. anemoi/datasets/create/input/empty.py +21 -3
  49. anemoi/datasets/create/input/filter.py +60 -12
  50. anemoi/datasets/create/input/function.py +154 -37
  51. anemoi/datasets/create/input/join.py +86 -14
  52. anemoi/datasets/create/input/misc.py +67 -17
  53. anemoi/datasets/create/input/pipe.py +33 -6
  54. anemoi/datasets/create/input/repeated_dates.py +189 -41
  55. anemoi/datasets/create/input/result.py +202 -87
  56. anemoi/datasets/create/input/step.py +119 -22
  57. anemoi/datasets/create/input/template.py +100 -13
  58. anemoi/datasets/create/input/trace.py +62 -7
  59. anemoi/datasets/create/patch.py +52 -4
  60. anemoi/datasets/create/persistent.py +134 -17
  61. anemoi/datasets/create/size.py +15 -1
  62. anemoi/datasets/create/source.py +51 -0
  63. anemoi/datasets/create/sources/__init__.py +36 -0
  64. anemoi/datasets/create/{functions/sources → sources}/accumulations.py +296 -30
  65. anemoi/datasets/create/{functions/sources → sources}/constants.py +27 -2
  66. anemoi/datasets/create/{functions/sources → sources}/eccc_fstd.py +7 -3
  67. anemoi/datasets/create/sources/empty.py +37 -0
  68. anemoi/datasets/create/{functions/sources → sources}/forcings.py +25 -1
  69. anemoi/datasets/create/sources/grib.py +297 -0
  70. anemoi/datasets/create/{functions/sources → sources}/hindcasts.py +38 -4
  71. anemoi/datasets/create/sources/legacy.py +93 -0
  72. anemoi/datasets/create/{functions/sources → sources}/mars.py +168 -20
  73. anemoi/datasets/create/sources/netcdf.py +42 -0
  74. anemoi/datasets/create/sources/opendap.py +43 -0
  75. anemoi/datasets/create/{functions/sources/__init__.py → sources/patterns.py} +35 -4
  76. anemoi/datasets/create/sources/recentre.py +150 -0
  77. anemoi/datasets/create/{functions/sources → sources}/source.py +27 -5
  78. anemoi/datasets/create/{functions/sources → sources}/tendencies.py +64 -7
  79. anemoi/datasets/create/sources/xarray.py +92 -0
  80. anemoi/datasets/create/sources/xarray_kerchunk.py +36 -0
  81. anemoi/datasets/create/sources/xarray_support/README.md +1 -0
  82. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/__init__.py +109 -8
  83. anemoi/datasets/create/sources/xarray_support/coordinates.py +442 -0
  84. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/field.py +94 -16
  85. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/fieldlist.py +90 -25
  86. anemoi/datasets/create/sources/xarray_support/flavour.py +1036 -0
  87. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/grid.py +92 -31
  88. anemoi/datasets/create/sources/xarray_support/metadata.py +395 -0
  89. anemoi/datasets/create/sources/xarray_support/patch.py +91 -0
  90. anemoi/datasets/create/sources/xarray_support/time.py +391 -0
  91. anemoi/datasets/create/sources/xarray_support/variable.py +331 -0
  92. anemoi/datasets/create/sources/xarray_zarr.py +41 -0
  93. anemoi/datasets/create/{functions/sources → sources}/zenodo.py +34 -5
  94. anemoi/datasets/create/statistics/__init__.py +233 -44
  95. anemoi/datasets/create/statistics/summary.py +52 -6
  96. anemoi/datasets/create/testing.py +76 -0
  97. anemoi/datasets/create/{functions/filters/noop.py → typing.py} +6 -3
  98. anemoi/datasets/create/utils.py +97 -6
  99. anemoi/datasets/create/writer.py +26 -4
  100. anemoi/datasets/create/zarr.py +170 -23
  101. anemoi/datasets/data/__init__.py +51 -4
  102. anemoi/datasets/data/complement.py +191 -40
  103. anemoi/datasets/data/concat.py +141 -16
  104. anemoi/datasets/data/dataset.py +552 -61
  105. anemoi/datasets/data/debug.py +197 -26
  106. anemoi/datasets/data/ensemble.py +93 -8
  107. anemoi/datasets/data/fill_missing.py +165 -18
  108. anemoi/datasets/data/forwards.py +428 -56
  109. anemoi/datasets/data/grids.py +323 -97
  110. anemoi/datasets/data/indexing.py +112 -19
  111. anemoi/datasets/data/interpolate.py +92 -12
  112. anemoi/datasets/data/join.py +158 -19
  113. anemoi/datasets/data/masked.py +129 -15
  114. anemoi/datasets/data/merge.py +137 -23
  115. anemoi/datasets/data/misc.py +172 -16
  116. anemoi/datasets/data/missing.py +233 -29
  117. anemoi/datasets/data/rescale.py +111 -10
  118. anemoi/datasets/data/select.py +168 -26
  119. anemoi/datasets/data/statistics.py +67 -6
  120. anemoi/datasets/data/stores.py +149 -64
  121. anemoi/datasets/data/subset.py +159 -25
  122. anemoi/datasets/data/unchecked.py +168 -57
  123. anemoi/datasets/data/xy.py +168 -25
  124. anemoi/datasets/dates/__init__.py +191 -16
  125. anemoi/datasets/dates/groups.py +189 -47
  126. anemoi/datasets/grids.py +270 -31
  127. anemoi/datasets/testing.py +28 -1
  128. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/METADATA +9 -6
  129. anemoi_datasets-0.5.17.dist-info/RECORD +137 -0
  130. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/WHEEL +1 -1
  131. anemoi/datasets/create/functions/__init__.py +0 -66
  132. anemoi/datasets/create/functions/filters/__init__.py +0 -9
  133. anemoi/datasets/create/functions/filters/empty.py +0 -17
  134. anemoi/datasets/create/functions/filters/orog_to_z.py +0 -58
  135. anemoi/datasets/create/functions/filters/rename.py +0 -79
  136. anemoi/datasets/create/functions/filters/speeddir_to_uv.py +0 -78
  137. anemoi/datasets/create/functions/filters/uv_to_speeddir.py +0 -56
  138. anemoi/datasets/create/functions/sources/empty.py +0 -15
  139. anemoi/datasets/create/functions/sources/grib.py +0 -150
  140. anemoi/datasets/create/functions/sources/netcdf.py +0 -15
  141. anemoi/datasets/create/functions/sources/opendap.py +0 -15
  142. anemoi/datasets/create/functions/sources/recentre.py +0 -60
  143. anemoi/datasets/create/functions/sources/xarray/coordinates.py +0 -255
  144. anemoi/datasets/create/functions/sources/xarray/flavour.py +0 -472
  145. anemoi/datasets/create/functions/sources/xarray/metadata.py +0 -148
  146. anemoi/datasets/create/functions/sources/xarray/patch.py +0 -44
  147. anemoi/datasets/create/functions/sources/xarray/time.py +0 -177
  148. anemoi/datasets/create/functions/sources/xarray/variable.py +0 -188
  149. anemoi/datasets/create/functions/sources/xarray_kerchunk.py +0 -42
  150. anemoi/datasets/create/functions/sources/xarray_zarr.py +0 -15
  151. anemoi/datasets/utils/fields.py +0 -47
  152. anemoi_datasets-0.5.16.dist-info/RECORD +0 -129
  153. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/entry_points.txt +0 -0
  154. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info/licenses}/LICENSE +0 -0
  155. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,53 @@
1
+ # (C) Copyright 2025 Anemoi contributors.
2
+ #
3
+ # This software is licensed under the terms of the Apache Licence Version 2.0
4
+ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5
+ #
6
+ # In applying this licence, ECMWF does not waive the privileges and immunities
7
+ # granted to it by virtue of its status as an intergovernmental organisation
8
+ # nor does it submit to any jurisdiction.
9
+
10
+ from typing import Any
11
+ from typing import Dict
12
+
13
+ import earthkit.data as ekd
14
+
15
+ from ..filter import Filter
16
+
17
+
18
+ class TransformFilter(Filter):
19
+ """Calls filters from anemoi.transform.filters
20
+
21
+ Parameters
22
+ ----------
23
+ context : Any
24
+ The context in which the filter is created.
25
+ name : str
26
+ The name of the filter.
27
+ config : Dict[str, Any]
28
+ The configuration for the filter.
29
+ """
30
+
31
+ def __init__(self, context: Any, name: str, config: Dict[str, Any]) -> None:
32
+
33
+ from anemoi.transform.filters import create_filter
34
+
35
+ self.name = name
36
+ self.transform_filter = create_filter(self, config)
37
+
38
+ def execute(self, context: Any, input: ekd.FieldList) -> ekd.FieldList:
39
+ """Execute the transformation filter.
40
+
41
+ Parameters
42
+ ----------
43
+ context : Any
44
+ The context in which the execution occurs.
45
+ input : ekd.FieldList
46
+ The input data to be transformed.
47
+
48
+ Returns
49
+ -------
50
+ ekd.FieldList
51
+ The transformed data.
52
+ """
53
+ return self.transform_filter.forward(input)
@@ -7,29 +7,38 @@
7
7
  # granted to it by virtue of its status as an intergovernmental organisation
8
8
  # nor does it submit to any jurisdiction.
9
9
 
10
-
11
10
  from collections import defaultdict
11
+ from typing import Any
12
12
 
13
- # import numpy as np
14
- from earthkit.data.indexing.fieldlist import FieldArray
13
+ import earthkit.data as ekd
14
+ from anemoi.transform.fields import new_field_from_numpy
15
+ from anemoi.transform.fields import new_fieldlist_from_list
15
16
  from earthkit.geo.rotate import unrotate_vector
16
17
 
18
+ from .legacy import legacy_filter
17
19
 
18
- class NewDataField:
19
- def __init__(self, field, data):
20
- self.field = field
21
- self.data = data
22
-
23
- def to_numpy(self, *args, **kwargs):
24
- return self.data
25
20
 
26
- def __getattr__(self, name):
27
- return getattr(self.field, name)
21
+ @legacy_filter(__file__)
22
+ def execute(context: Any, input: ekd.FieldList, u: str, v: str) -> ekd.FieldList:
23
+ """Unrotate the wind components of a GRIB file.
28
24
 
25
+ Parameters
26
+ ----------
27
+ context : Any
28
+ The execution context.
29
+ input : List[Any]
30
+ The list of input fields.
31
+ u : str
32
+ The parameter name for the u-component of the wind.
33
+ v : str
34
+ The parameter name for the v-component of the wind.
29
35
 
30
- def execute(context, input, u, v):
31
- """Unrotate the wind components of a GRIB file."""
32
- result = FieldArray()
36
+ Returns
37
+ -------
38
+ ekd.FieldList
39
+ The resulting field array with unrotated wind components.
40
+ """
41
+ result = []
33
42
 
34
43
  wind_params = (u, v)
35
44
  wind_pairs = defaultdict(dict)
@@ -72,10 +81,10 @@ def execute(context, input, u, v):
72
81
  lon_unrotated=raw_longs,
73
82
  )
74
83
 
75
- result.append(NewDataField(x, u_new))
76
- result.append(NewDataField(y, v_new))
84
+ result.append(new_field_from_numpy(x, u_new))
85
+ result.append(new_field_from_numpy(y, v_new))
77
86
 
78
- return result
87
+ return new_fieldlist_from_list(result)
79
88
 
80
89
 
81
90
  if __name__ == "__main__":
@@ -0,0 +1,94 @@
1
+ # (C) Copyright 2024 Anemoi contributors.
2
+ #
3
+ # This software is licensed under the terms of the Apache Licence Version 2.0
4
+ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5
+ #
6
+ # In applying this licence, ECMWF does not waive the privileges and immunities
7
+ # granted to it by virtue of its status as an intergovernmental organisation
8
+ # nor does it submit to any jurisdiction.
9
+
10
+ """This module provides a function to convert u and v wind components to wind speed and direction."""
11
+
12
+ from collections import defaultdict
13
+ from typing import Any
14
+ from typing import List
15
+
16
+ import earthkit.data as ekd
17
+ import numpy as np
18
+ from anemoi.transform.fields import new_field_from_numpy
19
+ from anemoi.transform.fields import new_fieldlist_from_list
20
+ from earthkit.meteo.wind.array import xy_to_polar
21
+
22
+ from .legacy import legacy_filter
23
+
24
+
25
+ @legacy_filter(__file__)
26
+ def execute(
27
+ context: Any,
28
+ input: List[Any],
29
+ u_component: str,
30
+ v_component: str,
31
+ wind_speed: str,
32
+ wind_dir: str,
33
+ in_radians: bool = False,
34
+ ) -> ekd.FieldList:
35
+ """Converts u and v wind components to wind speed and direction.
36
+
37
+ Parameters
38
+ ----------
39
+ context : Any
40
+ The context in which the function is executed.
41
+ input : List[Any]
42
+ List of input fields containing wind components.
43
+ u_component : str
44
+ The name of the u component field.
45
+ v_component : str
46
+ The name of the v component field.
47
+ wind_speed : str
48
+ The name of the wind speed field to be created.
49
+ wind_dir : str
50
+ The name of the wind direction field to be created.
51
+ in_radians : bool, optional
52
+ If True, the wind direction is returned in radians. Default is False.
53
+
54
+ Returns
55
+ -------
56
+ ekd.FieldList
57
+ A FieldArray containing the wind speed and direction fields.
58
+ """
59
+ result = []
60
+
61
+ wind_params = (u_component, v_component)
62
+ wind_pairs = defaultdict(dict)
63
+
64
+ for f in input:
65
+ key = f.metadata(namespace="mars")
66
+ param = key.pop("param")
67
+
68
+ if param not in wind_params:
69
+ result.append(f)
70
+ continue
71
+
72
+ key = tuple(key.items())
73
+
74
+ if param in wind_pairs[key]:
75
+ raise ValueError(f"Duplicate wind component {param} for {key}")
76
+
77
+ wind_pairs[key][param] = f
78
+
79
+ for _, pairs in wind_pairs.items():
80
+ if len(pairs) != 2:
81
+ raise ValueError("Missing wind component")
82
+
83
+ u = pairs[u_component]
84
+ v = pairs[v_component]
85
+
86
+ # assert speed.grid_mapping == dir.grid_mapping
87
+ magnitude, direction = xy_to_polar(u.to_numpy(flatten=True), v.to_numpy(flatten=True))
88
+ if in_radians:
89
+ direction = np.deg2rad(direction)
90
+
91
+ result.append(new_field_from_numpy(u, magnitude, params=wind_speed))
92
+ result.append(new_field_from_numpy(v, direction, params=wind_dir))
93
+
94
+ return new_fieldlist_from_list(result)
@@ -7,37 +7,39 @@
7
7
  # granted to it by virtue of its status as an intergovernmental organisation
8
8
  # nor does it submit to any jurisdiction.
9
9
 
10
-
11
10
  from collections import defaultdict
12
-
13
- from earthkit.data.indexing.fieldlist import FieldArray
14
-
15
-
16
- class NewDataField:
17
- def __init__(self, field, data, new_name):
18
- self.field = field
19
- self.data = data
20
- self.new_name = new_name
21
-
22
- def to_numpy(self, *args, **kwargs):
23
- return self.data
24
-
25
- def metadata(self, key=None, **kwargs):
26
- if key is None:
27
- return self.field.metadata(**kwargs)
28
-
29
- value = self.field.metadata(key, **kwargs)
30
- if key == "param":
31
- return self.new_name
32
- return value
33
-
34
- def __getattr__(self, name):
35
- return getattr(self.field, name)
36
-
37
-
38
- def execute(context, input, wz, t, w="w"):
39
- """Convert geometric vertical velocity (m/s) to vertical velocity (Pa / s)"""
40
- result = FieldArray()
11
+ from typing import Any
12
+
13
+ import earthkit.data as ekd
14
+ from anemoi.transform.fields import new_field_from_numpy
15
+ from anemoi.transform.fields import new_fieldlist_from_list
16
+
17
+ from .legacy import legacy_filter
18
+
19
+
20
+ @legacy_filter(__file__)
21
+ def execute(context: Any, input: ekd.FieldList, wz: str, t: str, w: str = "w") -> ekd.FieldList:
22
+ """Convert geometric vertical velocity (m/s) to vertical velocity (Pa / s).
23
+
24
+ Parameters
25
+ ----------
26
+ context : Any
27
+ The context for the execution.
28
+ input : List[Any]
29
+ The list of input fields.
30
+ wz : str
31
+ The parameter name for geometric vertical velocity.
32
+ t : str
33
+ The parameter name for temperature.
34
+ w : str, optional
35
+ The parameter name for vertical velocity. Defaults to "w".
36
+
37
+ Returns
38
+ -------
39
+ ekd.FieldList
40
+ The resulting FieldArray with converted vertical velocity fields.
41
+ """
42
+ result = []
41
43
 
42
44
  params = (wz, t)
43
45
  pairs = defaultdict(dict)
@@ -67,12 +69,28 @@ def execute(context, input, wz, t, w="w"):
67
69
  pressure = keys[4][1] * 100 # TODO: REMOVE HARDCODED INDICES
68
70
 
69
71
  w_pl = wz_to_w(wz_pl, t_pl, pressure)
70
- result.append(NewDataField(values[wz], w_pl, w))
72
+ result.append(new_field_from_numpy(values[wz], w_pl, param=w))
73
+
74
+ return new_fieldlist_from_list(result)
75
+
71
76
 
72
- return result
77
+ def wz_to_w(wz: Any, t: Any, pressure: float) -> Any:
78
+ """Convert geometric vertical velocity (m/s) to vertical velocity (Pa / s).
73
79
 
80
+ Parameters
81
+ ----------
82
+ wz : Any
83
+ The geometric vertical velocity data.
84
+ t : Any
85
+ The temperature data.
86
+ pressure : float
87
+ The pressure value.
74
88
 
75
- def wz_to_w(wz, t, pressure):
89
+ Returns
90
+ -------
91
+ Any
92
+ The vertical velocity data in Pa / s.
93
+ """
76
94
  g = 9.81
77
95
  Rd = 287.058
78
96
 
@@ -9,14 +9,37 @@
9
9
 
10
10
  import logging
11
11
  from copy import deepcopy
12
+ from typing import Any
13
+ from typing import Union
14
+
15
+ from anemoi.datasets.dates.groups import GroupOfDates
12
16
 
13
17
  from .trace import trace_select
14
18
 
15
19
  LOG = logging.getLogger(__name__)
16
20
 
17
21
 
22
+ class Context:
23
+ """Context for building input data."""
24
+
25
+ pass
26
+
27
+
18
28
  class InputBuilder:
19
- def __init__(self, config, data_sources, **kwargs):
29
+ """Builder class for creating input data from configuration and data sources."""
30
+
31
+ def __init__(self, config: dict, data_sources: Union[dict, list], **kwargs: Any) -> None:
32
+ """Initialize the InputBuilder.
33
+
34
+ Parameters
35
+ ----------
36
+ config : dict
37
+ Configuration dictionary.
38
+ data_sources : Union[dict, list]
39
+ Data sources.
40
+ **kwargs : Any
41
+ Additional keyword arguments.
42
+ """
20
43
  self.kwargs = kwargs
21
44
 
22
45
  config = deepcopy(config)
@@ -31,7 +54,19 @@ class InputBuilder:
31
54
  self.action_path = ["input"]
32
55
 
33
56
  @trace_select
34
- def select(self, group_of_dates):
57
+ def select(self, group_of_dates: GroupOfDates) -> Any:
58
+ """Select data based on the group of dates.
59
+
60
+ Parameters
61
+ ----------
62
+ group_of_dates : GroupOfDates
63
+ Group of dates to select data for.
64
+
65
+ Returns
66
+ -------
67
+ Any
68
+ Selected data.
69
+ """
35
70
  from .action import ActionContext
36
71
  from .action import action_factory
37
72
 
@@ -40,7 +75,14 @@ class InputBuilder:
40
75
  action = action_factory(self.config, context, self.action_path)
41
76
  return action.select(group_of_dates)
42
77
 
43
- def __repr__(self):
78
+ def __repr__(self) -> str:
79
+ """Return a string representation of the InputBuilder.
80
+
81
+ Returns
82
+ -------
83
+ str
84
+ String representation.
85
+ """
44
86
  from .action import ActionContext
45
87
  from .action import action_factory
46
88
 
@@ -48,8 +90,37 @@ class InputBuilder:
48
90
  a = action_factory(self.config, context, self.action_path)
49
91
  return repr(a)
50
92
 
51
- def _trace_select(self, group_of_dates):
93
+ def _trace_select(self, group_of_dates: GroupOfDates) -> str:
94
+ """Trace the select operation.
95
+
96
+ Parameters
97
+ ----------
98
+ group_of_dates : GroupOfDates
99
+ Group of dates to select data for.
100
+
101
+ Returns
102
+ -------
103
+ str
104
+ Trace string.
105
+ """
52
106
  return f"InputBuilder({group_of_dates})"
53
107
 
54
108
 
55
- build_input = InputBuilder
109
+ def build_input(config: dict, data_sources: Union[dict, list], **kwargs: Any) -> InputBuilder:
110
+ """Build an InputBuilder instance.
111
+
112
+ Parameters
113
+ ----------
114
+ config : dict
115
+ Configuration dictionary.
116
+ data_sources : Union[dict, list]
117
+ Data sources.
118
+ **kwargs : Any
119
+ Additional keyword arguments.
120
+
121
+ Returns
122
+ -------
123
+ InputBuilder
124
+ An instance of InputBuilder.
125
+ """
126
+ return InputBuilder(config, data_sources, **kwargs)
@@ -9,17 +9,49 @@
9
9
 
10
10
  import logging
11
11
  from copy import deepcopy
12
+ from typing import Any
13
+ from typing import Dict
14
+ from typing import List
12
15
 
13
16
  from earthkit.data.core.order import build_remapping
14
17
 
18
+ from ...dates.groups import GroupOfDates
15
19
  from .context import Context
16
- from .misc import is_function
17
20
 
18
21
  LOG = logging.getLogger(__name__)
19
22
 
20
23
 
21
24
  class Action:
22
- def __init__(self, context, action_path, /, *args, **kwargs):
25
+ """Represents an action to be performed within a given context.
26
+
27
+ Attributes
28
+ ----------
29
+ context : ActionContext
30
+ The context in which the action exists.
31
+ kwargs : Dict[str, Any]
32
+ Additional keyword arguments.
33
+ args : Any
34
+ Additional positional arguments.
35
+ action_path : List[str]
36
+ The action path.
37
+ """
38
+
39
+ def __init__(
40
+ self, context: "ActionContext", action_path: List[str], /, *args: Any, **kwargs: Dict[str, Any]
41
+ ) -> None:
42
+ """Initialize an Action instance.
43
+
44
+ Parameters
45
+ ----------
46
+ context : ActionContext
47
+ The context in which the action exists.
48
+ action_path : List[str]
49
+ The action path.
50
+ args : Any
51
+ Additional positional arguments.
52
+ kwargs : Dict[str, Any]
53
+ Additional keyword arguments.
54
+ """
23
55
  if "args" in kwargs and "kwargs" in kwargs:
24
56
  """We have:
25
57
  args = []
@@ -38,13 +70,43 @@ class Action:
38
70
  self.action_path = action_path
39
71
 
40
72
  @classmethod
41
- def _short_str(cls, x):
73
+ def _short_str(cls, x: str) -> str:
74
+ """Shorten the string representation if it exceeds 1000 characters.
75
+
76
+ Parameters
77
+ ----------
78
+ x : str
79
+ The string to shorten.
80
+
81
+ Returns
82
+ -------
83
+ str
84
+ The shortened string.
85
+ """
42
86
  x = str(x)
43
87
  if len(x) < 1000:
44
88
  return x
45
89
  return x[:1000] + "..."
46
90
 
47
- def __repr__(self, *args, _indent_="\n", _inline_="", **kwargs):
91
+ def _repr(self, *args: Any, _indent_: str = "\n", _inline_: str = "", **kwargs: Any) -> str:
92
+ """Generate a string representation of the Action instance.
93
+
94
+ Parameters
95
+ ----------
96
+ args : Any
97
+ Additional positional arguments.
98
+ _indent_ : str, optional
99
+ The indentation string, by default "\n".
100
+ _inline_ : str, optional
101
+ The inline string, by default "".
102
+ kwargs : Any
103
+ Additional keyword arguments.
104
+
105
+ Returns
106
+ -------
107
+ str
108
+ The string representation.
109
+ """
48
110
  more = ",".join([str(a)[:5000] for a in args])
49
111
  more += ",".join([f"{k}={v}"[:5000] for k, v in kwargs.items()])
50
112
 
@@ -54,18 +116,77 @@ class Action:
54
116
  txt = txt.replace("\n", "\n ")
55
117
  return txt
56
118
 
57
- def select(self, dates, **kwargs):
119
+ def __repr__(self) -> str:
120
+ """Return the string representation of the Action instance.
121
+
122
+ Returns
123
+ -------
124
+ str
125
+ The string representation.
126
+ """
127
+ return self._repr()
128
+
129
+ def select(self, dates: object, **kwargs: Any) -> None:
130
+ """Select dates for the action.
131
+
132
+ Parameters
133
+ ----------
134
+ dates : object
135
+ The dates to select.
136
+ kwargs : Any
137
+ Additional keyword arguments.
138
+ """
58
139
  self._raise_not_implemented()
59
140
 
60
- def _raise_not_implemented(self):
141
+ def _raise_not_implemented(self) -> None:
142
+ """Raise a NotImplementedError indicating the method is not implemented."""
61
143
  raise NotImplementedError(f"Not implemented in {self.__class__.__name__}")
62
144
 
63
- def _trace_select(self, group_of_dates):
145
+ def _trace_select(self, group_of_dates: GroupOfDates) -> str:
146
+ """Trace the selection of a group of dates.
147
+
148
+ Parameters
149
+ ----------
150
+ group_of_dates : GroupOfDates
151
+ The group of dates to trace.
152
+
153
+ Returns
154
+ -------
155
+ str
156
+ The trace string.
157
+ """
64
158
  return f"{self.__class__.__name__}({group_of_dates})"
65
159
 
66
160
 
67
161
  class ActionContext(Context):
68
- def __init__(self, /, order_by, flatten_grid, remapping, use_grib_paramid):
162
+ """Represents the context in which an action is performed.
163
+
164
+ Attributes
165
+ ----------
166
+ order_by : str
167
+ The order by criteria.
168
+ flatten_grid : bool
169
+ Whether to flatten the grid.
170
+ remapping : Dict[str, Any]
171
+ The remapping configuration.
172
+ use_grib_paramid : bool
173
+ Whether to use GRIB parameter ID.
174
+ """
175
+
176
+ def __init__(self, /, order_by: str, flatten_grid: bool, remapping: Dict[str, Any], use_grib_paramid: bool) -> None:
177
+ """Initialize an ActionContext instance.
178
+
179
+ Parameters
180
+ ----------
181
+ order_by : str
182
+ The order by criteria.
183
+ flatten_grid : bool
184
+ Whether to flatten the grid.
185
+ remapping : Dict[str, Any]
186
+ The remapping configuration.
187
+ use_grib_paramid : bool
188
+ Whether to use GRIB parameter ID.
189
+ """
69
190
  super().__init__()
70
191
  self.order_by = order_by
71
192
  self.flatten_grid = flatten_grid
@@ -73,8 +194,23 @@ class ActionContext(Context):
73
194
  self.use_grib_paramid = use_grib_paramid
74
195
 
75
196
 
76
- def action_factory(config, context, action_path):
197
+ def action_factory(config: Dict[str, Any], context: ActionContext, action_path: List[str]) -> Action:
198
+ """Factory function to create an Action instance based on the configuration.
77
199
 
200
+ Parameters
201
+ ----------
202
+ config : Dict[str, Any]
203
+ The action configuration.
204
+ context : ActionContext
205
+ The context in which the action exists.
206
+ action_path : List[str]
207
+ The action path.
208
+
209
+ Returns
210
+ -------
211
+ Action
212
+ The created Action instance.
213
+ """
78
214
  from .concat import ConcatAction
79
215
  from .data_sources import DataSourcesAction
80
216
  from .function import FunctionAction
@@ -110,9 +246,9 @@ def action_factory(config, context, action_path):
110
246
  }.get(key)
111
247
 
112
248
  if cls is None:
113
- if not is_function(key, "sources"):
114
- raise ValueError(f"Unknown action '{key}' in {config}")
115
- cls = FunctionAction
116
- args = [key] + args
249
+ from ..sources import create_source
250
+
251
+ source = create_source(None, config)
252
+ return FunctionAction(context, action_path + [key], key, source)
117
253
 
118
254
  return cls(context, action_path + [key], *args, **kwargs)