From c9526b27c38b94d554b5c286edc1830059625031 Mon Sep 17 00:00:00 2001 From: Ivor Bosloper Date: Wed, 14 Oct 2026 04:11:10 +0200 Subject: [PATCH 1/7] JP: read MAFF's own Fude Polygon downloads, adding 2025 and 2026 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + fiboa_cli/datasets/jp.py | 104 ++++++++++++++++-- .../convert/jp/jp_field_polygons_2024.parquet | Bin 87857 -> 0 bytes .../data-files/convert/jp/jp_fude_2026_13.zip | Bin 0 -> 26279 bytes tests/test_convert.py | 1 + 5 files changed, 98 insertions(+), 8 deletions(-) delete mode 100644 tests/data-files/convert/jp/jp_field_polygons_2024.parquet create mode 100644 tests/data-files/convert/jp/jp_fude_2026_13.zip diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d0f1d57..8fd4ae98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - FR: Editions now cover 2017-2024, mapped through one shared crop code list (https://fiboa.org/code/fr/fr.csv). - HR: Editions now cover 2011-2024. - IT-1: Reads the JRC's newest EuroCrops V2 release (`gpqtv202`). +- JP: Reads MAFF's own Fude Polygon downloads instead of Pacific Spatial's copy, which adds the 2025 and 2026 editions. - LV: - Editions now cover 2015-2025 from yearly data.gov.lv releases. - `block_id` is published from the field-block identifier and `crop:name` comes from the official code list. diff --git a/fiboa_cli/datasets/jp.py b/fiboa_cli/datasets/jp.py index d2174330..9f759efd 100644 --- a/fiboa_cli/datasets/jp.py +++ b/fiboa_cli/datasets/jp.py @@ -1,14 +1,32 @@ +import os +import shutil +import tempfile +import zipfile + +import geopandas as gpd +import pandas as pd +import pyogrio +import requests from vecorel_cli.conversion.duckdb import DuckDBBaseConverter from ..conversion.fiboa_converter import FiboaBaseConverter +API = "https://restapi.fude.maff.go.jp" +BASE = "https://opendata.fude.maff.go.jp" +# MAFF's CDN refuses requests that do not look like a browser +HEADERS = { + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126 Safari/537.36" +} +PREFECTURES = [f"{code:02d}" for code in range(1, 48)] + class JPConverter(DuckDBBaseConverter, FiboaBaseConverter): + # One zip of municipality GeoJSONs per prefecture; the URLs only work signed, see download_files variants = { - "2024": "https://data.source.coop/pacificspatial/field-polygon-jp/parquet/jp_field_polygons_2024.parquet", - "2023": "https://data.source.coop/pacificspatial/field-polygon-jp/parquet/jp_field_polygons_2023.parquet", - "2022": "https://data.source.coop/pacificspatial/field-polygon-jp/parquet/jp_field_polygons_2022.parquet", - "2021": "https://data.source.coop/pacificspatial/field-polygon-jp/parquet/jp_field_polygons_2021.parquet", + str(year): { + f"{BASE}/{year}/{year}_{pref}.zip": f"jp_fude_{year}_{pref}.zip" for pref in PREFECTURES + } + for year in range(2026, 2020, -1) } id = "jp" @@ -21,19 +39,20 @@ class JPConverter(DuckDBBaseConverter, FiboaBaseConverter): current conditions. Fude Polygons are created for the purpose of roughly indicating the locations of farmland. """ - provider = "Japanese Ministry of Agriculture, Forestry and Fisheries (MAFF, 農林水産省) " - attribution = "Fude Polygon Data (2021-2024). Japanese Ministry of Agriculture, Forestry and Fisheries. Processed by Pacific Spatial Solutions, Inc" + provider = "Japanese Ministry of Agriculture, Forestry and Fisheries (MAFF, 農林水産省) " + attribution = "出典:「筆ポリゴンデータ」(農林水産省)を加工して作成" license = "CC-BY-4.0" columns = { - "GEOM": "geometry", + "geometry": "geometry", "polygon_uuid": "id", - "land_type_en": "land_type_en", + "land_type": "land_type_en", "local_government_cd": "admin_local_code", "issue_year": "determination:datetime", } # SQL migrations (DuckDB converter): per-feature determination date from the issue year column_migrations = { + "land_type": "CASE land_type WHEN 100 THEN 'rice_field' WHEN 200 THEN 'field' END", "issue_year": "make_timestamp(CAST(issue_year AS INTEGER), 1, 1, 0, 0, 0) AT TIME ZONE 'UTC'", } missing_schemas = { @@ -42,3 +61,72 @@ class JPConverter(DuckDBBaseConverter, FiboaBaseConverter): "admin_local_code": {"type": "string"}, } } + + def select_variant(self, variant): + super().select_variant(variant) + # MAFF's terms ask for this wording, naming the edition and that it was processed + self.attribution = ( + f"出典:「筆ポリゴンデータ({self.variant}年度公開)」(農林水産省)を加工して作成" + ) + + def convert(self, output_file, cache=None, **kwargs): + self._parquet_dir = tempfile.mkdtemp(prefix="jp_fude_") + try: + # the zips must go through download_files, which DuckDBBaseConverter skips without a cache + cache = cache or self._parquet_dir + return super().convert(output_file, cache=cache, **kwargs) + finally: + shutil.rmtree(self._parquet_dir, ignore_errors=True) + + def download_files(self, uris, cache_folder=None, **kwargs): + cache_fs, cache_folder = self.get_cache(cache_folder) + missing = [ + uri + for uri, name in uris.items() + if "://" in uri and not cache_fs.exists(os.path.join(cache_folder, name)) + ] + signed = self.sign(missing) + uris = {signed.get(uri, uri): name for uri, name in uris.items()} + kwargs["client_kwargs"] = {"headers": HEADERS} + zips = super().download_files(uris, cache_folder, **kwargs) + return [self.to_parquet(path[0] if isinstance(path, tuple) else path) for path in zips] + + def sign(self, uris): + """The download URLs of the API, signed for 30 minutes, for the given unsigned URLs.""" + if not uris: + return {} + prefectures = [uri.rsplit("_", 1)[1].removesuffix(".zip") for uri in uris] + body = { + "issue_year": self.variant, + "pref_cd": prefectures, + "pref_name": self.prefecture_names(prefectures), + "city_cd": [], + "city_name": [], + } + response = requests.post( + f"{API}/download/lambda-download", json=body, headers=HEADERS, timeout=60 + ) + response.raise_for_status() + signed = {found["url"].split("?")[0]: found["url"] for found in response.json()["found"]} + return {uri: signed[uri] for uri in uris} + + def prefecture_names(self, codes): + url = f"{API}/getprefcitycd/lambda-getprefcitycd?issue_year={self.variant}" + response = requests.get(url, headers=HEADERS, timeout=60) + response.raise_for_status() + names = {p["pref_cd"]: p["pref_name"] for p in response.json()[0]} + return [names[code] for code in codes] + + def to_parquet(self, zip_path): + """One GeoParquet file of all municipality GeoJSONs in a prefecture zip.""" + target = os.path.join( + self._parquet_dir, os.path.basename(zip_path).replace(".zip", ".parquet") + ) + with zipfile.ZipFile(zip_path) as archive: + members = sorted(n for n in archive.namelist() if n.endswith(".json")) + frames = [pyogrio.read_dataframe(f"/vsizip/{zip_path}/{member}") for member in members] + gdf = gpd.GeoDataFrame(pd.concat(frames, ignore_index=True), crs=frames[0].crs) + # each geometry declares JGD2011, which GDAL ignores + gdf = gdf.set_crs(6668, allow_override=True) + gdf.drop(columns=["history"]).to_parquet(target) + return target diff --git a/tests/data-files/convert/jp/jp_field_polygons_2024.parquet b/tests/data-files/convert/jp/jp_field_polygons_2024.parquet deleted file mode 100644 index 384fa415c83605b857ad4e8b97b7eca4eada9f84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87857 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

