Lightweight class introspection toolkit — define typed markers, annotate fields and methods, collect metadata via MRO-walking descriptors.
Project description
markers
Lightweight class introspection toolkit for Python. Define typed markers, annotate fields and methods, collect metadata via MRO-walking descriptors.
Install
pip install code-is-magic-markers
Quick start
from typing import Annotated
from markers import Marker, MarkerGroup, Registry
# 1. Define markers — the class body IS the schema
class Required(Marker): pass
class MaxLen(Marker):
mark = "max_length"
limit: int
class Searchable(Marker):
boost: float = 1.0
analyzer: str = "standard"
class OnSave(Marker):
mark = "on_save"
priority: int = 0
# 2. Bundle into groups
class Validation(MarkerGroup):
Required = Required
MaxLen = MaxLen
class Lifecycle(MarkerGroup):
OnSave = OnSave
# 3. Annotate your classes
class User(Validation.mixin, Lifecycle.mixin):
name: Annotated[str, Validation.Required(), Validation.MaxLen(limit=100)]
email: Annotated[str, Validation.Required()]
bio: Annotated[str, Searchable()] = ""
@Lifecycle.OnSave(priority=10)
def validate(self) -> list[str]:
errors = []
for name, info in type(self).required.items():
if info.is_field and not getattr(self, name, None):
errors.append(f"{name} is required")
return errors
# 4. Query metadata — same dict[str, MemberInfo] everywhere
User.fields # all fields
User.methods # all methods
User.members # both
User.required # only members marked 'required'
User.on_save # only members marked 'on_save'
# Introspect
User.fields["name"].get("max_length").limit # 100
User.methods["validate"].get("on_save").priority # 10
Core concepts
Marker
Subclass Marker to define a marker. The class body is the pydantic schema — typed fields become validated parameters:
class ForeignKey(Marker):
mark = "foreign_key" # explicit name (default: lowercased class name)
table: str # required parameter
column: str = "id" # optional with default
on_delete: str = "CASCADE"
Markers work as both Annotated[] metadata and method decorators:
# As annotation
author_id: Annotated[int, ForeignKey(table="users")]
# As decorator
@OnSave(priority=10)
def validate(self): ...
Schema-less markers accept no parameters:
class Required(Marker): pass
Required() # ok
Required(x=1) # TypeError
Intermediate bases share schema fields:
class LifecycleMarker(Marker):
priority: int = 0
class OnSave(LifecycleMarker):
mark = "on_save"
class OnDelete(LifecycleMarker):
mark = "on_delete"
# Both have 'priority'
MarkerGroup
Bundle related markers and produce a .mixin:
class DB(MarkerGroup):
PrimaryKey = PrimaryKey
Indexed = Indexed
ForeignKey = ForeignKey
class User(DB.mixin):
id: Annotated[int, DB.PrimaryKey()]
email: Annotated[str, DB.Indexed(unique=True)]
User.primary_key # {'id': MemberInfo(...)}
User.indexed # {'email': MemberInfo(...)}
User.fields # all fields (from BaseMixin)
Groups compose via inheritance:
class ExtendedDB(DB):
Unique = Unique
Registry
Track subclasses for cross-class queries:
class Entity(DB.mixin, Registry):
id: Annotated[int, DB.PrimaryKey()]
class User(Entity):
name: Annotated[str, Required()]
class Post(Entity):
title: Annotated[str, Required()]
# List all subclasses
Entity.subclasses() # [User, Post]
# Iterate with the same per-class API
for cls in Entity.subclasses():
print(cls.__name__, list(cls.required.keys()))
# Or gather across all subclasses
Entity.all.required # {'name': [MemberInfo(owner=User)], 'title': [MemberInfo(owner=Post)]}
Entity.all.fields # {'id': [MemberInfo(owner=User), MemberInfo(owner=Post)], ...}
MemberInfo
Every collected member (field or method) is a MemberInfo:
info = User.fields["name"]
info.name # 'name'
info.kind # MemberKind.FIELD
info.type # <class 'str'>
info.owner # <class 'User'>
info.default # MISSING (no default)
info.has_default # False
info.is_field # True
info.is_method # False
info.markers # [MarkerInstance('required', ...), MarkerInstance('max_length', ...)]
info.has("required") # True
info.get("max_length").limit # 100
info.get_all("required") # [MarkerInstance(...)]
API reference
| Class | Purpose |
|---|---|
Marker |
Subclass to define markers with optional typed schema |
MarkerGroup |
Subclass to bundle markers into a .mixin |
Registry |
Subclass to track all subclasses, provides .subclasses() and .all |
MarkerInstance |
A specific usage of a marker with validated params |
MemberInfo |
Metadata about a field or method |
MemberKind |
Enum: FIELD or METHOD |
MISSING |
Sentinel for fields with no default |
Marker class methods
| Method | Description |
|---|---|
MyMarker.collect(cls) |
Collect members carrying this marker from cls |
Marker.invalidate(cls) |
Clear cached collection for cls |
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file code_is_magic_markers-0.1.0.tar.gz.
File metadata
- Download URL: code_is_magic_markers-0.1.0.tar.gz
- Upload date:
- Size: 70.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
878c0ca771ebf0cec6c3350ff00ecad1eb871eab605ddbb9c8b13da72a301af0
|
|
| MD5 |
ca022b202d499570e429551a6ee3d430
|
|
| BLAKE2b-256 |
c8ec3e7e6a15b8e530601830d3d8f33bb778c3caf45cc86e83201a8394d25cda
|
Provenance
The following attestation bundles were made for code_is_magic_markers-0.1.0.tar.gz:
Publisher:
release.yml on Richard-Lynch/markers
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
code_is_magic_markers-0.1.0.tar.gz -
Subject digest:
878c0ca771ebf0cec6c3350ff00ecad1eb871eab605ddbb9c8b13da72a301af0 - Sigstore transparency entry: 1286367282
- Sigstore integration time:
-
Permalink:
Richard-Lynch/markers@9200f22c537db947c7436ef28452f7be9074ee52 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Richard-Lynch
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9200f22c537db947c7436ef28452f7be9074ee52 -
Trigger Event:
push
-
Statement type:
File details
Details for the file code_is_magic_markers-0.1.0-py3-none-any.whl.
File metadata
- Download URL: code_is_magic_markers-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5bceb4f39ce642bde4e62e619515628e5dede67097be4bcdcf1dbb477e863a97
|
|
| MD5 |
8e1d0f7860ac2a56fcfadda97b2b916c
|
|
| BLAKE2b-256 |
bd12c9c4b486c3d6b6719c1f701d3bc96acd8b217067b545d09084e1a7761e5f
|
Provenance
The following attestation bundles were made for code_is_magic_markers-0.1.0-py3-none-any.whl:
Publisher:
release.yml on Richard-Lynch/markers
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
code_is_magic_markers-0.1.0-py3-none-any.whl -
Subject digest:
5bceb4f39ce642bde4e62e619515628e5dede67097be4bcdcf1dbb477e863a97 - Sigstore transparency entry: 1286367377
- Sigstore integration time:
-
Permalink:
Richard-Lynch/markers@9200f22c537db947c7436ef28452f7be9074ee52 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Richard-Lynch
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9200f22c537db947c7436ef28452f7be9074ee52 -
Trigger Event:
push
-
Statement type: