Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

argus-atlas

PyPI Python License: MIT CI

Bake one georeferenced fantasy-map overlay from real terrain.

argus-atlas warps a DEM into hillshade, slope, and land-mask control rasters with GDAL, restyles them through a diffusion backend with a cartography LoRA, stamps the painted pixels back onto the original geotransform, and cuts a PMTiles archive you can drape on a real map.

Albany, Western Australia: DEM hillshade, the OSM composite, and the baked antique map, same extent

King George Sound at Albany, Western Australia: the Copernicus GLO-30 hillshade, the composite control (hillshade plus OpenStreetMap water, forest and roads, with the sea taken from the DEM), and the bake. Same 1024x1024 canvas at 33 m/px, native zoom 12. Preset antique, seed 7, 30 steps, cfg 6, denoise 0.6, Juggernaut XL, no LoRA. The prompt, read back from the PNG's own EXIF:

antique fantasy map, ink coastlines, parchment, illustrated relief, blue water, no text, no labels, no compass rose

negative: text, watermark, letters, words, frame, border, photograph, satellite

The overlay fading from the painted map to the real basemap and back: coastline and harbours stay put

The GIF is examples/viewer.html sliding the overlay's opacity over an OpenFreeMap basemap. The coastline, the sound and the harbours do not move, because the model only ever painted pixels inside a frame GDAL had already georeferenced.

Status: v0, first alpha being prepared. Real AOIs have passed the alignment gate (Lake Garda on 17 Oct 2026; four Western Australian AOIs on 23 Oct); see docs/first-bake.md.

Part of the Argus suite.

Why this exists

Every piece of this pipeline already exists somewhere. No package puts them together, and none of them carries coordinates through the generative step. That gap is the product.

What argus-atlas guarantees Why nothing on PyPI does it today
Georeferencing survives generation. The job sidecar is the coordinate authority. It is written before the model runs and checked after. An output that is one pixel off, or in the wrong CRS, is refused and no GeoTIFF is written. ComfyUI workflows and ControlNet research code (generative-ai-mapmaking, map-sat, "Fantasy Maps from Height Maps") produce PNGs. Coordinates go in, pixels come out, and nothing reattaches the two.
Provenance in the manifest. Every job records the SHA256 of the source DEM, of each control PNG, and of the checkpoint and LoRA, plus the exact GDAL argv that ran. Terrain tools (xdem, richdem, nst-guide/terrain) write rasters and stop. ComfyUI clients (pycomfy, comfy-commander, comfy-api-client) submit graphs and return images. Neither side keeps a record you could reproduce from.
Tiles bounded to the AOI and capped at native resolution. The archive never contains a tile outside the bounding box and never invents a zoom finer than the canvas. rio-pmtiles cuts tiles from any GeoTIFF but will happily over-zoom a 1024-pixel canvas. gdal2tiles and gdal raster tile emit XYZ directories with no PMTiles packer. rio-mbtiles and mbutil are unmaintained.
Canvas sized for Web Mercator, not for the model. The AOI is fitted to a canvas whose long side is at most 1536 and whose sides are multiples of 64. If the AOI cannot fit at the requested resolution, the job fails and tells you the largest extent that would. Diffusion front-ends size images for the sampler. Nothing resamples with the ground resolution and the resulting Web Mercator zoom in mind, and nothing refuses to silently downsample a continent into 1024 pixels.
Demo-safe by default. Baking is off unless ATLAS_ALLOW_BAKE=1. Stored jobs, previews, and archives stay readable. Paths in API requests are confined to configured roots. Hosted generation wrappers either expose the GPU to anyone who can reach the port, or hide finished work when the GPU is gone.
CLI first, server second. controls, bake, and tiles run with no HTTP server and no studio. The FastAPI app is a thin wrapper for argus-studio. Most of the comparable work is a notebook, a QGIS print layout, or a node graph that only runs inside a UI.
Weights are mounted, never bundled. The image contains GDAL and Python. The checkpoint and LoRA live on the host and are hashed into the job. pycomfy downloads checkpoints on demand. imaginAIry runs the model in-process and does not support SDXL ControlNet.

The nearest off-the-shelf neighbours and where they stop:

  • Terrain analysis: xdem (active) and richdem (last release 2018) compute hillshade and slope as arrays. No 8-bit PNG export for a model, no sidecar, no canvas constraints.
  • Tiling: rio-pmtiles (active, Protomaps) is the only maintained GeoTIFF-to-PMTiles route and is a candidate for the tiles extra. It has no alignment gate and no native-zoom ceiling. GDAL's own PMTiles driver is vector-only.
  • ComfyUI clients: none implement a placeholder contract where a missing placeholder is an error that names it, and none know what a hillshade is.
  • Procedural generators: worldengine renders "ancient style" maps, but only from its own simulated worlds, never from a real DEM.
  • Research code: generative-ai-mapmaking trains an SD 1.5 ControlNet on Swisstopo vector tiles. Scripts only, no DEM input, no georeferencing after generation, seams left to the reader.

OSM layers and the composite

The hillshade tells the model where relief is, not where water, forest, towns or roads are. argus-atlas osm fetches those from OpenStreetMap for the same bbox, scale-aware (at zoom 11 you get motorways and primaries, not footpaths), and controls --layer burns each one to a mask on the canvas grid. The known layers are also painted over the hillshade into composite.png; the packaged comfyui_sdxl_img2img_composite_nolora template uses that as the img2img init image, so lakes come out blue and forests green while the ControlNet keeps the ridges where the DEM put them.

argus-atlas osm --bbox 10.55,45.40,10.95,45.90 --zoom 11 --layers water,rivers,roads,forest,urban --out osm/garda
argus-atlas controls --dem dem.tif --bbox 10.55,45.40,10.95,45.90 --out jobs/garda-2 \
  --layer water=osm/garda/water.geojson --layer rivers=osm/garda/rivers.geojson \
  --layer roads=osm/garda/roads.geojson --layer forest=osm/garda/forest.geojson --layer urban=osm/garda/urban.geojson
ATLAS_ALLOW_BAKE=1 argus-atlas bake jobs/garda-2 --checkpoint <ckpt> --workflow comfyui_sdxl_img2img_composite_nolora.json

Any OGR-readable file works as a layer, in any CRS; a name outside the palette is burnt as a mask only and is available to a template as $<name>. Where to get such files (HydroSHEDS, Natural Earth, Geofabrik extracts, GADM, land cover …) is in docs/data-sources.md. OSM data is ODbL; osm.json records the query and the data timestamp so the attribution can be traced.

Presets and provenance

argus-atlas presets lists six named styles (antique, imhof, tolkien, watercolor, cyberpunk, minecraft, plus pixel) and bake --preset <name> uses one; the API has GET /presets and takes preset on the bake request, so a demo can offer a fixed menu instead of free-text prompts. Every output carries the full request: the PNG in an EXIF ImageDescription and a text chunk, the GeoTIFF in TIFFTAG_IMAGEDESCRIPTION, the PMTiles archive in its metadata. exiftool fantasy.png, gdalinfo fantasy.tif or argus_atlas.metadata.read_png_payload() give it back, which is what makes a hundred bakes rankable later.

Chunks and style recipes

