Skip to content

liyif/Tomori

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tomori

Use Python as a declarative Markup language with field dependencies automatically resolved.

⚠️ This is a simple experimental project, not intended for production configuration management.

Features

  • 📝 Write structured data in Python with nested dictionaries and objects.
  • 🔄 Support multiple file merging and automatically updates dependent fields when other fields change.
  • 📦 Supports python operations like importing libraries and calling functions during configuring .

Installation

pip install git+https://github.com/liyif/Tomori.git

Getting Started

config.tomori

from tomori import opendata

# Helper functions
def make_url(service, host, port):
    return f"http://{host}:{port}/{service}"

with opendata() as d:
    # ⚠️ Important note:
    # In `opendata()` blocks, statements except Assign and With are not allowed.
    
    # Application info
    with d.app as app: 
        # Different from standard python, 
        # the as-name there has a strict scope in the block.
        app.name = "MyApp"
        app.env = "dev"
        app.host = "localhost"
        app.port = 8080
        
    # or 
    # d.app = dict(name="MyApp",env="dev",host="localhost",port=8080)
        
    d.app.debug = (d.app.env == "dev")
    
    # Services 
    with d.services as s:
        with s.auth as auth:
            auth.name = "auth"
            auth.url = make_url(auth.name, d.app.host, d.app.port)

        with s.payments as payments:
            payments.name = "payments"
            payments.url = make_url(payments.name, d.app.host, d.app.port)

        with s.notifications as notif:
            notif.name = "notifications"
            notif.url = make_url(notif.name, d.app.host, d.app.port)

    # Derived fields
    d.serviceCount = len(d.services)
    d.dynamicFlag = not d.app.debug

config.prod.tomori

from tomori import opendata, values

with opendata() as d:
    d.app.env = "prod"
    d.app.host = "0.0.0.0"
    
    # ⚠️ Important note:
    # Each field can be redeclared or override.
    # However, please avoid patterns where a field is read and reassigned in the same expression (self-modification).
    # Not Recommend:
    # d.app = d.app | { "port": 80 }
    # No guarantee that is evaluated after all subfields are built.
    # Just this:
    d.app.port = 80

    # ⚠️ Important note:
    # Using `d.services.values()` would create a dependency on the `values` attribute itself,
    # which might be evaluated before all other subfields (auth, payments, notifications) are built.
    # Using `tomori.values(d.services)` or simply `getattr(d.services, "values")()` ensures 
    # that the dependency is on `d.services` itself, 
    # so all fields are fully constructed before the comprehension executes.
    d.servicesUrls = [ s.url for s in values(d.services) ]
    

main.py

from tomori import Data
import json

data = (Data()
        .include_file("config.tomori")
        .include_file("config.prod.tomori")
        .resolve())

print(json.dumps(data, indent=4))

output

{
    "app": {
        "name": "MyApp",
        "env": "prod",
        "host": "0.0.0.0",
        "port": 80,
        "debug": false
    },
    "services": {
        "auth": {
            "name": "auth",
            "url": "http://0.0.0.0:80/auth"
        },
        "payments": {
            "name": "payments",
            "url": "http://0.0.0.0:80/payments"
        },
        "notifications": {
            "name": "notifications",
            "url": "http://0.0.0.0:80/notifications"
        }
    },
    "dynamicFlag": true,
    "serviceCount": 3,
    "servicesUrls": [
        "http://0.0.0.0:80/auth",
        "http://0.0.0.0:80/payments",
        "http://0.0.0.0:80/notifications"
    ]
}

How it works

Tomori uses libcst to do static analysis on inputs, then toposort the statements by their reads and writes.

So you may not expect it can magically resolve any dependencies like a reactive spreadsheet.

About

Use Python as a declarative Markup language with field dependencies automatically resolved.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages