Skip to content
Merged
16 changes: 13 additions & 3 deletions src/sphinxnotes/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,17 @@
Template,
Host,
pending_node,
BaseDataDefineRole,
BaseDataDefineDirective,
PendingContext,
ResolvedContext,
BaseContextRole,
BaseContextDirective,
ExtraContextRegistry,
ExtraContextGenerator,
UnparsedData,
BaseDataDefineRole,
BaseDataDefineDirective,
StrictDataDefineDirective,
)
from .examples.strict import StrictDataDefineDirective

if TYPE_CHECKING:
from sphinx.application import Sphinx
Expand All @@ -52,7 +57,12 @@
'Phase',
'Template',
'Host',
'PendingContext',
'ResolvedContext',
'pending_node',
'BaseContextRole',
'BaseContextDirective',
'UnparsedData',
'BaseDataDefineRole',
'BaseDataDefineDirective',
'StrictDataDefineDirective',
Expand Down
28 changes: 19 additions & 9 deletions src/sphinxnotes/data/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,8 @@ class RawData:
attrs: dict[str, str]
content: str | None


@dataclass
class PendingData(Unpicklable):
raw: RawData
schema: Schema

def parse(self) -> ParsedData:
return self.schema.parse(self.raw)
def __hash__(self) -> int:
return hash((self.name, frozenset(self.attrs.items()), self.content))


@dataclass
Expand All @@ -233,6 +227,9 @@ class ParsedData:
attrs: dict[str, Value]
content: Value

def __hash__(self) -> int:
return hash((self.name, frozenset(self.attrs.items()), self.content))

def asdict(self) -> dict[str, Any]:
"""
Convert Data to a dict for usage of Jinja2 context.
Expand Down Expand Up @@ -268,6 +265,12 @@ class Field(Unpicklable):
required: bool = False
sep: str | None = None

def __hash__(self) -> int:
flags = {
k: v if not isinstance(v, list) else tuple(v) for k, v in self.flags.items()
}
return hash((self.etype, self.ctype, frozenset(flags.items()), self.dsl))

@classmethod
def from_dsl(cls, dsl: str) -> Self:
self = cls()
Expand Down Expand Up @@ -451,11 +454,18 @@ def by_option_store_value_error(opt: ByOption) -> ValueError:


@dataclass(frozen=True)
class Schema(object):
class Schema(Unpicklable):
name: Field | None
attrs: dict[str, Field] | Field
content: Field | None

def __hash__(self) -> int:
if isinstance(self.attrs, Field):
attrs_hash = hash(self.attrs)
else:
attrs_hash = hash(frozenset(self.attrs.items()))
return hash((self.name, attrs_hash, self.content))

@classmethod
def from_dsl(
cls,
Expand Down
7 changes: 4 additions & 3 deletions src/sphinxnotes/data/examples/datadomain.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,10 @@ def __init__(self, orig_name: str) -> None:
self.orig_name = orig_name

@override
def current_data(self) -> RawData:
_, _, name = self.orig_name.partition('+')
return RawData(name, {}, self.text)
def current_raw_data(self) -> RawData:
data = super().current_raw_data()
_, _, data.name = self.orig_name.partition('+')
return data

@override
def current_schema(self) -> Schema:
Expand Down
60 changes: 0 additions & 60 deletions src/sphinxnotes/data/examples/strict.py

This file was deleted.

29 changes: 23 additions & 6 deletions src/sphinxnotes/data/render/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
from __future__ import annotations
from typing import TYPE_CHECKING

from .render import Phase, Template, Host
from .datanodes import pending_node
from .render import (
Phase,
Template,
Host,
)
from .ctx import PendingContext, ResolvedContext
from .ctxnodes import pending_node
from .pipeline import (
BaseDataDefineRole,
BaseDataDefineDirective,
BaseContextRole,
BaseContextDirective,
)
from .extractx import ExtraContextRegistry, ExtraContextGenerator
from .sources import (
UnparsedData,
BaseDataDefineDirective,
StrictDataDefineDirective,
BaseDataDefineRole,
)

if TYPE_CHECKING:
from sphinx.application import Sphinx
Expand All @@ -17,11 +28,17 @@
'Phase',
'Template',
'Host',
'PendingContext',
'ResolvedContext',
'pending_node',
'BaseDataDefineRole',
'BaseDataDefineDirective',
'BaseContextRole',
'BaseContextDirective',
'ExtraContextRegistry',
'ExtraContextGenerator',
'UnparsedData',
'BaseDataDefineDirective',
'StrictDataDefineDirective',
'BaseDataDefineRole',
]


Expand Down
76 changes: 76 additions & 0 deletions src/sphinxnotes/data/render/ctx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
sphinxnotes.data.render.ctx
~~~~~~~~~~~~~~~~~~~~~~~~~~~

:copyright: Copyright 2026 by the Shengyu Zhang.
:license: BSD, see LICENSE for details.

This module wraps the :mod:`..data` into context for rendering the template.
"""

from typing import TYPE_CHECKING
from abc import ABC, abstractmethod
from collections.abc import Hashable
from dataclasses import dataclass

from sphinxnotes.data.utils import Unpicklable

if TYPE_CHECKING:
from typing import Any
from ..data import ParsedData

type ResolvedContext = ParsedData | dict[str, Any]


@dataclass
class PendingContextRef:
"""A abstract class that references to :cls:`PendingCtx`."""

ref: int
chksum: int

def __hash__(self) -> int:
return hash((self.ref, self.chksum))


class PendingContext(ABC, Unpicklable, Hashable):
"""A abstract representation of context that is not currently available.

Call :meth:`resolve` at the right time (depends on the implment) to get
context available.
"""

@abstractmethod
def resolve(self) -> ResolvedContext: ...


class PendingContextStorage:
"""Area for temporarily storing PendingContext.

This class is indented to resolve the problem that:

Some of the PendingContext are :cls:Unpicklable` and they can not be hold
by :cls:`pending_node` (as ``pending_node`` will be pickled along with
the docutils doctree)

This class maintains a mapping from :cls:`PendingContextRef` -> :cls:`PendingContext`.
``pending_node`` owns the ``PendingContextRef``, and can retrieve the context
by calling :meth:`retrieve`.
"""

_next_id: int
_data: dict[PendingContextRef, PendingContext] = {}

def __init__(self) -> None:
self._next_id = 0
self._data = {}

def stash(self, pending: PendingContext) -> PendingContextRef:
ref = PendingContextRef(self._next_id, hash(pending))
self._next_id += 1
self._data[ref] = pending
return ref

def retrieve(self, ref: PendingContextRef) -> PendingContext | None:
data = self._data.pop(ref, None)
return data
Loading