yirgacheffe 1.9.3__py3-none-any.whl → 1.9.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.

Potentially problematic release.


This version of yirgacheffe might be problematic. Click here for more details.

yirgacheffe/__init__.py CHANGED
@@ -12,12 +12,13 @@ except ModuleNotFoundError:
12
12
  pyproject_data = tomllib.load(f)
13
13
  __version__ = pyproject_data["project"]["version"]
14
14
 
15
- from ._core import read_raster, read_rasters, read_shape, read_shape_like, constant, read_narrow_raster
15
+ from .layers import YirgacheffeLayer
16
+ from ._core import read_raster, read_rasters, read_shape, read_shape_like, constant, read_narrow_raster, from_array
16
17
  from .constants import WGS_84_PROJECTION
17
18
  from .window import Area, MapProjection, Window
18
19
  from ._backends.enumeration import dtype as DataType
19
20
 
20
- from ._operators import where, minumum, maximum, clip, log, log2, log10, exp, exp2, nan_to_num, isin, \
21
+ from ._operators import where, minimum, maximum, clip, log, log2, log10, exp, exp2, nan_to_num, isin, \
21
22
  floor, ceil # pylint: disable=W0611
22
23
  from ._operators import abs, round # pylint: disable=W0611,W0622
23
24
 
@@ -1,5 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  from enum import Enum
2
4
 
5
+ import numpy as np
3
6
  from osgeo import gdal
4
7
 
5
8
  class operators(Enum):
@@ -77,5 +80,31 @@ class dtype(Enum):
77
80
  return self.value
78
81
 
79
82
  @classmethod
80
- def of_gdal(cls, val):
83
+ def of_gdal(cls, val: int) -> dtype:
81
84
  return cls(val)
85
+
86
+ @classmethod
87
+ def of_array(cls, val: np.ndarray) -> dtype:
88
+ match val.dtype:
89
+ case np.float32:
90
+ return dtype.Float32
91
+ case np.float64:
92
+ return dtype.Float64
93
+ case np.int8:
94
+ return dtype.Int8
95
+ case np.int16:
96
+ return dtype.Int16
97
+ case np.int32:
98
+ return dtype.Int32
99
+ case np.int64:
100
+ return dtype.Int64
101
+ case np.uint8:
102
+ return dtype.UInt8
103
+ case np.uint16:
104
+ return dtype.UInt16
105
+ case np.uint32:
106
+ return dtype.UInt32
107
+ case np.uint64:
108
+ return dtype.UInt64
109
+ case _:
110
+ raise ValueError
yirgacheffe/_core.py CHANGED
@@ -3,20 +3,22 @@ from __future__ import annotations
3
3
  from pathlib import Path
4
4
  from typing import Sequence
5
5
 
6
+ import numpy as np
7
+
6
8
  from .layers.area import UniformAreaLayer
7
9
  from .layers.base import YirgacheffeLayer
8
10
  from .layers.constant import ConstantLayer
9
11
  from .layers.group import GroupLayer, TiledGroupLayer
10
12
  from .layers.rasters import RasterLayer
11
13
  from .layers.vectors import VectorLayer
12
- from .window import MapProjection
14
+ from .window import Area, MapProjection
13
15
  from ._backends.enumeration import dtype as DataType
14
16
 
15
17
  def read_raster(
16
18
  filename: Path | str,
17
19
  band: int = 1,
18
20
  ignore_nodata: bool = False,
19
- ) -> RasterLayer:
21
+ ) -> YirgacheffeLayer:
20
22
  """Open a raster file (e.g., GeoTIFF).
21
23
 
22
24
  Args:
@@ -38,7 +40,7 @@ def read_narrow_raster(
38
40
  filename: Path | str,
39
41
  band: int = 1,
40
42
  ignore_nodata: bool = False,
41
- ) -> RasterLayer:
43
+ ) -> YirgacheffeLayer:
42
44
  """Open a 1 pixel wide raster file as a global raster.
43
45
 
44
46
  This exists for the special use case where an area per pixel raster would have the same value per horizontal row
@@ -58,7 +60,7 @@ def read_narrow_raster(
58
60
  def read_rasters(
59
61
  filenames : Sequence[Path | str],
60
62
  tiled: bool=False
61
- ) -> GroupLayer:
63
+ ) -> YirgacheffeLayer:
62
64
  """Open a set of raster files (e.g., GeoTIFFs) as a single layer.
63
65
 
64
66
  Args:
@@ -86,7 +88,7 @@ def read_shape(
86
88
  where_filter: str | None = None,
87
89
  datatype: DataType | None = None,
88
90
  burn_value: int | float | str = 1,
89
- ) -> VectorLayer:
91
+ ) -> YirgacheffeLayer:
90
92
  """Open a polygon file (e.g., GeoJSON, GPKG, or ESRI Shape File).
