songleaf.library¶
The library’s lists: which stored songs are in which lists, shared by everyone using it.
The page calls them lists; the model and the stored file keep the name zodal-groups gives
them (groups), so this module and groups.json say “groups”.
The groups are a zodal-groups snapshot ({"nodes": [...], "edges": [...]}: songs and
groups are nodes, a membership is an edge from a group to a song or to another group). The
page edits them with zodal-groups and saves the whole snapshot; this module keeps it as one
JSON document under the data root (library/groups.json) with a revision number.
A save names the revision it was made from. If the document has moved on since (someone
else saved), the save is refused with RevisionConflict, which carries the current
document, so no one’s change is silently overwritten.
>>> import tempfile
>>> root = tempfile.mkdtemp()
>>> read_groups(root=root)
{'revision': 0, 'snapshot': {'nodes': [], 'edges': []}}
>>> snapshot = {"nodes": [{"id": "Family"}, {"id": "memory:1"}],
... "edges": [{"id": "e1", "parent": "Family", "child": "memory:1",
... "kind": "contains"}]}
>>> save_groups(snapshot, revision=0, root=root)["revision"]
1
>>> drop_item("memory:1", root=root)["snapshot"]["edges"]
[]
Module Attributes
thousands of songs fit many times over. |
Functions
|
|
|
Take |
|
One writer of |
|
The songs in a list or in any list inside it, each once, in the order they were added. |
|
|
|
The lists whose name the query names, best first: |
|
|
|
Replace the groups with |
|
Write |
Exceptions
|
The groups changed since the revision a save was made from. |
- songleaf.library.MAX_BYTES = 2000000¶
thousands of songs fit many times over.
- Type:
The largest groups document accepted, in bytes
- exception songleaf.library.RevisionConflict(current)[source]¶
Bases:
ExceptionThe groups changed since the revision a save was made from.
- songleaf.library.checked_snapshot(snapshot)[source]¶
snapshotif it has the shape of a zodal-groups snapshot, elseValueError.The checks are zodal-groups’ own read-path rules (
parseSnapshot: non-empty string ids, unique node and edge ids, every edge with a parent, a child and a kind), plus no self-loop and no cycle, so a page can always load what was saved. The profile’s other rules are zodal-groups’, applied where the edit is made.>>> edge = {"id": "e", "parent": "a", "child": "a", "kind": "contains"} >>> checked_snapshot({"nodes": [], "edges": [edge]}) Traceback (most recent call last): ... ValueError: edges[0]: a group cannot contain itself ('a')
- Return type:
- songleaf.library.drop_item(item, *, root=None)[source]¶
Take
item(a song key) out of every group; returns the document, saved if it changed.- Return type:
- songleaf.library.file_lock(path)[source]¶
One writer of
pathat a time: threads of this process, and other processes where fcntl exists.
- songleaf.library.list_members(snapshot, list_id)[source]¶
The songs in a list or in any list inside it, each once, in the order they were added.
>>> snap = {"nodes": [], "edges": [ ... {"id": "1", "parent": "Campfire", "child": "x:1", "kind": "contains"}, ... {"id": "2", "parent": "Campfire", "child": "Slow", "kind": "contains"}, ... {"id": "3", "parent": "Slow", "child": "x:2", "kind": "contains"}, ... {"id": "4", "parent": "Slow", "child": "x:1", "kind": "contains"}]} >>> list_members(snap, "Campfire") ['x:1', 'x:2']
- songleaf.library.list_names(snapshot)[source]¶
{id: name}of every list (group) in a snapshot: its nodes, and every edge’s parent.>>> list_names({"nodes": [{"id": "a", "label": "Campfire"}], ... "edges": [{"id": "e", "parent": "b", "child": "x:1", "kind": "contains"}]}) {'a': 'Campfire', 'b': 'b'}
- songleaf.library.lists_matching(query, snapshot)[source]¶
The lists whose name the query names, best first:
[{"id", "name", "spans"}].A list matches when every word of the query starts a word of its name (
"camp"finds Campfire,"to le"finds To learn; a one-letter word must be a whole word), or when the whole name, four letters or more, is the query give or take one typo (a letter added, dropped, changed or two swapped). So a short name (Fun) is not found by every query that holds it (funeral,funk).spansare the matched parts of the name.>>> snap = {"nodes": [{"id": "Campfire"}, {"id": "To learn"}, {"id": "Fun"}], "edges": []} >>> [m["id"] for m in lists_matching("camp", snap)] ['Campfire'] >>> [m["id"] for m in lists_matching("campfier", snap)], lists_matching("funk", snap) (['Campfire'], []) >>> lists_matching("t", snap) []
- songleaf.library.read_groups(*, root=None)[source]¶
{"revision", "snapshot"}: the current groups, revision 0 when there are none.- Return type:
- songleaf.library.save_groups(snapshot, *, revision, root=None, keep_item=None)[source]¶
Replace the groups with
snapshot, made fromrevision; returns the new document.keep_item(id) -> bool, when given, decides whether a member that is not a group (an item: a song key) may stay; memberships of the others are dropped (a song deleted while a page that still showed it was saving). RaisesRevisionConflictwhen the stored revision is notrevision, andValueErrorwhensnapshotis not a snapshot orrevisionnot a whole number.- Return type: