agrigee-lite-client 3.4.1__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 (27) hide show
  1. agrigee_lite_client-3.4.1/.gitignore +164 -0
  2. agrigee_lite_client-3.4.1/AGENTS.md +105 -0
  3. agrigee_lite_client-3.4.1/PKG-INFO +144 -0
  4. agrigee_lite_client-3.4.1/README.md +117 -0
  5. agrigee_lite_client-3.4.1/agrigee_lite_client/__init__.py +25 -0
  6. agrigee_lite_client-3.4.1/agrigee_lite_client/_async_client.py +205 -0
  7. agrigee_lite_client-3.4.1/agrigee_lite_client/_client.py +213 -0
  8. agrigee_lite_client-3.4.1/agrigee_lite_client/_exceptions.py +57 -0
  9. agrigee_lite_client-3.4.1/agrigee_lite_client/_geo_compat.py +47 -0
  10. agrigee_lite_client-3.4.1/agrigee_lite_client/_geoparquet.py +66 -0
  11. agrigee_lite_client-3.4.1/agrigee_lite_client/_get.py +111 -0
  12. agrigee_lite_client-3.4.1/agrigee_lite_client/_satellite_dates.py +95 -0
  13. agrigee_lite_client-3.4.1/agrigee_lite_client/_validation.py +91 -0
  14. agrigee_lite_client-3.4.1/agrigee_lite_client/_version.py +10 -0
  15. agrigee_lite_client-3.4.1/pyproject.toml +71 -0
  16. agrigee_lite_client-3.4.1/scripts/generate_satellite_dates.py +91 -0
  17. agrigee_lite_client-3.4.1/tests/__init__.py +0 -0
  18. agrigee_lite_client-3.4.1/tests/_fake_server.py +127 -0
  19. agrigee_lite_client-3.4.1/tests/test_async_client_mocked.py +188 -0
  20. agrigee_lite_client-3.4.1/tests/test_client_against_live_api.py +56 -0
  21. agrigee_lite_client-3.4.1/tests/test_client_mocked.py +186 -0
  22. agrigee_lite_client-3.4.1/tests/test_geo_compat.py +55 -0
  23. agrigee_lite_client-3.4.1/tests/test_geoparquet.py +70 -0
  24. agrigee_lite_client-3.4.1/tests/test_satellite_dates.py +30 -0
  25. agrigee_lite_client-3.4.1/tests/test_satellite_dates_match_server.py +50 -0
  26. agrigee_lite_client-3.4.1/tests/test_validation.py +100 -0
  27. agrigee_lite_client-3.4.1/tests/test_version_matches_server.py +41 -0