91
93
 
92
94
  Args:
@@ -124,7 +126,7 @@ def read_shape_like(
124
126
  where_filter: str | None = None,
125
127
  datatype: DataType | None = None,
126
128
  burn_value: int | float | str = 1,
127
- ) -> VectorLayer:
129
+ ) -> YirgacheffeLayer:
128
130
  """Open a polygon file (e.g., GeoJSON, GPKG, or ESRI Shape File).
129
131
 
130
132
  Args:
@@ -146,7 +148,7 @@ def read_shape_like(
146
148
  burn_value,
147
149
  )
148
150
 
149
- def constant(value: int | float) -> ConstantLayer:
151
+ def constant(value: int | float) -> YirgacheffeLayer:
150
152
  """Generate a layer that has the same value in all pixels regardless of scale, projection, and area.
151
153
 
152
154
  Generally this should not be necessary unless you must have the constant as the first term in an
@@ -161,3 +163,50 @@ def constant(value: int | float) -> ConstantLayer:
161
163
  A constant layer of the provided value.
162
164
  """
163
165
  return ConstantLayer(value)
166
+
167
+ def from_array(
168
+ values: np.ndarray,
169
+ origin: tuple[float, float],
170
+ projection: MapProjection | tuple[str, tuple[float, float]],
171
+ ) -> YirgacheffeLayer:
172
+ """Creates an in-memory layer from a numerical array.
173
+
174
+ Args:
175
+ values: a 2D array of data values, with Y on the first dimension, X on
176
+ the second dimension.
177
+ origin: the position of the top left pixel in the geospatial space
178
+ projection: the map projection and pixel scale to use.
179
+
180
+ Returns:
181
+ A geospatial layer that uses the provided data for its values.
182
+ """
183
+
184
+ if projection is None:
185
+ raise ValueError("Projection must not be none")
186
+
187
+ if not isinstance(projection, MapProjection):
188
+ projection_name, scale_tuple = projection
189
+ projection = MapProjection(projection_name, scale_tuple[0], scale_tuple[1])
190
+
191
+ dims = values.shape
192
+
193
+ area = Area(
194
+ left=origin[0],
195
+ top=origin[1],
196
+ right=origin[0] + (projection.xstep * dims[1]),
197
+ bottom=origin[1] + (projection.ystep * dims[0])
198
+ )
199
+
200
+ layer = RasterLayer.empty_raster_layer(
201
+ area,
202
+ scale=projection.scale,
203
+ datatype=DataType.of_array(values),
204
+ filename=None,
205
+ projection=projection.name,
206
+ )
207
+ assert layer._dataset
208
+ assert layer._dataset.RasterXSize == dims[1]
209
+ assert layer._dataset.RasterYSize == dims[0]
210
+ layer._dataset.GetRasterBand(1).WriteArray(values, 0, 0)
211
+
212
+ return layer
yirgacheffe/_operators.py CHANGED
@@ -10,12 +10,12 @@ import sys
10
10
  import tempfile
11
11
  import time
12
12
  import types
13
+ from collections.abc import Callable
13
14
  from contextlib import ExitStack
14
15
  from enum import Enum
15
16
  from multiprocessing import Semaphore, Process
16
17
  from multiprocessing.managers import SharedMemoryManager
17
18
  from pathlib import Path
18
- from typing import Callable
19
19
 
20
20
  import deprecation
21
21
  import numpy as np
@@ -393,31 +393,34 @@ class LayerMathMixin:
393
393
  class LayerOperation(LayerMathMixin):
394
394
 
395
395
  @staticmethod
396
+ @deprecation.deprecated(
397
+ deprecated_in="1.10",
398
+ removed_in="2.0",
399
+ current_version=__version__,
400
+ details="Use from top level module instead."
401
+ )
396
402
  def where(cond, a, b):
397
- return LayerOperation(
398
- cond,
399
- op.WHERE,
400
- rhs=a,
401
- other=b
402
- )
403
+ return where(cond, a, b)
403
404
 
404
405
  @staticmethod
405
- def maximum(a, b):
406
- return LayerOperation(
407
- a,
408
- op.MAXIMUM,
409
- b,
410
- window_op=WindowOperation.UNION,
411
- )
406
+ @deprecation.deprecated(
407
+ deprecated_in="1.10",
408
+ removed_in="2.0",
409
+ current_version=__version__,
410
+ details="Use from top level module instead."
411
+ )
412
+ def minimum(a, b):
413
+ return minimum(a, b)
412
414
 
413
415
  @staticmethod
414
- def minimum(a, b):
415
- return LayerOperation(
416
- a,
417
- op.MINIMUM,
418
- rhs=b,
419
- window_op=WindowOperation.UNION,
420
- )
416
+ @deprecation.deprecated(
417
+ deprecated_in="1.10",
418
+ removed_in="2.0",
419
+ current_version=__version__,
420
+ details="Use from top level module instead."
421
+ )
422
+ def maximum(a, b):
423
+ return maximum(a, b)
421
424
 
422
425
  def __init__(
423
426
  self,
@@ -999,7 +1002,8 @@ class LayerOperation(LayerMathMixin):
999
1002
  self,
1000
1003
  filename: Path | str,
1001
1004
  and_sum: bool = False,
1002
- parallelism: int | bool | None = None
1005
+ parallelism: int | bool | None = None,
1006
+ callback: Callable[[float], None] | None = None,
1003
1007
  ) -> float | None:
1004
1008
  """Saves a calculation to a raster file, optionally also returning the sum of pixels.
1005
1009
 
@@ -1009,6 +1013,8 @@ class LayerOperation(LayerMathMixin):
1009
1013
  that value.
1010
1014
  parallelism: If passed, attempt to use multiple CPU cores up to the number provided, or if set to True,
1011
1015
  yirgacheffe will pick a sensible value.
1016
+ callback: If passed, this callback will be called periodically with a progress update for the saving,
1017
+ with a value between 0.0 and 1.0.
1012
1018
 
1013
1019
  Returns:
1014
1020
  Either returns None, or the sum of the pixels in the resulting raster if `and_sum` was specified.
@@ -1026,12 +1032,12 @@ class LayerOperation(LayerMathMixin):
1026
1032
  from yirgacheffe.layers.rasters import RasterLayer # type: ignore # pylint: disable=C0415
1027
1033
  with RasterLayer.empty_raster_layer_like(self, filename=tempory_file.name) as layer:
1028
1034
  if parallelism is None:
1029
- result = self.save(layer, and_sum=and_sum)
1035
+ result = self.save(layer, and_sum=and_sum, callback=callback)
1030
1036
  else:
1031
1037
  if isinstance(parallelism, bool):
1032
1038
  # Parallel save treats None as "work it out"
1033
1039
  parallelism = None
1034
- result = self.parallel_save(layer, and_sum=and_sum, parallelism=parallelism)
1040
+ result = self.parallel_save(layer, and_sum=and_sum, callback=callback, parallelism=parallelism)
1035
1041
 
1036
1042
  os.makedirs(target_dir, exist_ok=True)
1037
1043
  os.rename(src=tempory_file.name, dst=filename)
@@ -1121,10 +1127,70 @@ class ShaderStyleOperation(LayerOperation):
1121
1127
 
1122
1128
  return result
1123
1129
 