HLVVbXC^6p^wfUT~#U62BM54|)w3ZA!Sca|i0 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 diff --git a/tests/data-files/convert/jp/jp_fude_2026_13.zip b/tests/data-files/convert/jp/jp_fude_2026_13.zip new file mode 100644 index 0000000000000000000000000000000000000000..b66c82c0c9604a04832fb998c0c1d6db3c4b87cb GIT binary patch literal 26279 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 y*^hT}MHy%qT-g7AQyIv Date: Sat, 17 Oct 2026 01:02:20 +0200 Subject: [PATCH 2/7] JP: sign only MAFF's own URLs; tests for the download path Co-Authored-By: Claude Opus 5.5 --- fiboa_cli/datasets/jp.py | 2 +- tests/test_convert_jp.py | 135 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 tests/test_convert_jp.py diff --git a/fiboa_cli/datasets/jp.py b/fiboa_cli/datasets/jp.py index 9f759efd..bea302e6 100644 --- a/fiboa_cli/datasets/jp.py +++ b/fiboa_cli/datasets/jp.py @@ -83,7 +83,7 @@ def download_files(self, uris, cache_folder=None, **kwargs): missing = [ uri for uri, name in uris.items() - if "://" in uri and not cache_fs.exists(os.path.join(cache_folder, name)) + if uri.startswith(BASE) and not cache_fs.exists(os.path.join(cache_folder, name)) ] signed = self.sign(missing) uris = {signed.get(uri, uri): name for uri, name in uris.items()} diff --git a/tests/test_convert_jp.py b/tests/test_convert_jp.py new file mode 100644 index 00000000..7628e9c8 --- /dev/null +++ b/tests/test_convert_jp.py @@ -0,0 +1,135 @@ +"""Tests for the JP converter's download path. + +MAFF's download URLs only work signed, so the converter asks the API to sign the files +that are not cached yet. These tests answer the API with a fake service and record +what reaches the regular download. +""" + +import shutil + +import pytest +from vecorel_cli.conversion.base import BaseConverter + +from fiboa_cli.datasets import jp + +FIXTURE = "tests/data-files/convert/jp/jp_fude_2026_13.zip" +NAMES = {"01": "北海道", "13": "東京都"} + + +class FakeAPI: + """Signs every requested prefecture and records the requests.""" + + def __init__(self): + self.posts = [] + self.gets = [] + + def get(self, url, headers=None, **kwargs): + self.gets.append((url, headers)) + return FakeResponse([[{"pref_cd": c, "pref_name": n} for c, n in NAMES.items()]]) + + def post(self, url, json=None, headers=None, **kwargs): + self.posts.append((url, json, headers)) + found = [ + {"url": f"{jp.BASE}/{json['issue_year']}/{json['issue_year']}_{code}.zip?sig=x"} + for code in json["pref_cd"] + ] + return FakeResponse({"found": found}) + + +class FakeResponse: + def __init__(self, body): + self.body = body + + def raise_for_status(self): + pass + + def json(self): + return self.body + + +@pytest.fixture +def setup(monkeypatch, tmp_path): + api = FakeAPI() + monkeypatch.setattr(jp.requests, "get", api.get) + monkeypatch.setattr(jp.requests, "post", api.post) + + downloads = [] + + def download_files(self, uris, cache_folder=None, **kwargs): + downloads.append((dict(uris), kwargs)) + paths = [] + for name in uris.values(): + path = tmp_path / "cache" / name + if not path.exists(): + shutil.copy(FIXTURE, path) + paths.append(str(path)) + return paths + + monkeypatch.setattr(BaseConverter, "download_files", download_files) + (tmp_path / "cache").mkdir() + + converter = jp.JPConverter() + converter.select_variant("2026") + converter._parquet_dir = str(tmp_path) + return converter, api, downloads, tmp_path / "cache" + + +def maff(pref): + return f"{jp.BASE}/2026/2026_{pref}.zip" + + +def test_signs_the_missing_files_only(setup): + converter, api, downloads, cache = setup + shutil.copy(FIXTURE, cache / "jp_fude_2026_01.zip") + uris = {maff("01"): "jp_fude_2026_01.zip", maff("13"): "jp_fude_2026_13.zip"} + + paths = converter.download_files(uris, str(cache)) + + assert len(api.posts) == 1 + url, body, headers = api.posts[0] + assert url.endswith("/download/lambda-download") + assert body["issue_year"] == "2026" + assert body["pref_cd"] == ["13"] + assert body["pref_name"] == ["東京都"] + assert headers == jp.HEADERS + forwarded, kwargs = downloads[0] + assert forwarded == { + maff("01"): "jp_fude_2026_01.zip", + f"{maff('13')}?sig=x": "jp_fude_2026_13.zip", + } + assert kwargs["client_kwargs"] == {"headers": jp.HEADERS} + assert [p.split("/")[-1] for p in paths] == [ + "jp_fude_2026_01.parquet", + "jp_fude_2026_13.parquet", + ] + + +def test_signs_everything_when_nothing_is_cached(setup): + converter, api, downloads, cache = setup + uris = {maff("01"): "jp_fude_2026_01.zip", maff("13"): "jp_fude_2026_13.zip"} + + converter.download_files(uris, str(cache)) + + assert api.posts[0][1]["pref_cd"] == ["01", "13"] + assert api.posts[0][1]["pref_name"] == ["北海道", "東京都"] + assert all("?sig=x" in uri for uri in downloads[0][0]) + + +def test_a_complete_cache_needs_no_api(setup): + converter, api, downloads, cache = setup + shutil.copy(FIXTURE, cache / "jp_fude_2026_13.zip") + + converter.download_files({maff("13"): "jp_fude_2026_13.zip"}, str(cache)) + + assert api.posts == [] and api.gets == [] + assert downloads[0][0] == {maff("13"): "jp_fude_2026_13.zip"} + + +def test_other_urls_are_not_signed(setup): + converter, api, downloads, cache = setup + mirror = "https://mirror.example/tokyo.zip" + + converter.download_files({mirror: "jp_fude_2026_13.zip"}, str(cache)) + + assert api.posts == [] and api.gets == [] + assert downloads[0][0] == {mirror: "jp_fude_2026_13.zip"} From 60db6e108d6147c0237c7b68bd4716e5528ec12b Mon Sep 17 00:00:00 2001 From: Ivor Bosloper Date: Sat, 24 Oct 2026 00:05:11 +0200 Subject: [PATCH 3/7] JP: browser headers through request_headers, no convert override Co-Authored-By: Claude Opus 5.5 --- fiboa_cli/datasets/jp.py | 30 ++++++++++++------------------ tests/test_convert_jp.py | 5 ++--- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/fiboa_cli/datasets/jp.py b/fiboa_cli/datasets/jp.py index bea302e6..f2b5e574 100644 --- a/fiboa_cli/datasets/jp.py +++ b/fiboa_cli/datasets/jp.py @@ -1,6 +1,4 @@ import os -import shutil -import tempfile import zipfile import geopandas as gpd @@ -42,6 +40,7 @@ class JPConverter(DuckDBBaseConverter, FiboaBaseConverter): provider = "Japanese Ministry of Agriculture, Forestry and Fisheries (MAFF, 農林水産省) " attribution = "出典:「筆ポリゴンデータ」(農林水産省)を加工して作成" license = "CC-BY-4.0" + request_headers = HEADERS columns = { "geometry": "geometry", @@ -69,15 +68,6 @@ def select_variant(self, variant): f"出典:「筆ポリゴンデータ({self.variant}年度公開)」(農林水産省)を加工して作成" ) - def convert(self, output_file, cache=None, **kwargs): - self._parquet_dir = tempfile.mkdtemp(prefix="jp_fude_") - try: - # the zips must go through download_files, which DuckDBBaseConverter skips without a cache - cache = cache or self._parquet_dir - return super().convert(output_file, cache=cache, **kwargs) - finally: - shutil.rmtree(self._parquet_dir, ignore_errors=True) - def download_files(self, uris, cache_folder=None, **kwargs): cache_fs, cache_folder = self.get_cache(cache_folder) missing = [ @@ -87,9 +77,11 @@ def download_files(self, uris, cache_folder=None, **kwargs): ] signed = self.sign(missing) uris = {signed.get(uri, uri): name for uri, name in uris.items()} - kwargs["client_kwargs"] = {"headers": HEADERS} zips = super().download_files(uris, cache_folder, **kwargs) - return [self.to_parquet(path[0] if isinstance(path, tuple) else path) for path in zips] + return [ + self.to_parquet(path[0] if isinstance(path, tuple) else path, cache_folder) + for path in zips + ] def sign(self, uris): """The download URLs of the API, signed for 30 minutes, for the given unsigned URLs.""" @@ -117,11 +109,13 @@ def prefecture_names(self, codes): names = {p["pref_cd"]: p["pref_name"] for p in response.json()[0]} return [names[code] for code in codes] - def to_parquet(self, zip_path): - """One GeoParquet file of all municipality GeoJSONs in a prefecture zip.""" - target = os.path.join( - self._parquet_dir, os.path.basename(zip_path).replace(".zip", ".parquet") - ) + def to_parquet(self, zip_path, cache_folder): + """One GeoParquet file of all municipality GeoJSONs in a prefecture zip, kept in the cache.""" + stem = os.path.splitext(os.path.basename(zip_path))[0] + target = os.path.join(cache_folder, f"extracted.{stem}", f"{stem}.parquet") + if os.path.exists(target): + return target + os.makedirs(os.path.dirname(target), exist_ok=True) with zipfile.ZipFile(zip_path) as archive: members = sorted(n for n in archive.namelist() if n.endswith(".json")) frames = [pyogrio.read_dataframe(f"/vsizip/{zip_path}/{member}") for member in members] diff --git a/tests/test_convert_jp.py b/tests/test_convert_jp.py index 7628e9c8..43137f8a 100644 --- a/tests/test_convert_jp.py +++ b/tests/test_convert_jp.py @@ -70,7 +70,6 @@ def download_files(self, uris, cache_folder=None, **kwargs): converter = jp.JPConverter() converter.select_variant("2026") - converter._parquet_dir = str(tmp_path) return converter, api, downloads, tmp_path / "cache" @@ -92,12 +91,12 @@ def test_signs_the_missing_files_only(setup): assert body["pref_cd"] == ["13"] assert body["pref_name"] == ["東京都"] assert headers == jp.HEADERS - forwarded, kwargs = downloads[0] + forwarded, _ = downloads[0] assert forwarded == { maff("01"): "jp_fude_2026_01.zip", f"{maff('13')}?sig=x": "jp_fude_2026_13.zip", } - assert kwargs["client_kwargs"] == {"headers": jp.HEADERS} + assert converter.request_headers == jp.HEADERS assert [p.split("/")[-1] for p in paths] == [ "jp_fude_2026_01.parquet", "jp_fude_2026_13.parquet", From 40ded3892ec258f4f30846919121986b91607360 Mon Sep 17 00:00:00 2001 From: Ivor Bosloper Date: Sat, 24 Oct 2026 22:13:44 +0200 Subject: [PATCH 4/7] JP tests: compare file names, not '/'-split paths, for Windows Co-Authored-By: Claude Opus 5.5 --- tests/test_convert_jp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_convert_jp.py b/tests/test_convert_jp.py index 43137f8a..2445e4c8 100644 --- a/tests/test_convert_jp.py +++ b/tests/test_convert_jp.py @@ -5,6 +5,7 @@ what reaches the regular download. """ +import os import shutil import pytest @@ -97,7 +98,7 @@ def test_signs_the_missing_files_only(setup): f"{maff('13')}?sig=x": "jp_fude_2026_13.zip", } assert converter.request_headers == jp.HEADERS - assert [p.split("/")[-1] for p in paths] == [ + assert [os.path.basename(p) for p in paths] == [ "jp_fude_2026_01.parquet", "jp_fude_2026_13.parquet", ] From 794fcf917f379b845302b7ff34455d33e9e5c31d Mon Sep 17 00:00:00 2001 From: Ivor Bosloper Date: Sun, 25 Oct 2026 16:23:52 +0100 Subject: [PATCH 5/7] Update vecorel-cli to 0.4.1 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 +++- pixi.lock | 58 +++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed4639ea..9b39ed7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,7 +50,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - `fiboa publish` no longer uploads to S3 or generates README/LICENSE files. It now creates GeoParquet, PMTiles and a STAC Collection with relative links, checksums and web-map-links. - Updated `aiohttp` to support Zenodo responses that include both returned `Content-Type` headers. - Improved geometry axis handling so generated tiles and bounding boxes keep x/y order consistent in output. -- Updated vecorel-cli to 0.4.0: +- Updated vecorel-cli to 0.4.1: + - A converter can set `request_headers` for its downloads, e.g. a browser User-Agent. + - The DuckDB converter downloads archives and other sources it cannot read in place also without a cache, and no longer turns `key=value` folders in a path into columns. - GeoParquet files under 250,000 rows get at least ten row groups, so a reader can skip by area. - `create-stac-collection` takes the bbox from the geometries, in EPSG:4326. - **Breaking:** `fiboa merge` keeps all properties by default (`--include`/`--exclude` restrict them), fails if the merged file would be invalid (`--no-strict` warns instead), and refuses datasets with different specification or extension versions. diff --git a/pixi.lock b/pixi.lock index 0b111fa5..246e6601 100644 --- a/pixi.lock +++ b/pixi.lock @@ -176,7 +176,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: ./ osx-64: @@ -340,7 +340,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: ./ osx-arm64: @@ -505,7 +505,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: ./ win-64: @@ -668,7 +668,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl - pypi: ./ @@ -823,7 +823,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: ./ osx-64: @@ -962,7 +962,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: ./ osx-arm64: @@ -1102,7 +1102,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: ./ win-64: @@ -1241,7 +1241,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl - pypi: ./ @@ -1358,7 +1358,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: ./ osx-64: @@ -1460,7 +1460,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: ./ osx-arm64: @@ -1563,7 +1563,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: ./ win-64: @@ -1663,7 +1663,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl - pypi: ./ @@ -1807,7 +1807,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: ./ osx-64: @@ -1936,7 +1936,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: ./ osx-arm64: @@ -2066,7 +2066,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: ./ win-64: @@ -2192,7 +2192,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl - pypi: ./ @@ -2335,7 +2335,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/0c/a71501bbc1a674ff72c4d6c2b75f4d9a5af819f5244c3a7558080a8802c5/yarl-1.25.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: ./ @@ -2464,7 +2464,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/55/e6/cd145cff8e5cf60b8b3c41fbecfa2a45028a8dec3fbc52bec03595ff3d3b/yarl-1.25.1-cp311-cp311-macosx_10_9_x86_64.whl - pypi: ./ @@ -2593,7 +2593,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ce/7c/fbb40fe2d53747c40aa36a9e2bd2178a202f942bda0b670f3306d4aefbce/yarl-1.25.1-cp311-cp311-macosx_11_0_arm64.whl - pypi: ./ @@ -2719,7 +2719,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/9d/8cdceec66a9b940700cb45931741403f045b162afd79bcb93c41cadd0972/yarl-1.25.1-cp311-cp311-win_amd64.whl @@ -2862,7 +2862,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/be/415b00c0fe5a0615b062a456b26623d7ec91c2bee20faea1a14045aa0469/yarl-1.25.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: ./ @@ -2992,7 +2992,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/a8/e6c1be0e6761d0f2d10bbf33a3e1e02b99dc83874d92945d7b461a72481e/yarl-1.25.1-cp314-cp314-macosx_10_15_x86_64.whl - pypi: ./ @@ -3122,7 +3122,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/bb/dda344765ffd3430afe1a1c66c866a57fae67786537d4f14607df6505ac1/yarl-1.25.1-cp314-cp314-macosx_11_0_arm64.whl - pypi: ./ @@ -3249,7 +3249,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/01/349c0386caedbbe488d519f252df54efac8a1459282d466c474bdd84a620/yarl-1.25.1-cp314-cp314-win_amd64.whl @@ -4765,9 +4765,9 @@ packages: - pypi: ./ name: fiboa-cli version: 0.21.0 - sha256: 0bc20c88d77a364347f871a2c3f14049f11fe421a5e2b321e013435351ada440 + sha256: cc05f5ea116fe87497bf02339b7f9bf7546850874c5fdd2d7da25d109dd97f56 requires_dist: - - vecorel-cli==0.4.0 + - vecorel-cli==0.4.1 - beautifulsoup4>=4.12 - spdx-license-list==3.27.0 requires_python: '>=3.11' @@ -13091,10 +13091,10 @@ packages: purls: [] size: 155910 timestamp: 1785359349999 -- pypi: https://files.pythonhosted.org/packages/6d/0b/10bac3a0771493d8582235a17206109af678e8177e2e85464c79e7ead13f/vecorel_cli-0.4.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/6d/81/ad34b5cb65bed8ac70e1eb4fb19a9978ef5bab5b94c2261e053d161ff137/vecorel_cli-0.4.1-py3-none-any.whl name: vecorel-cli - version: 0.4.0 - sha256: 725e479bd55635719e05b905d08a0106c2f3a9c0105c423d9986b444368fae93 + version: 0.4.1 + sha256: 7d08ad5a1193ac8c25d16da13cbf78dc441ce8b5746427979dfe7a15b7bd27d9 requires_dist: - pyyaml>=6.0,<7.0 - click>=8.1,<9.0 diff --git a/pyproject.toml b/pyproject.toml index d6cb60eb..a97c66bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ ] requires-python = ">=3.11" dependencies = [ - "vecorel-cli==0.4.0", + "vecorel-cli==0.4.1", "beautifulsoup4>=4.12", "spdx-license-list==3.27.0", ] From 39ddf835c05589984bc61e19be12ea2d6c3a4dd3 Mon Sep 17 00:00:00 2001 From: Ivor Date: Sun, 25 Oct 2026 21:05:39 +0100 Subject: [PATCH 6/7] Improvements from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- fiboa_cli/datasets/jp.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/fiboa_cli/datasets/jp.py b/fiboa_cli/datasets/jp.py index f2b5e574..07026765 100644 --- a/fiboa_cli/datasets/jp.py +++ b/fiboa_cli/datasets/jp.py @@ -70,18 +70,15 @@ def select_variant(self, variant): def download_files(self, uris, cache_folder=None, **kwargs): cache_fs, cache_folder = self.get_cache(cache_folder) - missing = [ - uri - for uri, name in uris.items() - if uri.startswith(BASE) and not cache_fs.exists(os.path.join(cache_folder, name)) - ] - signed = self.sign(missing) - uris = {signed.get(uri, uri): name for uri, name in uris.items()} - zips = super().download_files(uris, cache_folder, **kwargs) - return [ - self.to_parquet(path[0] if isinstance(path, tuple) else path, cache_folder) - for path in zips - ] + paths = [] + for uri, name in uris.items(): + if uri.startswith(BASE) and not cache_fs.exists(os.path.join(cache_folder, name)): + uri = self.sign([uri])[uri] + for path in super().download_files({uri: name}, cache_folder, **kwargs): + paths.append( + self.to_parquet(path[0] if isinstance(path, tuple) else path, cache_folder) + ) + return paths def sign(self, uris): """The download URLs of the API, signed for 30 minutes, for the given unsigned URLs.""" @@ -122,5 +119,7 @@ def to_parquet(self, zip_path, cache_folder): gdf = gpd.GeoDataFrame(pd.concat(frames, ignore_index=True), crs=frames[0].crs) # each geometry declares JGD2011, which GDAL ignores gdf = gdf.set_crs(6668, allow_override=True) - gdf.drop(columns=["history"]).to_parquet(target) + temporary_target = target + ".part" + gdf.drop(columns=["history"]).to_parquet(temporary_target) + os.replace(temporary_target, target) return target From b70655d318a535c160ad5b561e6aaf6e887d0d8d Mon Sep 17 00:00:00 2001 From: Ivor Bosloper Date: Sun, 25 Oct 2026 21:26:30 +0100 Subject: [PATCH 7/7] JP tests: one signature per file, just before its download Co-Authored-By: Claude Opus 5.5 --- tests/test_convert_jp.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/test_convert_jp.py b/tests/test_convert_jp.py index 2445e4c8..c40a473d 100644 --- a/tests/test_convert_jp.py +++ b/tests/test_convert_jp.py @@ -92,7 +92,7 @@ def test_signs_the_missing_files_only(setup): assert body["pref_cd"] == ["13"] assert body["pref_name"] == ["東京都"] assert headers == jp.HEADERS - forwarded, _ = downloads[0] + forwarded = {uri: name for batch, _ in downloads for uri, name in batch.items()} assert forwarded == { maff("01"): "jp_fude_2026_01.zip", f"{maff('13')}?sig=x": "jp_fude_2026_13.zip", @@ -110,9 +110,13 @@ def test_signs_everything_when_nothing_is_cached(setup): converter.download_files(uris, str(cache)) - assert api.posts[0][1]["pref_cd"] == ["01", "13"] - assert api.posts[0][1]["pref_name"] == ["北海道", "東京都"] - assert all("?sig=x" in uri for uri in downloads[0][0]) + # each file is signed just before its download, as a signature lasts 30 minutes + assert [body["pref_cd"] for _, body, _ in api.posts] == [["01"], ["13"]] + assert [body["pref_name"] for _, body, _ in api.posts] == [["北海道"], ["東京都"]] + assert [list(batch) for batch, _ in downloads] == [ + [f"{maff('01')}?sig=x"], + [f"{maff('13')}?sig=x"], + ] def test_a_complete_cache_needs_no_api(setup):