To grow a map piece by piece, bake it one XYZ tile at a time. argus-atlas chunk --tile z/x/y --recipe <style> --dem <dem> runs controls, seams, bake, crop and tiles in one go:

  • The grid comes from the tile id. A chunk is painted at 256, 512 or 1024 px, so its native zoom is z, z+1 or z+2 and every tile in its pyramid is a whole XYZ tile. All chunks of one size share one pixel lattice.
  • Seams continue the neighbours. The canvas is the tile plus a margin (default 128 px, a multiple of 32). Chunks already baked from the same recipe next to it (under --neighbours, by default the parent of the job dir) are pasted into the margin of a seam_init control. A seam_mask control marks those pixels as kept. The packaged comfyui_sdxl_chunk_seam*.json templates denoise only the unmasked area, so the model paints up to the tile edge while it sees the neighbour across it. A chunk job re-baked with bake or POST /bake/stream no longer counts as the recipe's, and a chunk of the recipe that would overlap one already baked (the same tile in another job dir, or another zoom over the same ground) is refused.
  • Match, then crop. The mask fades over 32 px on the neighbours' side (--mask-feather), so there is no hard latent edge. After the bake, the chunk's colours are shifted to match the neighbours along the seam (--tone), and what difference is left at the seam is faded out over 64 px into the tile (--feather). The painted fantasy.tif is kept as it came from the model; the crop comes from fantasy_blend.tif.
  • Crop, then gate again. After the alignment gate the margin is cut off (fantasy_chunk.tif) and the crop is gated against the tile's own grid. Only the crop is tiled, from zoom z to native, so the archives of neighbouring chunks never overlap.
  • A recipe is a style you can repeat. It is a JSON file (see examples/recipes/antique.json) naming a preset, checkpoint, optional LoRA, workflow and a seed scheme. With tile seeds, each chunk's seed is a hash of the recipe name, a base and z/x/y, so baking a chunk again gives the same image. --recipe <name> looks up $ATLAS_RECIPES_DIR/<name>.json. The whole recipe and its digest go into job.json.
  • The workflow comes from the recipe. chunk bakes with the recipe's workflow (unset: the packaged seam template matching its LoRA), never with --workflow or ATLAS_WORKFLOW, which name non-seam templates. Like the other packaged templates, the seam templates name the placeholder ControlNet diffusers_xl_depth_full.safetensors: on a host without it, copy one, edit its ControlNetLoader and set the recipe's workflow to the copy (a path relative to the recipe file).
  • Every stage is timed. job.json records wall-clock seconds for controls, seams, verify, generate, georeference, crop and tiles, so a bake queue can be sized from real runs.

--controls-only stops after the seam controls and needs no GPU.

Live demo

A hosted demo will be linked here. Until then, examples/flow.html runs the whole flow against a local server (AOI, OSM layers, controls, bake, tiles, drape) and examples/viewer.html drapes any baked job on a MapLibre basemap with an opacity slider: start argus-atlas serve --cors (with ATLAS_CORS_ORIGINS='*') and open the page, or feed it a fantasy.pmtiles directly. The first real AOI (Lake Garda, Copernicus GLO-30, Juggernaut XL, no LoRA) passed the alignment gate on 17 Oct 2026; the commands are in docs/first-bake.md.

Quick start

GDAL is a system dependency, not a pip module. Install gdal-bin (Debian and Ubuntu), gdal (Fedora, Arch, Homebrew), or use the Docker image, which is built on ghcr.io/osgeo/gdal:ubuntu-small-3.11.4 and already contains GDAL 3.11 and gdal2tiles.py.

uv pip install "argus-atlas[cli,tiles]"
argus-atlas doctor

Healthy doctor output names a GDAL version, lists gdalwarp, gdaldem, gdal_rasterize, gdal_translate, gdalinfo and the tiler it found, reports whether ComfyUI is reachable and whether baking is allowed, and says whether the Go pmtiles CLI is on PATH (informational only; packing uses the Python library). If GDAL is missing it says so and exits non-zero. Paste this output into any issue you open.

Then run the no-GPU half of the pipeline on the bundled sample DEM (examples/sample_dem.tif, southern Lake Garda from Copernicus GLO-30, 577x577 cells, 350 KB; in the source checkout or the sdist, not the wheel):

argus-atlas controls \
  --dem examples/sample_dem.tif \
  --bbox 10.60,45.44,10.76,45.60 \
  --out ./jobs/sample

argus-atlas inspect ./jobs/sample       # sidecar: geotransform, canvas size, SHA256s, GDAL argv

Open jobs/sample/hillshade.png. If the relief looks right, the georeferencing is already fixed; the bake only changes pixel colours inside that frame.

Three ways to bake