1130
+ def where(cond, a, b):
1131
+ """Return elements chosen from `a` or `b` depending on `cond`.
1132
+
1133
+ Behaves like numpy.where(condition, x, y), returning a layer operation
1134
+ where elements from `a` are selected where `cond` is True, and elements
1135
+ from `b` are selected where `cond` is False.
1136
+
1137
+ Args:
1138
+ cond: Layer or constant used as condition. Where True, yield `a`, otherwise yield `b`.
1139
+ a: Layer or constant with values from which to choose where `cond` is True.
1140
+ b: Layer or constant with values from which to choose where `cond` is False.
1141
+
1142
+ Returns:
1143
+ New layer representing the conditional selection.
1144
+ """
1145
+ return LayerOperation(
1146
+ cond,
1147
+ op.WHERE,
1148
+ rhs=a,
1149
+ other=b
1150
+ )
1151
+
1152
+ def maximum(a, b):
1153
+ """Element-wise maximum of layer elements.
1154
+
1155
+ Behaves like numpy.maximum(x1, x2), comparing two layers element-by-element
1156
+ and returning a new layer with the maximum values.
1157
+
1158
+ Args:
1159
+ a: First layer or constant to compare.
1160
+ b: Second layer or constant to compare.
1161
+
1162
+ Returns:
1163
+ New layer representing the element-wise maximum of the inputs.
1164
+ """
1165
+ return LayerOperation(
1166
+ a,
1167
+ op.MAXIMUM,
1168
+ b,
1169
+ window_op=WindowOperation.UNION,
1170
+ )
1171
+
1172
+ def minimum(a, b):
1173
+ """Element-wise minimum of layer elements.
1174
+
1175
+ Behaves like numpy.minimum(x1, x2), comparing two layers element-by-element
1176
+ and returning a new layer with the minimum values.
1177
+
1178
+ Args:
1179
+ a: First layer or constant to compare.
1180
+ b: Second layer or constant to compare.
1181
+
1182
+ Returns:
1183
+ New layer representing the element-wise minimum of the inputs.
1184
+ """
1185
+ return LayerOperation(
1186
+ a,
1187
+ op.MINIMUM,
1188
+ rhs=b,
1189
+ window_op=WindowOperation.UNION,
1190
+ )
1191
+
1192
+
1124
1193
  # We provide these module level accessors as it's often nicer to write `log(x/y)` rather than `(x/y).log()`
1125
- where = LayerOperation.where
1126
- minumum = LayerOperation.minimum
1127
- maximum = LayerOperation.maximum
1128
1194
  clip = LayerOperation.clip
1129
1195
  log = LayerOperation.log
1130
1196
  log2 = LayerOperation.log2
@@ -81,7 +81,7 @@ class GroupLayer(YirgacheffeLayer):
81
81
 
82
82
  @property
83
83
  def datatype(self) -> DataType:
84
- return DataType.of_gdal(self.layers[0].datatype)
84
+ return self.layers[0].datatype
85
85
 
86
86
  def set_window_for_intersection(self, new_area: Area) -> None:
87
87
  super().set_window_for_intersection(new_area)
yirgacheffe/operators.py CHANGED
@@ -1,7 +1,7 @@
1
1
  # Eventually all this should be moved to the top level in 2.0, but for backwards compatibility in 1.x needs
2
2
  # to remain here
3
3
 
4
- from ._operators import where, minumum, maximum, clip, log, log2, log10, exp, exp2, nan_to_num, isin, \
4
+ from ._operators import where, minimum, maximum, clip, log, log2, log10, exp, exp2, nan_to_num, isin, \
5
5
  floor, ceil # pylint: disable=W0611
6
6
  from ._operators import abs, round # pylint: disable=W0611,W0622
7
7
  from ._backends.enumeration import dtype as DataType # pylint: disable=W0611
yirgacheffe/window.py CHANGED
@@ -22,6 +22,15 @@ class MapProjection:
22
22
  name: The map projection used in WKT format.
23
23
  xstep: The number of units horizontal distance a step of one pixel makes in the map projection.
24
24
  ystep: The number of units vertical distance a step of one pixel makes in the map projection.
25
+
26
+ Examples:
27
+ Create a map projection using an EPSG code:
28
+
29
+ >>> proj_wgs84 = MapProjection("epsg:4326", 0.001, -0.001)
30
+
31
+ Create a projection using an ESRI code:
32
+
33
+ >>> proj_esri = MapProjection("esri:54030", 1000, -1000)
25
34
  """
26
35
 
27
36
  def __init__(self, projection_string: str, xstep: float, ystep: float) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yirgacheffe
3
- Version: 1.9.3
3
+ Version: 1.9.5
4
4
  Summary: Abstraction of gdal datasets for doing basic math operations
5
5
  Author-email: Michael Dales <mwd24@cam.ac.uk>
6
6
  License-Expression: ISC
@@ -106,11 +106,42 @@ with (
106
106
  yg.read_shape('species123.geojson') as range_map,
107
107
  ):
108
108
  refined_habitat = habitat_map.isin([...species habitat codes...])
109
- refined_elevation = (elevation_map >= species_min) && (elevation_map <= species_max)
109
+ refined_elevation = (elevation_map >= species_min) & (elevation_map <= species_max)
110
110
  aoh = refined_habitat * refined_elevation * range_polygon * area_per_pixel_map
111
111
  print(f'Area of habitat: {aoh.sum()}')
112
112
  ```
113
113
 
114
+ ## Citation
115
+
116
+ If you use Yirgacheffe in your research, please cite our paper:
117
+
118
+ > Michael Winston Dales, Alison Eyres, Patrick Ferris, Francesca A. Ridley, Simon Tarr, and Anil Madhavapeddy. 2025. Yirgacheffe: A Declarative Approach to Geospatial Data. In *Proceedings of the 2nd ACM SIGPLAN International Workshop on Programming for the Planet* (PROPL '25). Association for Computing Machinery, New York, NY, USA, 47–54. https://doi.org/10.1145/3759536.3763806
119
+
120
+ <details>
121
+ <summary>BibTeX</summary>
122
+
123
+ ```bibtex
124
+ @inproceedings{10.1145/3759536.3763806,
125
+ author = {Dales, Michael Winston and Eyres, Alison and Ferris, Patrick and Ridley, Francesca A. and Tarr, Simon and Madhavapeddy, Anil},
126
+ title = {Yirgacheffe: A Declarative Approach to Geospatial Data},
127
+ year = {2025},
128
+ isbn = {9798400721618},
129
+ publisher = {Association for Computing Machinery},
130
+ address = {New York, NY, USA},
131
+ url = {https://doi.org/10.1145/3759536.3763806},
132
+ doi = {10.1145/3759536.3763806},
133
+ abstract = {We present Yirgacheffe, a declarative geospatial library that allows spatial algorithms to be implemented concisely, supports parallel execution, and avoids common errors by automatically handling data (large geospatial rasters) and resources (cores, memory, GPUs). Our primary user domain comprises ecologists, where a typical problem involves cleaning messy occurrence data, overlaying it over tiled rasters, combining layers, and deriving actionable insights from the results. We describe the successes of this approach towards driving key pipelines related to global biodiversity and describe the capability gaps that remain, hoping to motivate more research into geospatial domain-specific languages.},
134
+ booktitle = {Proceedings of the 2nd ACM SIGPLAN International Workshop on Programming for the Planet},
135
+ pages = {47–54},
136
+ numpages = {8},
137
+ keywords = {Biodiversity, Declarative, Geospatial, Python},
138
+ location = {Singapore, Singapore},
139
+ series = {PROPL '25}
140
+ }
141
+ ```
142
+
143
+ </details>
144
+
114
145
  ## Thanks
115
146
 
116
147
  Thanks to discussion and feedback from my colleagues, particularly Alison Eyres, Patrick Ferris, Amelia Holcomb, and Anil Madhavapeddy.
@@ -1,27 +1,27 @@
1
- yirgacheffe/__init__.py,sha256=hDiVV0R4WCaRvYU_sBkL8Q6XO2PxFBM07fzNHYUGVJ0,878
2
- yirgacheffe/_core.py,sha256=AU6tlqovBV_l1dNZs6AlHSw59Z0U6pStUaQZvJGiLhM,5721
3
- yirgacheffe/_operators.py,sha256=cEjURX3GxI2kaNJwTy7JknaFsXNZTvRJj6yNLFvWTm0,41252
1
+ yirgacheffe/__init__.py,sha256=Ps6W8A1TRriVNxZEF3jW1_KOLEtji4ffyoGRmQXne8g,927
2
+ yirgacheffe/_core.py,sha256=Tr6RAiRZOO3vbtiTjLoNRjjd1DkXYZOgpDqrjs7jCBw,7231
3
+ yirgacheffe/_operators.py,sha256=VGQ9AOOJP6cxsr_G2kxdaPaXqKi7K1csocxsudlRwVc,43440
4
4
  yirgacheffe/constants.py,sha256=bKUjOGNj19zwggV79lJgK7tiv51DH2-rgNOKswl2gvQ,293
5
- yirgacheffe/operators.py,sha256=nw-BpnAwTjCwFtjosa8wKd2MGUuC0PJR5jACFdLhqCg,412
5
+ yirgacheffe/operators.py,sha256=Y1KkNt79N1elR4ZplQaQngx29wdf2QFF_5la4PI3EhI,412
6
6
  yirgacheffe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  yirgacheffe/rounding.py,sha256=Jzd9qlLnLpigT95GbQTByvYOo639Nfq4LBEVyvhYdoc,2289
8
- yirgacheffe/window.py,sha256=QuyBLOwKFI0XkEQ4Bd2hdELPbJSfHL7mt5KSi7CIHcE,9505
8
+ yirgacheffe/window.py,sha256=kAAsE3t49DbffrW78DLZuGwgT-MCpM3WBJQ-5MO5ciM,9755
9
9
  yirgacheffe/_backends/__init__.py,sha256=jN-2iRrHStnPI6cNL7XhwhsROtI0EaGfIrbF5c-ECV0,334
10
- yirgacheffe/_backends/enumeration.py,sha256=9bcCXz9Ssrh8Oh1iazodkx6Gm2kQBi9HQ9z9zehS4AE,1806
10
+ yirgacheffe/_backends/enumeration.py,sha256=Clb-oRha65po_dED_lECXjnih-n77CtUg18-34xX6nA,2652
11
11
  yirgacheffe/_backends/mlx.py,sha256=U1gl1lK1mZXLEET6ylF1TNs6WJ0PBEvfSk7ppn28n8w,6203
12
12
  yirgacheffe/_backends/numpy.py,sha256=Gxx49JJH79GFEkKIpV6IyjCUcdtN5-qLlzRfylzKhS4,4142
13
13
  yirgacheffe/layers/__init__.py,sha256=mYKjw5YTcMNv_hMy7a6K4yRzIuNUbR8WuBTw4WIAmSk,435
14
14
  yirgacheffe/layers/area.py,sha256=wJcMHbLJBaXS4BeFbu5rYeKfgu3gvaE9hwQ5j6aw-y4,3976
15
15
  yirgacheffe/layers/base.py,sha256=7b4WXuvnmCv8mR0iyCIuSEolnV8D3f2vtCaYlcJCIa8,13201
16
16
  yirgacheffe/layers/constant.py,sha256=gtkQ98Z01CYYDgFElswtRZY4ZG3UnS5NIAoIVue5ufk,1481
17
- yirgacheffe/layers/group.py,sha256=yaqf-ra_Vh59yrWcz7-OvJ1fBnTcBXZd18AfRDN5Ymo,16157
17
+ yirgacheffe/layers/group.py,sha256=jFJ60YcbkLNeD-2w3QOLwbcYEWdgicrXMClIo2vz97Q,16139
18
18
  yirgacheffe/layers/h3layer.py,sha256=Rq1bFo7CApIh5NdBcV7hSj3hm-DszY79nhYsTRAvJ_g,9916
19
19
  yirgacheffe/layers/rasters.py,sha256=zBE9uXm6LvAQF2_XdQzcOgJQOQWGmuPflY5JNDrUf3k,13527
20
20
  yirgacheffe/layers/rescaled.py,sha256=gEFbXeYxX1nVn7eQYmbGww90_yc5ENmgQrD_WxXxpQE,3352
21
21
  yirgacheffe/layers/vectors.py,sha256=A27kuTr0C9BZhHG0-cplNEa7aSNcse37Pm9xTjEzv-c,19990
22
- yirgacheffe-1.9.3.dist-info/licenses/LICENSE,sha256=dNSHwUCJr6axStTKDEdnJtfmDdFqlE3h1NPCveqPfnY,757
23
- yirgacheffe-1.9.3.dist-info/METADATA,sha256=g6QH8LpSDGzWwQZ33Swkb0lt61jbI95kTFM-VklAQZ8,5429
24
- yirgacheffe-1.9.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
- yirgacheffe-1.9.3.dist-info/entry_points.txt,sha256=j4KgHXbVGbGyfTySc1ypBdERpfihO4WNjppvCdE9HjE,52
26
- yirgacheffe-1.9.3.dist-info/top_level.txt,sha256=9DBFlKO2Ld3hG6TuE3qOTd3Tt8ugTiXil4AN4Wr9_y0,12
27
- yirgacheffe-1.9.3.dist-info/RECORD,,
22
+ yirgacheffe-1.9.5.dist-info/licenses/LICENSE,sha256=dNSHwUCJr6axStTKDEdnJtfmDdFqlE3h1NPCveqPfnY,757
23
+ yirgacheffe-1.9.5.dist-info/METADATA,sha256=TvX6Nwvdp_u80s9PXHCvSoxndvp4mjOBE1wRcn-neL0,7407
24
+ yirgacheffe-1.9.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
+ yirgacheffe-1.9.5.dist-info/entry_points.txt,sha256=j4KgHXbVGbGyfTySc1ypBdERpfihO4WNjppvCdE9HjE,52
26
+ yirgacheffe-1.9.5.dist-info/top_level.txt,sha256=9DBFlKO2Ld3hG6TuE3qOTd3Tt8ugTiXil4AN4Wr9_y0,12
27
+ yirgacheffe-1.9.5.dist-info/RECORD,,