stele1-filesystem
Read and write stele1 catalogs on disk.
A catalog is a directory whose layout is defined by the
stele1 spec:
one JSON file per record at {type}/{uuid}.json,
photo images at photo/{uuid}_image.{ext},
catalog metadata at stele1.json.
Your data is never held hostage by this library;
the worst-case recovery tool is ls and a text editor.
This package provides two classes with the same verbs:
Catalogspeaks validated stele1 datatypes (Climb,Area,Photo,Trail,Parking,Metadata). It is the one most scripts want.DictCatalogspeaks plain dicts, with no validation beyond well-formed JSON and filename/uuid agreement. It is for recovery, debugging, and bring-your-own-classes tooling.
Use
from uuid import uuid4
from stele1.filesystem import Catalog
from stele1.climb import Climb
from stele1.area import Area
catalog = Catalog('hueco.stele1.d')
if not catalog.exists():
catalog.create()
area = Area(uuid4())
area.name = 'The Big Time'
area.location = {
'type': 'approximation',
'circle': {'latitude': 31.9222, 'longitude': -106.0434,
'meter_radius': 40},
}
catalog.set_area(area)
climb = Climb(uuid4())
climb.name = 'See Spot Run'
climb.rating = {'difficulty': 'V6',
'protection': 'many pads and a good spotter'}
climb.length = {'lower_estimate': 7, 'upper_estimate': 8, 'unit': 'meters'}
climb.location = {'latitude': 31.9222, 'longitude': -106.0434,
'meter_radius': 15}
climb.tags = ['highball', 'classic']
catalog.set_climb(climb)
Nothing persists until a set_* call;
records are just objects.
Reading gives the same objects back:
spot = catalog.get_climb(climb.uuid)
spot.name # 'See Spot Run'
spot.notes = [{'topic': 'crux',
'content': 'the hard moves are low; the top is easier'}]
catalog.set_climb(spot)
The core loop is read, mutate, write back:
for c in catalog.each_climb():
if c.tags and 'highball' in c.tags:
c.fields = {**(c.fields or {}), 'bring': 'many pads'}
catalog.set_climb(c)
Every record type has the same five verbs:
catalog.climb_uuids() # iterate uuids on disk
catalog.get_climb(uuid) # -> Climb; KeyError if absent
catalog.set_climb(climb) # validate and write
catalog.each_climb(on_error=cb) # yield every valid Climb
catalog.purge_climb(uuid) # remove, scrubbing photo references
area mirrors climb, including purge_area.
photo, trail, and parking have plain drop_* instead of purge_*:
nothing references them, so there is nothing to purge.
Errors
Invalid records raise TypeError or ValueError
at the read or write that touched them,
naming the problem:
climb.length = {'lower_estimate': 9, 'upper_estimate': 7, 'unit': 'meters'}
# ValueError: climb .length lower_estimate must not exceed upper_estimate
each_* never dies on one bad file:
unreadable or invalid records are skipped,
and reported through on_error(exception, uuid) if given.
def report(e, uuid):
print(f'skipped {uuid}: {e}')
for c in catalog.each_climb(on_error=report):
...
Removing records
Climbs and areas can be referenced by photo overlay layers, so removing them safely means cleaning those references:
catalog.purge_climb(climb.uuid) # removes the climb AND scrubs
# climb_layers from every photo
catalog.drop_climb_no_purge(uuid) # removes only the record file,
# leaving references dangling;
# the name is long on purpose
Purges are idempotent and silent when the record is already gone. They touch many files and are not atomic; re-run to complete after an interruption.
Photo images
The image itself is a file beside the photo record:
from stele1.photo import Photo
photo = Photo(uuid4())
photo.capture_time = '2026-01'
catalog.set_photo(photo)
catalog.set_photo_image(photo.uuid, 'spot-west-face.jpg') # from a path
catalog.set_photo_image(photo.uuid, image_bytes, 'jpg') # from bytes
catalog.photo_image_path(photo.uuid) # or None
catalog.drop_photo(photo.uuid) # removes record and image
Broken catalogs
Catalog validates,
and some of its checks are deliberately stricter than the spec
(marked beyond spec in the source).
DictCatalog reads and writes the same directory as plain dicts,
so any well-formed catalog always loads:
from stele1.filesystem import DictCatalog
raw = DictCatalog('hueco.stele1.d')
data = raw.get_climb(uuid) # dict, no validation
data['length']['unit'] = 'meters' # repair by hand
raw.set_climb(data) # canonical formatting back out
catalog.get_climb(uuid) # now validates
Constructing a DictCatalog is the deliberate act;
Catalog offers no unvalidated access.
Make it yours
Most projects grow their own conventions: a tag taxonomy, required fields, derived values. The blessed pattern is a thin wrapper, not a subclass:
ALLOWED_TAGS = {'highball', 'classic', 'lowball', 'roof'}
class HuecoCatalog:
def __init__(self, path):
self._c = Catalog(path)
def get_climb(self, uuid):
climb = self._c.get_climb(uuid)
self._check(climb)
return climb
def set_climb(self, climb):
self._check(climb)
self._c.set_climb(climb)
def _check(self, climb):
for tag in climb.tags or ():
if tag not in ALLOWED_TAGS:
raise ValueError(f'{climb.uuid}: unknown tag {tag!r}')
def __getattr__(self, name): # everything else passes through
return getattr(self._c, name)
Wrappers can rely on:
records are self-contained (no back-reference to the catalog);
all typed reads funnel through get_*;
formatting is the catalog's job, never yours.
The documented exceptions: purges and DictCatalog
bypass the typed verbs by design.
Formatting
All writes are canonical: sorted keys, 2-space indent, UTF-8, trailing newline. A catalog diffs cleanly under version control regardless of which tool wrote it. The formatter is exported for tooling that writes catalog files without a catalog object:
from stele1.filesystem import dumps_data
dumps_data({'uuid': '...', 'name': 'See Spot Run'})
Install
pip install stele1-filesystem
Depends only on stele1-datatypes; both are stdlib-only.
Development
Source and issues live at codeberg.org/stele-climbing/stele1.
License
MIT
Metadata
Release files for stele1-filesystem 0.3.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| stele1_filesystem-0.3.8.tar.gz | 15.0 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| stele1_filesystem-0.3.8-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.2 kB
Release files / stele1_filesystem-0.3.8.tar.gz
| Download URL | stele1_filesystem-0.3.8.tar.gz |
|---|---|
| Size | 15.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0a1306e50f8db58ee069d2b51e0c96845d2cab2773055eb1c44581ec6936991e
|
|
BLAKE2b-256 checksum How to use checksums |
fac77f90efe18559502ddc85130d205caf925ff0e3b733ae670e3bdb29aab91e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / stele1_filesystem-0.3.8-py3-none-any.whl
| Download URL | stele1_filesystem-0.3.8-py3-none-any.whl |
|---|---|
| Size | 14.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6444f032b0d63bcce0c9ee295e8faa5ee857404cb79e4df33d7aa447e5b08e20
|
|
BLAKE2b-256 checksum How to use checksums |
93a1da69aa11ea732a709a21cf6ee7a04996d4a6069818a076412690ecc261b2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Security for stele1-filesystem 0.3.8
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.