You have Do this
A local GPU with ComfyUI running ATLAS_ALLOW_BAKE=1 argus-atlas bake ./jobs/sample --checkpoint ... --lora ... as in the operator workflow below. The packaged ComfyUI workflow template is the starting graph; drop in your own.
No GPU Run controls and tiles locally. A hosted notebook that runs the bake against a remote ComfyUI is planned (see TODO.md, Phase 4).
ComfyUI, and you want to own the graph Start from src/argus_atlas/templates/comfyui_sdxl_img2img_control.json, keep the $placeholders, and point ATLAS_WORKFLOW at your copy. bake submits it, then stamps and gates the result. A standalone command for stamping a PNG painted outside atlas is on the roadmap (see TODO.md).

Where it sits in the Argus suite

argus-quarry → argus-curator → argus-lens → argus-forge → trainer → argus-proof → argus-atlas
acquire          curate            caption      configs                  evaluate       bake

A cartography LoRA is trained with the existing suite and passed to atlas as a file. Atlas does not train, caption, or curate, and it does not modify the upstream packages.

Operator workflow

# Controls only (no GPU). Inspect the hillshade before spending a bake.
# --bbox is EPSG:4326 degrees. The --out directory name becomes the job id.
argus-atlas controls \
  --dem ./dem.tif \
  --bbox west,south,east,north \
  --out ./jobs/aoi-1

# Size the canvas either by its long side (default 1024, cap 1536, multiple
# of 64) or by an explicit ground resolution in metres per pixel. Too fine a
# resolution for the AOI fails and prints the largest --bbox that would fit.
argus-atlas controls --dem ./dem.tif --bbox west,south,east,north \
  --long-side 1536 --out ./jobs/aoi-1
argus-atlas controls --dem ./dem.tif --bbox west,south,east,north \
  --resolution 30 --out ./jobs/aoi-1

# Optional land mask, rasterised onto the same grid as the warped DEM.
argus-atlas controls \
  --dem ./dem.tif \
  --bbox west,south,east,north \
  --land ./land.gpkg \
  --out ./jobs/aoi-1

# Bake through a ComfyUI already running on the host GPU.
# Refuses with exit code 3 unless ATLAS_ALLOW_BAKE=1.
ATLAS_ALLOW_BAKE=1 argus-atlas bake ./jobs/aoi-1 \
  --checkpoint sd_xl_base_1.0.safetensors \
  --lora fantasy_map.safetensors --lora-weight 0.8

# Everything else has a default: --prompt, --negative, --seed (random if
# omitted), --steps 30, --cfg 6.0, --denoise 0.55, --models-dir, --workflow,
# --comfyui-url. Pass --out to bake a variant into a copy of the job dir
# and keep the original.
ATLAS_ALLOW_BAKE=1 argus-atlas bake ./jobs/aoi-1 -c sd_xl_base_1.0.safetensors \
  --lora fantasy_map.safetensors --seed 7 --denoise 0.6 --out ./jobs/aoi-1-d60

# Tiles are a separate step so a bad stylisation can be rejected before a
# pyramid is built. Takes the job dir or its fantasy.tif, re-runs the
# alignment gate, then cuts --min-zoom..--max-zoom (never above native).
argus-atlas tiles ./jobs/aoi-1 --out ./jobs/aoi-1/fantasy.pmtiles

# Print the sidecar: geotransform, canvas, hashes, GDAL argv, bake params.
argus-atlas inspect ./jobs/aoi-1

# Thin HTTP wrapper for argus-studio.
argus-atlas serve --port 8105 --cors

# Report GDAL version, ComfyUI reachability, and whether baking is allowed.
argus-atlas doctor

Exit codes: 0 success, 1 runtime failure, 2 input error, 3 baking disabled.

What a job contains

jobs/aoi-1/
  job.json          # geotransform, CRS, canvas size, SHA256s, GDAL argv  ← coordinate authority
  dem_3857.tif      # DEM warped to EPSG:3857 and clipped to the AOI
  hillshade.tif     # gdaldem hillshade
  slope.tif         # gdaldem slope
  land_mask.tif     # gdal_rasterize of the optional polygon file (only with --land)
  hillshade.png     # 8-bit model inputs, no georeferencing of their own
  slope.png
  land.png          # only with --land
  fantasy.png       # raw model output, kept even if the gate fails
  fantasy.tif       # written only after the alignment gate passes
  fantasy.pmtiles   # zoom range native-2 .. native, bounded to the AOI

Defaults

Knob Default Notes
Canvas long side 1024 Hard cap 1536. Both sides multiple of 64.
Denoise 0.55 High enough to restyle, low enough that relief survives.
Steps / CFG 30 / 6.0
Seed random Recorded in the sidecar so a bake can be repeated.
LoRA weight 0.8
Prompt antique fantasy map, ink coastlines, parchment, illustrated relief, no text, no labels, no compass rose Labels and roses fight georeferencing and tile seams.
Negative text, watermark, letters, words, frame, border, photograph, satellite
Zoom range native-2 through native, clipped to 0–14 Native is the Web Mercator zoom closest to the canvas ground resolution.

ControlNet choice and weight live in the ComfyUI workflow template, not in Python. The packaged template is a starting point the operator replaces.

The ComfyUI workflow template

src/argus_atlas/templates/comfyui_sdxl_img2img_control.json is an API-format graph with $placeholders that bake fills: $checkpoint, $lora, $lora_weight, $positive, $negative, $seed, $steps, $cfg, $denoise, $width, $height, $hillshade, and optionally $slope and $land. Point ATLAS_WORKFLOW or --workflow at your own copy to change the sampler, the ControlNet, or anything else. Two rules:

  • A placeholder the bake needs that is missing from the template is an error that names the placeholder.
  • A template that contains $lora requires --lora.

The starter template names an SDXL depth ControlNet model directly; replace that name with whatever your ComfyUI has installed.

The alignment gate

Before a GeoTIFF or any tile is written, all of the following must hold:

  • gdalinfo -json fantasy.tif width and height equal the control canvas.
  • SRS is EPSG:3857.
  • Corner coordinates match the sidecar within 1e-3 map units.

On failure the PNG is kept, no GeoTIFF is written, and the command exits non-zero.

When it fails

The three failures new users hit, and what to do:

Message Cause Fix
gdal not found from doctor or /health GDAL CLI is not on PATH Install the system package or use the Docker image.
--resolution too fine, largest --bbox that fits is printed Bounding box is too large for the 1536 cap at that ground resolution Use the --bbox the message gives you, or drop --resolution and let --long-side choose the resolution.
ComfyUI unreachable at ... Nothing listening on COMFYUI_BASE_URL Start ComfyUI on the host, or pass --comfyui-url. Inside Compose the default reaches the host via host.docker.internal.
bake exits 3 ATLAS_ALLOW_BAKE is not 1 Set it on purpose. The default is read-only so a demo box cannot be made to spend GPU time.

HTTP API

Port 8105, a peer of the other suite servers. CORS from ATLAS_CORS_ORIGINS, default http://localhost:3000.

Method and path Purpose
GET /health status, service, version, read-only flag, GDAL version
GET /doctor GDAL version, ComfyUI reachability, bake-allowed flag
POST /controls DEM name, bbox, optional land name → job id and sidecar. Names resolve under ATLAS_DEM_DIR only
POST /bake/stream NDJSON progress for an existing job. 403 when baking is disabled
POST /tiles job id → PMTiles, after the alignment gate has passed
GET /jobs job digests
GET /jobs/{id} sidecar and status
GET /jobs/{id}/preview/{kind} hillshade, slope, land, or fantasy. Ids only, no path input
GET /jobs/{id}/archive the PMTiles bytes

Progress events on the bake stream, in order: controls, generate, georeference, then done or error.

