a76760edba
Funciones reutilizables creadas esta sesion para el sistema self-hosted de contactos/calendario (Xandikos) y la app osint_web: - grupo dav (infra): split_vcards, split_vevents_to_vcalendars, extract_or_make_uid, carddav_put_vcard, caldav_put_event, dav_list_resources, dav_get_resource, dav_list_calendars - pipelines: import_vcf_to_carddav, import_ics_to_caldav - obsidian: build_obsidian_graph (grafo agregado del vault)
5.5 KiB
5.5 KiB
name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, params, output, tested, tests, test_file_path, file_path
| name | kind | lang | domain | version | purity | signature | description | tags | uses_functions | uses_types | returns | returns_optional | error_type | imports | params | output | tested | tests | test_file_path | file_path | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| build_obsidian_graph | function | py | obsidian | 1.0.0 | impure | def build_obsidian_graph(vault_dir: str, include_dangling: bool = True) -> dict | Construye el grafo agregado (nodos + aristas) de un vault de Obsidian leyendo todas sus notas .md. Cada nota es un nodo tipado (tipo por carpeta o frontmatter, id = slug, label = frontmatter['nombre'] o slug) y cada wikilink ... del cuerpo es una arista dirigida resuelta por slug del ultimo segmento del destino. Los wikilinks rotos se incluyen como nodos fantasma dangling o se descartan segun include_dangling. Compone list_obsidian_notes, read_obsidian_note y slugify_obsidian_name del grupo obsidian. Es la pieza que cierra la frontera 'el grupo obsidian no indexa el grafo agregado'. Base de la vista grafo (sigma.js) de la app osint_web. |
|
|
false | error_go_core |
|
|
dict con 'nodes' (lista de {id: slug, tipo: str, label: str, frontmatter: dict}; los fantasma anaden dangling=True y frontmatter vacio) y 'edges' (lista de {source: slug, target: slug, kind: str} deduplicada; kind es relacion/lugar/documento segun la seccion ## donde aparece el wikilink, o 'wikilink' por defecto) | true |
|
python/functions/obsidian/build_obsidian_graph_test.py | python/functions/obsidian/build_obsidian_graph.py |
Ejemplo
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from obsidian.build_obsidian_graph import build_obsidian_graph
graph = build_obsidian_graph("/home/enmanuel/Obsidian/osint")
print(len(graph["nodes"]), "nodos,", len(graph["edges"]), "aristas")
# 1199 nodos (984 reales + 215 fantasma), 618 aristas
# Un nodo tipado listo para sigma.js: color por tipo, label visible.
n = next(x for x in graph["nodes"] if x["tipo"] == "persona")
print(n["id"], n["label"], n["tipo"]) # ana-gomez Ana Gómez persona
# Aristas con su kind (relacion / lugar / documento / wikilink).
for e in graph["edges"][:3]:
print(e["source"], "->", e["target"], f"({e['kind']})")
Lanzable directo sobre el vault real (imprime conteo de nodos/aristas/dangling):
cd /home/enmanuel/fn_registry
PYTHONPATH=python/functions python/.venv/bin/python3 \
python/functions/obsidian/build_obsidian_graph.py /home/enmanuel/Obsidian/osint
Cuando usarla
Cuando necesites el grafo entero de un vault de Obsidian de una sola pasada: para pintar una vista de nodos navegable (sigma.js / graphology), para detectar objetivos OSINT aun sin fichar (nodos dangling), o para alimentar el endpoint /api/graph de una app que lee el vault sin BD intermedia. Es el agregado que las funciones atomicas del grupo obsidian (list_obsidian_notes, read_obsidian_note) no daban por si solas.
Gotchas
- Lee todo el vault de disco (I/O impuro): el grafo refleja el estado de los
.mden ese instante; vuelve a llamar para refrescar tras editar notas. - El
idde un nodo es el slug = nombre de archivo sin.md. Si dos notas en carpetas distintas comparten ese nombre (caso real en el vault osint:dni.md,fotos.md,certificado-digital.mdrepetidos dentro de cada subcarpetapersonas/<slug>/), colapsan al mismo nodo y solo la primera en orden alfabetico sobrevive. Por eso el vault con 1022.mdproduce 984 nodos reales (13 grupos de slugs colisionan). Para evitarlo habria que usar el path relativo como id — se dejo el slug por compatibilidad con la resolucion de wikilinks[[slug]]. - Resolucion de wikilinks por ultimo segmento:
[[organizaciones/acme-sl|Acme SL]]resuelve a la nota cuyo slug esacme-sl; el alias (|...) y el ancla (#...) se ignoran. Nombres con acentos/mayusculas ([[María del Mar]]) se slugifican conslugify_obsidian_nameantes de buscar, asi que resuelven igual que[[maria-del-mar]]. kindpor seccion es heuristico: depende del texto del encabezado## ...mas cercano por encima del wikilink (Relaciones/Relacionado->relacion,Lugares->lugar,Documentos->documento, resto ->wikilink). Un wikilink fuera de cualquier seccion conocida eswikilink.- Auto-enlaces ignorados: si una nota se enlaza a si misma, esa arista no se emite.
- Nodos fantasma (
dangling: true) llevantipo: "desconocido"yfrontmattervacio; no representan un.mden disco. Coninclude_dangling=Falseno aparecen ni ellos ni sus aristas. - Lanza
FileNotFoundErrorsivault_dirno existe yNotADirectoryErrorsi no es un directorio (heredado delist_obsidian_notes). Una nota individual ilegible se omite sin tumbar el grafo.