@@ -0,0 +1,164 @@
1
+ docs/source
2
+
3
+ # From https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore
4
+
5
+ # Byte-compiled / optimized / DLL files
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+
10
+ # C extensions
11
+ *.so
12
+
13
+ # Distribution / packaging
14
+ .Python
15
+ build/
16
+ develop-eggs/
17
+ dist/
18
+ downloads/
19
+ eggs/
20
+ .eggs/
21
+ lib/
22
+ lib64/
23
+ parts/
24
+ sdist/
25
+ var/
26
+ wheels/
27
+ share/python-wheels/
28
+ *.egg-info/
29
+ .installed.cfg
30
+ *.egg
31
+ MANIFEST
32
+
33
+ # PyInstaller
34
+ # Usually these files are written by a python script from a template
35
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
36
+ *.manifest
37
+ *.spec
38
+
39
+ # Installer logs
40
+ pip-log.txt
41
+ pip-delete-this-directory.txt
42
+
43
+ # Unit test / coverage reports
44
+ htmlcov/
45
+ .tox/
46
+ .nox/
47
+ .coverage
48
+ .coverage.*
49
+ .cache
50
+ nosetests.xml
51
+ coverage.xml
52
+ *.cover
53
+ *.py,cover
54
+ .hypothesis/
55
+ .pytest_cache/
56
+ cover/
57
+
58
+ # Translations
59
+ *.mo
60
+ *.pot
61
+
62
+ # Django stuff:
63
+ *.log
64
+ local_settings.py
65
+ db.sqlite3
66
+ db.sqlite3-journal
67
+
68
+ # Flask stuff:
69
+ instance/
70
+ .webassets-cache
71
+
72
+ # Scrapy stuff:
73
+ .scrapy
74
+
75
+ # Sphinx documentation
76
+ docs/_build/
77
+
78
+ # PyBuilder
79
+ .pybuilder/
80
+ target/
81
+
82
+ # Jupyter Notebook
83
+ .ipynb_checkpoints
84
+
85
+ # IPython
86
+ profile_default/
87
+ ipython_config.py
88
+
89
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
90
+ __pypackages__/
91
+
92
+ # Celery stuff
93
+ celerybeat-schedule
94
+ celerybeat.pid
95
+
96
+ # SageMath parsed files
97
+ *.sage.py
98
+
99
+ # Environments
100
+ .env
101
+ .venv
102
+ env/
103
+ venv/
104
+ ENV/
105
+ env.bak/
106
+ venv.bak/
107
+
108
+ # Spyder project settings
109
+ .spyderproject
110
+ .spyproject
111
+
112
+ # Rope project settings
113
+ .ropeproject
114
+
115
+ # mkdocs documentation
116
+ /site
117
+
118
+ # mypy
119
+ .mypy_cache/
120
+ .dmypy.json
121
+ dmypy.json
122
+
123
+ # Pyre type checker
124
+ .pyre/
125
+
126
+ # pytype static type analyzer
127
+ .pytype/
128
+
129
+ # Cython debug symbols
130
+ cython_debug/
131
+
132
+ # Vscode config files
133
+ .vscode/
134
+
135
+ # PyCharm
136
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
137
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
138
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
139
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
140
+ #.idea/
141
+
142
+
143
+
144
+ # Specific OS junk files
145
+ *.DS_Store
146
+ core.*
147
+ *:Zone.Identifier
148
+
149
+ *.parquet
150
+ *.npz
151
+ *.csv
152
+
153
+ data/
154
+ !data/sample.parquet
155
+
156
+
157
+ *.pdf
158
+ *.html
159
+ *.zip
160
+ *.npg
161
+ aria2.session
162
+ # pixi environments
163
+ .pixi/*
164
+ !.pixi/config.toml
@@ -0,0 +1,105 @@
1
+ ## AGENTS (compact LLM-oriented) — agrigee_lite_client
2
+
3
+ Subproject inside the AgriGEE.lite monorepo. See `../AGENTS.md` for the
4
+ server; this file covers only the HTTP client. Read `SPECS.md` in full
5
+ before touching this code — the design decisions (naive on purpose, no
6
+ install extras, geopandas optional-only) are already made and justified
7
+ there; don't reopen them without a new reason.
8
+
9
+ Run checks:
10
+
11
+ ```bash
12
+ cd agrigee_lite_client
13
+ pip install -e ".[dev]"
14
+ pytest
15
+ ruff check .
16
+ ```
17
+
18
+ ## Key rules (short)
19
+
20
+ - `geopandas` is never a hard dependency, but `multiple_sits()` accepts it as
21
+ input via lazy detection (`_geo_compat.py`: `try: import geopandas` inside
22
+ the function, never at module top-level). A top-level `import geopandas`
23
+ anywhere in the package is a bug — callers who only use `sits()`/geopolars/
24
+ polars must never pay that import. `geopandas` as a **test-only**
25
+ dependency (`test_geoparquet.py`, `test_geo_compat.py`) is fine.
26
+ - Fixed dependencies, no extras: `geopolars`, `shapely`, `httpx`. Do not add
27
+ `[async]`, `[shapely]`, or any optional install variant — explicit user
28
+ decision, don't reintroduce.
29
+ - Version always matches the server's (`../pyproject.toml`). Every server
30
+ version bump gets the same bump here, same commit.
31
+ `tests/test_version_matches_server.py` breaks CI on drift.
32
+ - Sync and async are deliberately duplicated code (`_client.py` /
33
+ `_async_client.py`) — do not build a sync/async unification layer (e.g.
34
+ `unasync`) on top. `_get.py` already holds everything that's genuinely
35
+ shared (payload building, response parsing); only I/O (`httpx` calls,
36
+ `sleep`/`asyncio.sleep`) is duplicated.
37
+ - `get.sits()` is sugar over `get.multiple_sits()`, not a separate endpoint —
38
+ don't give it its own HTTP path without reviewing SPECS.md §6 and §11
39
+ first (a synchronous `/sits/single/file` server endpoint is explicitly
40
+ deferred future work, don't implement it preemptively).
41
+ - GeoParquet metadata is hand-built (`_geoparquet.py`) via
42
+ `pyarrow.Table.replace_schema_metadata`, because `geopolars` 0.1.0-alpha.4
43
+ doesn't write the `"geo"` key on its own (`GeoDataFrame.write_parquet` is
44
+ the plain `polars.DataFrame.write_parquet`). Reconfirm that behavior on
45
+ the installed `geopolars` version before changing this approach — it may
46
+ have changed.
47
+ - CRS is always `EPSG:4326`/`OGC:CRS84` — the client never reprojects. Don't
48
+ add `pyproj`/reprojection without discussing first (breaks the minimal-
49
+ dependency promise). A `geopandas.GeoDataFrame` with a different CRS is
50
+ rejected with `ValueError`, not reprojected.
51
+ - Don't add configurable retry/backoff, local caching, authentication, a
52
+ CLI, or image decoding (`numpy`/`tifffile`/`rasterio`) without updating
53
+ SPECS.md §10 first — explicit non-goals of this v1, not oversights.
54
+ - **No new server endpoint just to support this client.** One was proposed
55
+ (`POST /satellites/date_range`) and rejected — the user didn't want the
56
+ server growing surface area purely for the client's benefit. `GET
57
+ /version` is the one exception, added specifically because `/health`
58
+ already has consumers and must not change shape. Prefer extending an
59
+ existing endpoint over adding a new one; if that's not possible, ask
60
+ before adding one.
61
+ - `_satellite_dates.py` is generated, not hand-edited. Regenerate with:
62
+
63
+ ```bash
64
+ python scripts/generate_satellite_dates.py > agrigee_lite_client/_satellite_dates.py
65
+ ```
66
+
67
+ (needs `agrigee_lite` importable — use the `all-features` pixi env)
68
+ whenever a satellite is added/removed or its range changes server-side,
69
+ and bump both packages' version together (SPECS.md §2.1).
70
+ `test_satellite_dates_match_server.py` catches drift automatically
71
+ whenever both packages are installed side by side, but doesn't replace
72
+ regenerating after a real server-side change.
73
+ - `pl.col(...).cast(pl.Datetime, strict=False)` silently nulls a `Utf8`
74
+ column instead of parsing it, as of polars 1.43 — it used to parse; that
75
+ behavior is deprecated/gone, `str.to_datetime(strict=False)` is the
76
+ replacement. Bit this code once (every row got silently dropped).
77
+ `_validation.py::_as_datetime_expr` checks dtype and picks the right one —
78
+ don't copy the server's `sanitize_and_prepare_input_gdf` pattern
79
+ (`agrigee_lite/get/sits.py`) verbatim; it only works there because the
80
+ column already arrives converted via `pandas.to_datetime`.
81
+
82
+ ## Tests
83
+
84
+ - `test_geoparquet.py` — no network, validates bytes against
85
+ `geopandas.read_parquet`.
86
+ - `test_validation.py`, `test_geo_compat.py`, `test_satellite_dates.py` — no
87
+ network, test `_validation.py`/`_geo_compat.py`/`_satellite_dates.py` in
88
+ isolation.
89
+ - `test_satellite_dates_match_server.py` — only runs when `agrigee_lite` is
90
+ importable (`pytest.importorskip`); this is what actually catches drift
91
+ between the static table and the server's `REGISTRY`.
92
+ - `test_client_mocked.py` / `test_async_client_mocked.py` — no network, use
93
+ `httpx.MockTransport` + `tests/_fake_server.py` (a stateful fake of the
94
+ real API: health, version, satellites, job lifecycle). Add new scenarios
95
+ here, not against a real server.
96
+ - `test_client_against_live_api.py` — only runs with `AGRIGEE_TEST_API_URL`
97
+ set; not part of the default test run (depends on network + the server's
98
+ Earth Engine credentials).
99
+ - `test_version_matches_server.py` — always runs, the version lock.
100
+
101
+ ## Docker Hub
102
+
103
+ This subproject has no image of its own — it's consumed by whoever runs
104
+ `pip install agrigee_lite_client`. The relevant Docker image is the
105
+ server's (`mateuspinto/agrigee-lite`, see `../AGENTS.md`).
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.5
2
+ Name: agrigee_lite_client
3
+ Version: 3.4.1
4
+ Summary: Thin HTTP client for the AgriGEE.lite REST API — no Earth Engine, no GDAL, no DuckDB.
5
+ Project-URL: Homepage, https://mateuspinto.github.io/agrigee_lite/
6
+ Project-URL: Repository, https://github.com/mateuspinto/agrigee_lite
7
+ Project-URL: Documentation, https://mateuspinto.github.io/agrigee_lite/
8
+ Author-email: Mateus Pinto da Silva <mateus.p.silva@ufv.br>
9
+ Keywords: python
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: <4.0,>=3.10
19
+ Requires-Dist: geopolars==0.1.0a4
20
+ Requires-Dist: httpx>=0.27.0
21
+ Requires-Dist: shapely>=2.0.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: geopandas>=1.0.1; extra == 'dev'
24
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
25
+ Requires-Dist: ruff>=0.11.0; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # agrigee_lite_client
29
+
30
+ Thin HTTP client for the [`agrigee_lite[api]`](../README.md) server. Doesn't
31
+ install Earth Engine, GDAL/PROJ, or DuckDB — it only knows how to build a
32
+ request, upload/download bytes, and talk to `/jobs`. See
33
+ [`SPECS.md`](SPECS.md) for the full design and the reasoning behind it.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install agrigee_lite_client
39
+ ```
40
+
41
+ One fixed dependency set, always — `geopolars`, `shapely`, and `httpx`.
42
+ There's no install variation (no `[async]`, `[shapely]`, etc. extras): the
43
+ same install already covers both sync and async use.
44
+
45
+ ## Quickstart — sync
46
+
47
+ ```python
48
+ from agrigee_lite_client import AgriGEEClient
49
+ from shapely.geometry import Polygon
50
+
51
+ geometry = Polygon([(-56.42, -11.20), (-56.41, -11.20), (-56.41, -11.19)])
52
+
53
+ with AgriGEEClient("http://192.168.3.204:8100") as client:
54
+ # a single geometry — naive: internally becomes a 1-row multiple_sits
55
+ df = client.get.sits(geometry, "2023-01-01", "2023-06-01", satellite="Sentinel2")
56
+
57
+ # multiple geometries — uploads a GeoParquet, polls the job, downloads the result parquet
58
+ import polars as pl
59
+ from agrigee_lite_client._geoparquet import encode_geometries
60
+
61
+ gdf = pl.DataFrame({
62
+ "start_date": ["2023-01-01", "2023-01-01"],
63
+ "end_date": ["2023-06-01", "2023-06-01"],
64
+ "geometry": encode_geometries([geometry, geometry]),
65
+ })
66
+ df = client.get.multiple_sits(gdf, satellite="Sentinel2", reducers=["mean", "std"])
67
+
68
+ # geopandas.GeoDataFrame also works (geopandas is optional — only
69
+ # imported if you actually pass one; it's not installed alongside the client)
70
+ import geopandas as gpd
71
+
72
+ gdf_geopandas = gpd.GeoDataFrame(
73
+ {"start_date": ["2023-01-01"], "end_date": ["2023-06-01"]},
74
+ geometry=[geometry],
75
+ crs="EPSG:4326",
76
+ )
77
+ df = client.get.multiple_sits(gdf_geopandas, satellite="Sentinel2")
78
+
79
+ # image — returns an in-memory zipfile.ZipFile, nothing decoded
80
+ zf = client.get.image(geometry, "2023-01-01", "2023-06-01", satellite="Sentinel2")
81
+ zf.extractall("out/")
82
+ ```
83
+
84
+ ## Quickstart — async
85
+
86
+ Same surface, `AsyncAgriGEEClient`:
87
+
88
+ ```python
89
+ import asyncio
90
+ from agrigee_lite_client import AsyncAgriGEEClient
91
+
92
+ async def main():
93
+ async with AsyncAgriGEEClient("http://192.168.3.204:8100") as client:
94
+ df = await client.get.sits(geometry, "2023-01-01", "2023-06-01", satellite="Sentinel2")
95
+
96
+ asyncio.run(main())
97
+ ```
98
+
99
+ ## Validation before anything leaves your machine
100
+
101
+ `sits()`/`multiple_sits()` check that the requested dates actually intersect
102
+ the satellite's operating period — the same check the server does — and
103
+ drop (with a warning) rows that don't, raising `ValueError` if none are
104
+ left. All of this happens **before** building the GeoParquet or opening a
105
+ connection, using a static table embedded in the client (no network call).
106
+ An unknown satellite name is also caught here (`AgriGEEUnknownSatelliteError`).
107
+
108
+ Since that table is a copy of what the server knows, the client checks that
109
+ it's running against the same server version (via `GET /version`) before
110
+ trusting it — a mismatch raises `AgriGEEVersionMismatchError` instead of
111
+ validating against possibly stale data.
112
+
113
+ ## What this client does NOT do (on purpose)
114
+
115
+ - No CRS reprojection — input geometries must be in WGS84 (EPSG:4326 /
116
+ OGC:CRS84). If you pass a `geopandas.GeoDataFrame` with a different CRS,
117
+ the client rejects it with a clear error instead of reprojecting.
118
+ - No local caching, no sophisticated retry/backoff — the cache already lives
119
+ on the server; the only "retry" here is `httpx`'s transport timeout and a
120
+ simple poll loop against `GET /jobs/{id}`.
121
+ - No image decoding — `get.image(...)` returns a raw `zipfile.ZipFile`, no
122
+ `numpy`/`tifffile`/`rasterio`.
123
+ - `get.sits(...)` isn't a separate fast path: it's sugar over
124
+ `get.multiple_sits(...)` with a single row, so it pays the job+poll+
125
+ download cost even for one geometry. See `SPECS.md §6`.
126
+
127
+ ## Versioning
128
+
129
+ This package's version always matches `agrigee_lite`'s (the server), same
130
+ commit — they aren't versioned independently. A test
131
+ (`tests/test_version_matches_server.py`) enforces this in CI.
132
+
133
+ ## Development
134
+
135
+ ```bash
136
+ cd agrigee_lite_client
137
+ pip install -e ".[dev]"
138
+ pytest
139
+ ruff check .
140
+ ```
141
+
142
+ `tests/test_client_against_live_api.py` only runs if `AGRIGEE_TEST_API_URL`
143
+ is set — the rest (`test_client_mocked.py`, `test_async_client_mocked.py`)
144
+ use `httpx.MockTransport` and need no network.
@@ -0,0 +1,117 @@
1
+ # agrigee_lite_client
2
+
3
+ Thin HTTP client for the [`agrigee_lite[api]`](../README.md) server. Doesn't
4
+ install Earth Engine, GDAL/PROJ, or DuckDB — it only knows how to build a
5
+ request, upload/download bytes, and talk to `/jobs`. See
6
+ [`SPECS.md`](SPECS.md) for the full design and the reasoning behind it.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install agrigee_lite_client
12
+ ```
13
+
14
+ One fixed dependency set, always — `geopolars`, `shapely`, and `httpx`.
15
+ There's no install variation (no `[async]`, `[shapely]`, etc. extras): the
16
+ same install already covers both sync and async use.
17
+
18
+ ## Quickstart — sync
19
+
20
+ ```python
21
+ from agrigee_lite_client import AgriGEEClient
22
+ from shapely.geometry import Polygon
23
+
24
+ geometry = Polygon([(-56.42, -11.20), (-56.41, -11.20), (-56.41, -11.19)])
25
+
26
+ with AgriGEEClient("http://192.168.3.204:8100") as client:
27
+ # a single geometry — naive: internally becomes a 1-row multiple_sits
28
+ df = client.get.sits(geometry, "2023-01-01", "2023-06-01", satellite="Sentinel2")
29
+
30
+ # multiple geometries — uploads a GeoParquet, polls the job, downloads the result parquet
31
+ import polars as pl
32
+ from agrigee_lite_client._geoparquet import encode_geometries
33
+
34
+ gdf = pl.DataFrame({
35
+ "start_date": ["2023-01-01", "2023-01-01"],
36
+ "end_date": ["2023-06-01", "2023-06-01"],
37
+ "geometry": encode_geometries([geometry, geometry]),
38
+ })
39
+ df = client.get.multiple_sits(gdf, satellite="Sentinel2", reducers=["mean", "std"])
40
+
41
+ # geopandas.GeoDataFrame also works (geopandas is optional — only
42
+ # imported if you actually pass one; it's not installed alongside the client)
43
+ import geopandas as gpd
44
+
45
+ gdf_geopandas = gpd.GeoDataFrame(
46
+ {"start_date": ["2023-01-01"], "end_date": ["2023-06-01"]},
47
+ geometry=[geometry],
48
+ crs="EPSG:4326",
49
+ )
50
+ df = client.get.multiple_sits(gdf_geopandas, satellite="Sentinel2")
51
+
52
+ # image — returns an in-memory zipfile.ZipFile, nothing decoded
53
+ zf = client.get.image(geometry, "2023-01-01", "2023-06-01", satellite="Sentinel2")
54
+ zf.extractall("out/")
55
+ ```
56
+
57
+ ## Quickstart — async
58
+
59
+ Same surface, `AsyncAgriGEEClient`:
60
+
61
+ ```python
62
+ import asyncio
63
+ from agrigee_lite_client import AsyncAgriGEEClient
64
+
65
+ async def main():
66
+ async with AsyncAgriGEEClient("http://192.168.3.204:8100") as client:
67
+ df = await client.get.sits(geometry, "2023-01-01", "2023-06-01", satellite="Sentinel2")
68
+
69
+ asyncio.run(main())
70
+ ```
71
+
72
+ ## Validation before anything leaves your machine
73
+
74
+ `sits()`/`multiple_sits()` check that the requested dates actually intersect
75
+ the satellite's operating period — the same check the server does — and
76
+ drop (with a warning) rows that don't, raising `ValueError` if none are
77
+ left. All of this happens **before** building the GeoParquet or opening a
78
+ connection, using a static table embedded in the client (no network call).
79
+ An unknown satellite name is also caught here (`AgriGEEUnknownSatelliteError`).
80
+
81
+ Since that table is a copy of what the server knows, the client checks that
82
+ it's running against the same server version (via `GET /version`) before
83
+ trusting it — a mismatch raises `AgriGEEVersionMismatchError` instead of
84
+ validating against possibly stale data.
85
+
86
+ ## What this client does NOT do (on purpose)
87
+
88
+ - No CRS reprojection — input geometries must be in WGS84 (EPSG:4326 /
89
+ OGC:CRS84). If you pass a `geopandas.GeoDataFrame` with a different CRS,
90
+ the client rejects it with a clear error instead of reprojecting.
91
+ - No local caching, no sophisticated retry/backoff — the cache already lives
92
+ on the server; the only "retry" here is `httpx`'s transport timeout and a
93
+ simple poll loop against `GET /jobs/{id}`.
94
+ - No image decoding — `get.image(...)` returns a raw `zipfile.ZipFile`, no
95
+ `numpy`/`tifffile`/`rasterio`.
96
+ - `get.sits(...)` isn't a separate fast path: it's sugar over
97
+ `get.multiple_sits(...)` with a single row, so it pays the job+poll+
98
+ download cost even for one geometry. See `SPECS.md §6`.
99
+
100
+ ## Versioning
101
+
102
+ This package's version always matches `agrigee_lite`'s (the server), same
103
+ commit — they aren't versioned independently. A test
104
+ (`tests/test_version_matches_server.py`) enforces this in CI.
105
+
106
+ ## Development
107
+
108
+ ```bash
109
+ cd agrigee_lite_client
110
+ pip install -e ".[dev]"
111
+ pytest
112
+ ruff check .
113
+ ```
114
+
115
+ `tests/test_client_against_live_api.py` only runs if `AGRIGEE_TEST_API_URL`
116
+ is set — the rest (`test_client_mocked.py`, `test_async_client_mocked.py`)
117
+ use `httpx.MockTransport` and need no network.
@@ -0,0 +1,25 @@
1
+ """Thin HTTP client for the AgriGEE.lite REST API. See SPECS.md."""
2
+
3
+ from agrigee_lite_client._async_client import AsyncAgriGEEClient
4
+ from agrigee_lite_client._client import AgriGEEClient
5
+ from agrigee_lite_client._exceptions import (
6
+ AgriGEEClientError,
7
+ AgriGEEHTTPError,
8
+ AgriGEEJobError,
9
+ AgriGEEJobTimeoutError,
10
+ AgriGEEUnknownSatelliteError,
11
+ AgriGEEVersionMismatchError,
12
+ )
13
+ from agrigee_lite_client._version import __version__
14
+
15
+ __all__ = [
16
+ "AgriGEEClient",
17
+ "AgriGEEClientError",
18
+ "AgriGEEHTTPError",
19
+ "AgriGEEJobError",
20
+ "AgriGEEJobTimeoutError",
21
+ "AgriGEEUnknownSatelliteError",
22
+ "AgriGEEVersionMismatchError",
23
+ "AsyncAgriGEEClient",
24
+ "__version__",
25
+ ]