Env Default Role
ATLAS_JOBS_DIR /data/out/atlas Sidecars, controls, GeoTIFFs, PMTiles
ATLAS_DEM_DIR /data/dem Only DEMs under here can be named by the API
ATLAS_ALLOW_BAKE unset 1 enables POST /bake/stream
ATLAS_BACKEND comfyui Backend key
COMFYUI_BASE_URL http://host.docker.internal:8188 ComfyUI on the compose host
ATLAS_MODELS_DIR /data/models Checkpoints and LoRAs, read-only, SHA256 recorded per job
ATLAS_WORKFLOW packaged template API-format ComfyUI graph with $placeholders
ATLAS_CORS_ORIGINS http://localhost:3000 Browser origins

Install

uv pip install argus-atlas                 # library only
uv pip install "argus-atlas[cli]"          # typer, rich
uv pip install "argus-atlas[server]"       # fastapi, uvicorn, argus-cortex[server]
uv pip install "argus-atlas[tiles]"        # Python pmtiles writer; the packer for the XYZ directory
uv pip install "argus-atlas[dev]"          # pytest, httpx, ruff

Python 3.11 or newer. MIT. GDAL (gdal-bin, including gdal2tiles.py) is a system dependency; argus-atlas doctor and GET /health report its version and fail clearly when it is missing. PMTiles packing uses the Python pmtiles library from the tiles extra, because the Go pmtiles CLI cannot pack an XYZ directory; the Docker image therefore does not ship the Go CLI.

Develop

make install   # venv + editable install with the "dev,server,cli,tiles" extras
make test
make lint

Versioning is derived from git tags via hatch-vcs; tag vX.Y.Z to cut a release. CI is a standalone workflow that apt-installs gdal-bin and python3-gdal before running the suite, since the tests drive real GDAL on a synthetic DEM. Release publishes to PyPI (OIDC trusted publishing) and GHCR on v* tags. This repo was scaffolded from argus-pkg-template; run copier update to pull template changes.

The launch and onboarding plan lives in TODO.md. Agents working in this repo should read CLAUDE.md first.

Community and help

  • Bugs and feature requests: GitHub issues. Include your argus-atlas doctor output and the job.json of the failing job.
  • Show your bakes: open an issue with the AOI and a screenshot. Early users' AOIs become the gallery.
  • Related projects: Protomaps PMTiles for the archive format, MapLibre for viewing it, ComfyUI for the generation backend.

Not in v0

  • Downloading a DEM. The caller passes a local path; atlas records it and its SHA256.
  • Training, captioning, or curating.
  • A worldwide pyramid, or merging chunk archives into one (chunks are laid side by side by the caller).
  • Serving tiles beyond GET of the finished PMTiles file.
  • GeoServer, PostGIS, tippecanoe, Leaflet, or any game logic.

Metadata

Release files for argus-atlas 0.2.0a1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for argus-atlas 0.2.0a1
File Size Uploaded Provenance
argus_atlas-0.2.0a1.tar.gz 509.4 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for argus-atlas 0.2.0a1
File Interpreter ABI Platform
argus_atlas-0.2.0a1-py3-none-any.whl Python 3 none any Details

Total release size: 605.8 kB

Release files / argus_atlas-0.2.0a1.tar.gz

Download URL argus_atlas-0.2.0a1.tar.gz
Size 509.4 kB
Tags Source
SHA-256 checksum
How to use checksums
e36b6377afe931f00910ad718cbb462a3462c8b884b687cc8c50d2df45463456
BLAKE2b-256 checksum
How to use checksums
24d07d325c13f83a0c8ba9b269fd3d82b27b51034231647dd9fe80041cd8a9be
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

Release files / argus_atlas-0.2.0a1-py3-none-any.whl

Download URL argus_atlas-0.2.0a1-py3-none-any.whl
Size 96.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4a93585574e885186cce783828d065f821e56a090269b0b46d7c65b90917e92d
BLAKE2b-256 checksum
How to use checksums
2a1e551b5f7abce2c30da748b9fa33d38713df43b7c68ba39631f1ca2b39e0c1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

Security for argus-atlas 0.2.0a1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for argus_atlas-0.2.0a1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page