From 7dadbdd586af408e29e0e801ea78d28c5b8e4000 Mon Sep 17 00:00:00 2001 From: Thomas Walker Lynch Date: Thu, 8 Jan 2026 15:37:58 +0000 Subject: [PATCH] annotating Python objects with property values passes a simple experiment test --- README.md | 2 - README.org | 28 +++++ developer/__init__.py | 2 + .../authored/Epimetheus/ObjectRegistry.py | 113 ++++++++++++++++++ .../{ => Epimetheus}/ProcessLocalId.py | 0 .../authored/{ => Epimetheus}/Property.py | 0 .../{ => Epimetheus}/PropertyManager.py | 0 .../{ => Epimetheus}/PropertyStore.py | 0 .../authored/{ => Epimetheus}/SemanticSets.py | 0 developer/authored/{ => Epimetheus}/Syntax.py | 0 developer/authored/Epimetheus/__init__.py | 1 + developer/authored/Identity.py | 42 ------- developer/authored/ObjectRegistry.py | 69 ----------- developer/authored/__init__.py | 15 --- developer/experiment/test_Epimetheus.py | 60 ++++++++++ developer/tool/env | 3 + nohup.out | 0 temp.sh | 11 -- 18 files changed, 207 insertions(+), 139 deletions(-) delete mode 100644 README.md create mode 100644 README.org create mode 100644 developer/__init__.py create mode 100644 developer/authored/Epimetheus/ObjectRegistry.py rename developer/authored/{ => Epimetheus}/ProcessLocalId.py (100%) rename developer/authored/{ => Epimetheus}/Property.py (100%) rename developer/authored/{ => Epimetheus}/PropertyManager.py (100%) rename developer/authored/{ => Epimetheus}/PropertyStore.py (100%) rename developer/authored/{ => Epimetheus}/SemanticSets.py (100%) rename developer/authored/{ => Epimetheus}/Syntax.py (100%) create mode 100644 developer/authored/Epimetheus/__init__.py delete mode 100644 developer/authored/Identity.py delete mode 100644 developer/authored/ObjectRegistry.py delete mode 100644 developer/authored/__init__.py create mode 100755 developer/experiment/test_Epimetheus.py delete mode 100644 nohup.out delete mode 100644 temp.sh diff --git a/README.md b/README.md deleted file mode 100644 index 7104a94..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Epimetheus - diff --git a/README.org b/README.org new file mode 100644 index 0000000..07975ca --- /dev/null +++ b/README.org @@ -0,0 +1,28 @@ +* Epimetheus +/The God of Afterthought/ + +Epimetheus is a Python library for **Higher Order Analysis** and **Semantic Annotation**. + +Unlike Prometheus (Forethought), who defines what an object /is/ before it is born (classic class instantiation), Epimetheus allows you to attach meaning, identity, and semantic types to objects /after/ they exist. + +It is a mechanism for "Late Binding" of ontology. + +** Concept +Most type systems are embedded in the object's definition (the Class). Epimetheus treats the object as a point in space and allows an external observer (the library) to weave a "constellation" of properties and relationships around it. + +This enables: +- **Semantic Typing:** Tagging objects with properties ($color$, $spin$, $is_valid$) without altering the object's code. +- **Higher Order Analysis:** defining relationships between objects (constellations) rather than just properties of objects. +- **Non-Invasive Instrumentation:** Annotating third-party objects or C-extensions that cannot be subclassed. + +** Etymology +Named after the Greek Titan *Epimetheus* (Afterthought). While his brother Prometheus is associated with architectural planning, Epimetheus is the patron of patching, extending, and adapting the world as it is found. + +** Status +- **Author:** Thomas Walker Lynch +- **Version:** Pre-Alpha + +#+BEGIN_QUOTE +"Zero length is a second order concept." +-- T.W. Lynch, *TTCA* +#+END_QUOTE diff --git a/developer/__init__.py b/developer/__init__.py new file mode 100644 index 0000000..45fa30d --- /dev/null +++ b/developer/__init__.py @@ -0,0 +1,2 @@ +# developer/authored/__init__.py +from .PropertyManager import PropertyManager diff --git a/developer/authored/Epimetheus/ObjectRegistry.py b/developer/authored/Epimetheus/ObjectRegistry.py new file mode 100644 index 0000000..9d30fd9 --- /dev/null +++ b/developer/authored/Epimetheus/ObjectRegistry.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2 -*- + +""" +ObjectRegistry + +Maps Python runtime objects to ProcessLocalId. + +Strategies: + 1. Weak Identity (Preferred): For objects that support weakrefs (instances ,classes). + - ID lifetime is bound to object lifetime. + - Auto-cleaned on GC. + + 2. Value Identity (Fallback): For immutable primitives (int ,str ,tuple). + - ID is bound to the *value* (hash/equality). + - Stored strongly (since values like 42 or "red" are conceptually eternal). +""" + +from __future__ import annotations + +import weakref +from typing import Any ,Callable ,Dict ,Optional + +from .ProcessLocalId import ProcessLocalIdGenerator ,ProcessLocalId + + +class ObjectRegistry: + def __init__(self ,id_gen: ProcessLocalIdGenerator): + self._id_gen = id_gen + + # Strategy 1: Entities (Weakref-able) + self._obj_to_id_wkd: "weakref.WeakKeyDictionary[Any ,ProcessLocalId]" = weakref.WeakKeyDictionary() + self._id_to_obj_ref: Dict[ProcessLocalId ,weakref.ref] = {} + + # Strategy 2: Values (Hashable ,not weakref-able) + self._value_to_id: Dict[Any ,ProcessLocalId] = {} + + self._finalizers: Dict[ProcessLocalId ,Callable[[ProcessLocalId] ,None]] = {} + self._global_finalizer: Optional[Callable[[ProcessLocalId] ,None]] = None + + def register_finalizer(self ,fn: Callable[[ProcessLocalId] ,None]): + """ + Registers a finalizer callback invoked when any registered *weak-refable* object is GC'd. + """ + self._global_finalizer = fn + + def _on_collect(self ,obj_id: ProcessLocalId): + # Only called for Strategy 1 objects + ref = self._id_to_obj_ref.pop(obj_id ,None) + if(ref is not None): + # The WeakKeyDictionary auto-cleans the forward mapping , + # but we double check or clean any edge cases if needed. + pass + + fn = self._global_finalizer + if(fn is not None): + fn(obj_id) + + def get_id(self ,obj: Any) -> ProcessLocalId: + """ + Returns the ProcessLocalId for `obj` ,registering it if needed. + """ + # 1. Try WeakRef Strategy (Entities) + try: + existing = self._obj_to_id_wkd.get(obj) + if(existing is not None): + return existing + except TypeError: + # obj is not weakref-able (e.g. int ,str ,tuple ,or list). + # Fall through to Strategy 2. + pass + else: + # It IS weakref-able ,but wasn't in the dictionary. Register it. + obj_id = self._id_gen.next_id() + self._obj_to_id_wkd[obj] = obj_id + # Create reverse lookup with callback + self._id_to_obj_ref[obj_id] = weakref.ref(obj ,lambda _ref ,oid=obj_id: self._on_collect(oid)) + return obj_id + + # 2. Try Value Strategy (Primitives) + # Note: Mutable non-weakrefables (like standard lists) will fail here because they are unhashable. + try: + existing = self._value_to_id.get(obj) + if(existing is not None): + return existing + + # Register new value + obj_id = self._id_gen.next_id() + self._value_to_id[obj] = obj_id + return obj_id + except TypeError: + # It is neither weakref-able NOR hashable (e.g. standard list ,dict). + raise TypeError( + f"ObjectRegistry: cannot track object of type {type(obj)!r}. " + "It is neither weakref-able (Entity) nor hashable (Value)." + ) + + def try_get_object(self ,obj_id: ProcessLocalId) -> Optional[Any]: + """ + Best-effort: returns the live object/value. + """ + # Check Entities + ref = self._id_to_obj_ref.get(obj_id) + if(ref is not None): + return ref() + + # Check Values + # For now ,we assume values identify themselves. + for val ,pid in self._value_to_id.items(): + if(pid == obj_id): + return val + + return None diff --git a/developer/authored/ProcessLocalId.py b/developer/authored/Epimetheus/ProcessLocalId.py similarity index 100% rename from developer/authored/ProcessLocalId.py rename to developer/authored/Epimetheus/ProcessLocalId.py diff --git a/developer/authored/Property.py b/developer/authored/Epimetheus/Property.py similarity index 100% rename from developer/authored/Property.py rename to developer/authored/Epimetheus/Property.py diff --git a/developer/authored/PropertyManager.py b/developer/authored/Epimetheus/PropertyManager.py similarity index 100% rename from developer/authored/PropertyManager.py rename to developer/authored/Epimetheus/PropertyManager.py diff --git a/developer/authored/PropertyStore.py b/developer/authored/Epimetheus/PropertyStore.py similarity index 100% rename from developer/authored/PropertyStore.py rename to developer/authored/Epimetheus/PropertyStore.py diff --git a/developer/authored/SemanticSets.py b/developer/authored/Epimetheus/SemanticSets.py similarity index 100% rename from developer/authored/SemanticSets.py rename to developer/authored/Epimetheus/SemanticSets.py diff --git a/developer/authored/Syntax.py b/developer/authored/Epimetheus/Syntax.py similarity index 100% rename from developer/authored/Syntax.py rename to developer/authored/Epimetheus/Syntax.py diff --git a/developer/authored/Epimetheus/__init__.py b/developer/authored/Epimetheus/__init__.py new file mode 100644 index 0000000..ce046ae --- /dev/null +++ b/developer/authored/Epimetheus/__init__.py @@ -0,0 +1 @@ +from .PropertyManager import PropertyManager diff --git a/developer/authored/Identity.py b/developer/authored/Identity.py deleted file mode 100644 index 236ef8a..0000000 --- a/developer/authored/Identity.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 -# -*- mode: python; coding: utf-8; python-indent-offset: 2 -*- - - -""" -Identity - -An abstract identity used as the subject key for property attachment. - -Kinds (strings) determine storage and resolution behavior. -""" - -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any ,Optional ,Tuple - -from .ProcessLocalId import ProcessLocalId - - -IDENTITY_KIND_PY_OBJECT = "py_object" -IDENTITY_KIND_SYNTAX = "syntax" -IDENTITY_KIND_PROPERTY = "property" -IDENTITY_KIND_SET = "semantic_set" - - -@dataclass(frozen=True ,slots=True) -class Identity: - """ - `id` is always a ProcessLocalId. - - `kind` partitions lookup behavior. - - `payload` is kind-specific metadata (kept small; do not put giant graphs here). - """ - id: ProcessLocalId - kind: str - payload: Any = None - - def __repr__(self) -> str: - # Do not reveal id token. - return f"" diff --git a/developer/authored/ObjectRegistry.py b/developer/authored/ObjectRegistry.py deleted file mode 100644 index b3e4cab..0000000 --- a/developer/authored/ObjectRegistry.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python3 -# -*- mode: python; coding: utf-8; python-indent-offset: 2 -*- - - -""" -ObjectRegistry - -Maps Python runtime objects to ProcessLocalId using weak identity. - -Constraints: - - Only weakref-able Python objects can be registered. - - This is intentional: RT properties attach to identity-bearing runtime instances, - not to value-like primitives (ints/strings/lists/dicts). -""" - -from __future__ import annotations - -import weakref -from typing import Any ,Callable ,Dict ,Optional - -from .ProcessLocalId import ProcessLocalIdGenerator ,ProcessLocalId - - -class ObjectRegistry: - def __init__(self ,id_gen: ProcessLocalIdGenerator): - self._id_gen = id_gen - self._obj_to_id_wkd: "weakref.WeakKeyDictionary[Any ,ProcessLocalId]" = weakref.WeakKeyDictionary() - self._id_to_obj_ref: Dict[ProcessLocalId ,weakref.ref] = {} - self._finalizers: Dict[ProcessLocalId ,Callable[[ProcessLocalId],None]] = {} - - def register_finalizer(self ,fn: Callable[[ProcessLocalId],None]): - """ - Registers a finalizer callback invoked when any registered object is GC'd. - """ - self._global_finalizer = fn - - def _on_collect(self ,obj_id: ProcessLocalId): - self._obj_to_id_wkd.pop(self._id_to_obj_ref[obj_id]() ,None) - self._id_to_obj_ref.pop(obj_id ,None) - fn = getattr(self ,"_global_finalizer" ,None) - if fn is not None: fn(obj_id) - - def get_id(self ,obj: Any) -> ProcessLocalId: - """ - Returns the ProcessLocalId for `obj`, registering it if needed. - - Raises TypeError if `obj` is not weakref-able. - """ - try: - existing = self._obj_to_id_wkd.get(obj) - except TypeError: - raise TypeError("ObjectRegistry: object is not weakref-able; RT properties do not attach to value-like primitives.") - if existing is not None: return existing - - obj_id = self._id_gen.next_id() - try: - self._obj_to_id_wkd[obj] = obj_id - except TypeError: - raise TypeError("ObjectRegistry: object is not weakref-able; RT properties do not attach to value-like primitives.") - self._id_to_obj_ref[obj_id] = weakref.ref(obj ,lambda _ref ,oid=obj_id: self._on_collect(oid)) - return obj_id - - def try_get_object(self ,obj_id: ProcessLocalId) -> Optional[Any]: - """ - Best-effort: returns the live object, or None if it has been collected or never registered. - """ - ref = self._id_to_obj_ref.get(obj_id) - if ref is None: return None - return ref() diff --git a/developer/authored/__init__.py b/developer/authored/__init__.py deleted file mode 100644 index 2b53823..0000000 --- a/developer/authored/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python3 -# -*- mode: python; coding: utf-8; python-indent-offset: 2 -*- - -""" -rt_property_manager - -Process-local property attachment with: - - weak identity for Python runtime instances - - explicit identities for syntax instances and properties - -Notes: - - ProcessLocalId values are not meant to be serialized or persisted. -""" - -from .PropertyManager import PropertyManager diff --git a/developer/experiment/test_Epimetheus.py b/developer/experiment/test_Epimetheus.py new file mode 100755 index 0000000..7024e9d --- /dev/null +++ b/developer/experiment/test_Epimetheus.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2 -*- + +import sys +# CHANGED: Import directly from the package 'Epimetheus' +from Epimetheus import PropertyManager + +def test_everything() -> int: + """ + The Work Function. + Returns 0 on success ,non-zero on failure. + """ + try: + pm = PropertyManager() + + # 1. Declare Properties (The Schema) + prop_color = pm.declare_property("color") + prop_meaning = pm.declare_property("meaning") + + # 2. Annotate an Entity (Instance) + class Robot: pass + r1 = Robot() + + pm.set(r1 ,"color" ,"silver") + print(f"Robot Color: {pm.get(r1 ,'color')}") + # Expect: silver + + # 3. Annotate an Entity (Class) + pm.set(Robot ,"meaning" ,"A machine") + print(f"Robot Class Meaning: {pm.get(Robot ,'meaning')}") + # Expect: A machine + + # 4. Annotate a Value (Primitive) + pm.set(42 ,"meaning" ,"The Answer") + print(f"42 Meaning: {pm.get(42 ,'meaning')}") + # Expect: The Answer + + # Annotating a Tuple + my_key = (1 ,2) + pm.set(my_key ,"color" ,"invisible") + print(f"Tuple Color: {pm.get((1 ,2) ,'color')}") + # Expect: invisible + + print("\n--- Success ---") + return 0 + + except Exception as e: + print(f"Test Failed: {e}") + return 1 + + +def CLI(args_seq) -> int: + """ + The CLI Entry Point. + """ + return test_everything() + + +if(__name__ == "__main__"): + sys.exit(CLI(sys.argv)) diff --git a/developer/tool/env b/developer/tool/env index 0b993ad..610170c 100644 --- a/developer/tool/env +++ b/developer/tool/env @@ -1,3 +1,6 @@ #!/usr/bin/env bash script_afp=$(realpath "${BASH_SOURCE[0]}") +export PYTHONPATH="$REPO_HOME/developer/authored:$PYTHONPATH" + + diff --git a/nohup.out b/nohup.out deleted file mode 100644 index e69de29..0000000 diff --git a/temp.sh b/temp.sh deleted file mode 100644 index c166d29..0000000 --- a/temp.sh +++ /dev/null @@ -1,11 +0,0 @@ -2025-11-25 09:33:05 Z [subu:developer] Thomas_developer@StanleyPark -§/home/Thomas/subu_data/developer/subu_data/Harmony§ -> find . -type l -exec ls -l {} \; -lrwxrwxrwx 1 Thomas_developer Thomas_developer 35 Nov 25 09:08 ./tool/sync -> ../tool_shared/authored/sync/CLI.py -lrwxrwxrwx 1 Thomas_developer Thomas_developer 3 May 19 2025 ./shared/third_party/Python/lib64 -> lib -lrwxrwxrwx 1 Thomas_developer Thomas_developer 16 May 19 2025 ./shared/third_party/Python/bin/python3 -> /usr/bin/python3 -lrwxrwxrwx 1 Thomas_developer Thomas_developer 7 May 19 2025 ./shared/third_party/Python/bin/python -> python3 -lrwxrwxrwx 1 Thomas_developer Thomas_developer 7 May 19 2025 ./shared/third_party/Python/bin/python3.11 -> python3 -lrwxrwxrwx 1 Thomas_developer Thomas_developer 15 Nov 24 15:19 ./shared/authored/git-empty-dir/source_sync -> ../source_sync/ -lrwxrwxrwx 1 Thomas_developer Thomas_developer 25 Nov 24 15:21 ./shared/authored/git-empty-dir/Harmony.py -> ../source_sync/Harmony.py -lrwxrwxrwx 1 Thomas_developer Thomas_developer 37 Nov 24 15:22 ./shared/authored/git-empty-dir/load_command_module.py -> ../source_sync/load_command_module.py -- 2.20.1