core-lens 0.1.dev200__tar.gz → 0.1.dev202__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.
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/PKG-INFO +1 -1
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/_version.py +2 -2
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/base/view.py +26 -4
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_view.py +70 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/.github/ISSUE_TEMPLATE/blank-proposal.yaml +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/.github/ISSUE_TEMPLATE/bug-report.yaml +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/.github/ISSUE_TEMPLATE/feature-request.yaml +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/.github/pull_request_template.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/.github/workflows/ci.yml +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/.github/workflows/gh-pages.yml +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/.github/workflows/pre-release.yml +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/.github/workflows/release.yml +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/.gitignore +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/.gitmessage +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/.pre-commit-config.yaml +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/.python-version +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/CONTRIBUTING.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/LICENSE +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/README.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/SKILLS.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/benchmarks/README.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/benchmarks/bench_aoi.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/benchmarks/bench_entity.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/benchmarks/bench_export.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/benchmarks/bench_polars_utils.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/benchmarks/bench_result.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/benchmarks/bench_schema.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/benchmarks/bench_season.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/benchmarks/bench_spatial.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/benchmarks/bench_view.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/benchmarks/run_all.sh +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/docs/Makefile +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/docs/make.bat +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/docs/source/concepts.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/docs/source/conf.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/docs/source/export.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/docs/source/index.rst +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/docs/source/intro.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/docs/source/logging.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/docs/source/plots.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/docs/source/plugins.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/docs/source/queries.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/docs/source/quickstart.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/docs/source/stats.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/examples/demo_mws.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/examples/demo_tehsil.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/hooks/mypy.sh +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/hooks/no-parquet-outside-fixtures.sh +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/hooks/pytest.sh +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/pyproject.toml +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/__init__.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/__main__.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/aoi.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/base/__init__.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/base/entity.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/base/namespaces/__init__.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/base/namespaces/plot.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/base/namespaces/stats.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/base/result.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/entities/__init__.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/entities/farms.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/entities/mws.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/entities/tehsil.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/entities/waterbody.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/export/__init__.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/export/formats.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/py.typed +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/schema/__init__.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/schema/detection.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/schema/profile.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/utils/__init__.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/utils/paths.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/utils/polars_utils.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/utils/season.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/src/core_lens/utils/spatial.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/fixtures/generate_fixtures.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/conftest.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_aoi.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_entities.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_entity.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_export.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_main.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_plot.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_polars_utils.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_profile.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_result.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_schema_detection.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_schema_profile.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_season.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_season_config.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_spatial.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/tests/unit/test_stats.py +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/usage.md +0 -0
- {core_lens-0.1.dev200 → core_lens-0.1.dev202}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: core-lens
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.dev202
|
|
4
4
|
Summary: Query, analyse, and visualise CoreStack's microwatershed and Earth science data through a clean, composable Python API.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ApoorvaKashyap/core-lens
|
|
6
6
|
Project-URL: Issues, https://github.com/ApoorvaKashyap/core-lens/issues
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.1.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, '
|
|
21
|
+
__version__ = version = '0.1.dev202'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 'dev202')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -234,15 +234,25 @@ class View:
|
|
|
234
234
|
season_config=self._season_config,
|
|
235
235
|
)
|
|
236
236
|
|
|
237
|
-
def spatial_join(self, other: "BaseEntity", agg: dict[str, str]) -> "View":
|
|
237
|
+
def spatial_join(self, other: "BaseEntity | View", agg: dict[str, str]) -> "View":
|
|
238
238
|
"""Return a new View with a cross-entity join pending.
|
|
239
239
|
|
|
240
240
|
The join is recorded in the View's ``join_spec`` and computed only at
|
|
241
241
|
materialisation time (``.static``, ``.annual``, or ``.sub_annual``).
|
|
242
242
|
|
|
243
|
+
``other`` may be either a :class:`~core_lens.base.entity.BaseEntity`
|
|
244
|
+
**or** a :class:`View` (e.g. ``aoi.waterbody``). When a ``View`` is
|
|
245
|
+
supplied its underlying ``.entity`` is extracted automatically, so
|
|
246
|
+
both of the following are equivalent::
|
|
247
|
+
|
|
248
|
+
aoi.mws.spatial_join(aoi.waterbody, agg={"kharif": "mean"})
|
|
249
|
+
aoi.mws.spatial_join(aoi.waterbody.entity, agg={"kharif": "mean"})
|
|
250
|
+
|
|
243
251
|
Args:
|
|
244
|
-
other (BaseEntity): The secondary
|
|
245
|
-
joined and aggregated onto ``self``.
|
|
252
|
+
other (BaseEntity | View): The secondary entity whose columns will
|
|
253
|
+
be joined and aggregated onto ``self``. Accepts either a
|
|
254
|
+
:class:`~core_lens.base.entity.BaseEntity` instance or a
|
|
255
|
+
:class:`View` (the latter is automatically unwrapped).
|
|
246
256
|
agg (dict[str, str]): Mapping of ``{column: aggregation}`` specifying which columns
|
|
247
257
|
from ``other`` to bring in and how to aggregate them.
|
|
248
258
|
|
|
@@ -256,12 +266,17 @@ class View:
|
|
|
256
266
|
)
|
|
257
267
|
raise ValueError("View already has a pending spatial_join.")
|
|
258
268
|
|
|
269
|
+
# Accept a View as `other` (e.g. aoi.waterbody) by unwrapping it.
|
|
270
|
+
# AoI attributes return scoped Views, not bare BaseEntity instances,
|
|
271
|
+
# so this is the natural way to reference a secondary entity.
|
|
272
|
+
other_entity = other.entity if isinstance(other, View) else other
|
|
273
|
+
|
|
259
274
|
return View(
|
|
260
275
|
keys=self.keys,
|
|
261
276
|
entity=self.entity,
|
|
262
277
|
entity_name=self.entity_name,
|
|
263
278
|
time_filter=self.time_filter,
|
|
264
|
-
join_spec={"other":
|
|
279
|
+
join_spec={"other": other_entity, "agg": agg},
|
|
265
280
|
season_config=self._season_config,
|
|
266
281
|
)
|
|
267
282
|
|
|
@@ -513,6 +528,13 @@ class View:
|
|
|
513
528
|
other = self.join_spec["other"]
|
|
514
529
|
agg = self.join_spec["agg"]
|
|
515
530
|
|
|
531
|
+
# Defensively unwrap a View to its entity — join_spec["other"] may be a
|
|
532
|
+
# View when callers pass e.g. aoi.waterbody directly (AoI.__getattr__
|
|
533
|
+
# returns Views, not bare entities). spatial_join() already does this
|
|
534
|
+
# unwrap, but _materialise guards here too for robustness.
|
|
535
|
+
if isinstance(other, View):
|
|
536
|
+
other = other.entity
|
|
537
|
+
|
|
516
538
|
from core_lens.base.entity import _entity_name as _ename
|
|
517
539
|
|
|
518
540
|
other_entity_name = _ename(type(other))
|
|
@@ -297,6 +297,76 @@ class TestViewMaterialisation:
|
|
|
297
297
|
with pytest.raises(ValueError, match="already has a pending spatial_join"):
|
|
298
298
|
view.spatial_join(entity_cls(), {"district": "count"})
|
|
299
299
|
|
|
300
|
+
def test_spatial_join_accepts_view_as_other(self, entity_cls: Any) -> None:
|
|
301
|
+
"""Passing a View as `other` (e.g. aoi.waterbody) should unwrap to the
|
|
302
|
+
underlying entity so that materialisation can call .schema_profile on it.
|
|
303
|
+
|
|
304
|
+
Regression test for: AttributeError: 'View' object has no attribute
|
|
305
|
+
'schema_profile' raised inside execute_spatial_join when AoI attributes
|
|
306
|
+
(which return Views) were passed directly to spatial_join.
|
|
307
|
+
"""
|
|
308
|
+
entity = entity_cls()
|
|
309
|
+
other_entity = entity_cls()
|
|
310
|
+
# Wrap the secondary entity in a View, mimicking what AoI.__getattr__ returns.
|
|
311
|
+
other_view = View(
|
|
312
|
+
keys=pl.DataFrame({"mws_id": []}, schema={"mws_id": pl.String}),
|
|
313
|
+
entity=other_entity,
|
|
314
|
+
entity_name="otherw",
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
view = View(
|
|
318
|
+
keys=pl.DataFrame({"mws_id": []}, schema={"mws_id": pl.String}),
|
|
319
|
+
entity=entity,
|
|
320
|
+
entity_name="minimalmws",
|
|
321
|
+
)
|
|
322
|
+
joined = view.spatial_join(other_view, {"district": "count"})
|
|
323
|
+
|
|
324
|
+
# The join_spec must store the unwrapped BaseEntity, not the View.
|
|
325
|
+
assert joined.join_spec is not None
|
|
326
|
+
assert joined.join_spec["other"] is other_entity
|
|
327
|
+
assert not isinstance(joined.join_spec["other"], View)
|
|
328
|
+
|
|
329
|
+
def test_spatial_join_view_other_materialises(self, entity_cls: Any) -> None:
|
|
330
|
+
"""Materialising a View whose join `other` was supplied as a View should
|
|
331
|
+
produce the same prefixed columns as passing a bare entity directly.
|
|
332
|
+
"""
|
|
333
|
+
import pathlib
|
|
334
|
+
import tempfile
|
|
335
|
+
import sys
|
|
336
|
+
import os
|
|
337
|
+
|
|
338
|
+
conftest_dir = os.path.dirname(__file__)
|
|
339
|
+
if conftest_dir not in sys.path:
|
|
340
|
+
sys.path.insert(0, conftest_dir)
|
|
341
|
+
from conftest import _make_static_parquet, _make_entity_cls
|
|
342
|
+
|
|
343
|
+
with tempfile.TemporaryDirectory() as td:
|
|
344
|
+
sp = pathlib.Path(td) / "static.parquet"
|
|
345
|
+
_make_static_parquet(sp)
|
|
346
|
+
full_cls = _make_entity_cls(sp)
|
|
347
|
+
full_entity = full_cls()
|
|
348
|
+
keys = full_entity._index.select(full_entity.key_cols)
|
|
349
|
+
|
|
350
|
+
# Build the secondary entity wrapped in a View — the pattern from aoi.X
|
|
351
|
+
other_view = View(
|
|
352
|
+
keys=keys,
|
|
353
|
+
entity=full_entity,
|
|
354
|
+
entity_name="minimalmws",
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
view = View(
|
|
358
|
+
keys=keys,
|
|
359
|
+
entity=full_entity,
|
|
360
|
+
entity_name="minimalmws",
|
|
361
|
+
join_spec={"other": other_view, "agg": {"district": "count"}},
|
|
362
|
+
)
|
|
363
|
+
result = view.static
|
|
364
|
+
|
|
365
|
+
joined_cols = [c for c in result.df().columns if c.startswith("minimalmws_")]
|
|
366
|
+
assert joined_cols, (
|
|
367
|
+
f"Expected prefixed join columns but got: {result.df().columns}"
|
|
368
|
+
)
|
|
369
|
+
|
|
300
370
|
def test_spatial_join_fetches_geometry_if_missing(
|
|
301
371
|
self, entity_cls_full: Any
|
|
302
372
|
) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|