MuseFlow is a Python framework for developing full-stack applications entirely in Python, eliminating the need for HTML, CSS, or JavaScript
Project description
Museflow
MuseFlow is a Python framework for developing full-stack applications entirely in Python, eliminating the need for HTML,
CSS, or JavaScript
With MuseFlow, developers can build web applications and APIs using pure Python code, streamlining development and
reducing context switching between programming languages
Museflow operates on two core mechanics:
- Flexible Server:
Museflow provides a Python-based server that can handle HTTP requests, serve pages, and manage APIs -
allowing you to run full-stack applications without leaving Python - HTML Tree:
Instead of using HTML, CSS, and JavaScript, Museflow lets you construct an HTML tree using Python objects
Elements can be nested, styled, and combined programmatically -
giving you full control over page structure and dynamic content
Together, these mechanics enable developers to build interactive, full-featured web applications entirely in Python.
Installation
Pip: pip install python-museflow
Features
Client Side / Frontend
- Python-Based HTML Tree – Build and manage HTML structures entirely using Python
- Python-to-JavaScript Compilation – Automatically convert Python code to JavaScript, with full support for dynamic
imports and namespaces
Server Side / Backend (Flexible Server)
-
Routing:
Add routes with support for all standard HTTP methods (GET, POST, PUT, PATCH, DELETE)
Routes can be safely added even while the server is running -
Easy Route Handlers:
Quickly define HTTP route handlers using@route_handlerdecorator minimal boilerplate and automatic parameter parsing -
Request Size Limiting:
Optionally limit the maximum size of incoming request bodies to prevent abuse or accidental overloads
Returns HTTP 413 if exceeded
* In production, it is recommended to enforce request size limits at the proxy or load balancer -
Concurrency Control:
Optionally limit the number of concurrent requests to avoid resource exhaustion
* In production, it is recommended to enforce concurrent request limits at the proxy or load balancer -
Request Timeouts:
Protect against handlers that hang or take too long by enforcing per-request timeouts
Returns HTTP 504 if exceeded
* In production, it is recommended to enforce request timeouts at the proxy or load balancer -
Graceful Shutdown:
Proper handling of system signals (SIGINT/SIGTERM) allows clean shutdown without dropping ongoing requests -
Built-in Logging:
Optional configurable logging for requests and internal errors
Logging can be completely disabled for silent operation or redirected via a custom logger -
Error Handling:
Automatic HTTP 500 responses for uncaught exceptions in handlers, preventing the server from crashing -
Custom Response Headers:
Ability to set global headers for all responses, useful for security headers or default CORS configuration -
Thread-Safe Route Management:
Supports safe modification of routes while the server is serving requests -
Streaming File Handling:
Streaming file handling allows FlexibleServer to process large uploads and downloads efficiently by -
reading and writing data in chunks instead of loading entire files into memory
This greatly reduces memory usage and enables stable performance even with multi-gigabyte files -
Cipher & SSL Features:
generate_key()- Securely creates and stores an RSA private key (PEM format) with optional password-based encryption
Supports configurable key size and exponent for flexible cryptographic strength
generate_certificate()- Generates X.509 certificates (Self-Signed or CA-Signed) -
with full control over subject attributes, validity period, and SAN entries, ensuring robust SSL identity management
self_signed_ssl_context()- Provides an automatic, temporary self-signed SSL context for -
HTTPS testing and local development eliminating the need for pre-existing certificate files
Production-Ready Recommendations
FlexibleServer can enforce request size limits, concurrent request limits, and timeouts -
but for maximum robustness in production it is recommended to deploy it behind a reverse proxy like Nginx -
which handles rate limiting, SSL/TLS, and request management more efficiently
Museflow Guide
1. Basic HTML Page
Each HTML element is represented by a Python object (Element)
You build a tree by attaching child elements to their parent using the .adopt() method
Any element can serve as the root of a tree, giving you flexibility to start from <html> or even a single <div>
Trees can also be combined by adopting one tree into another, allowing you to reuse and compose UI parts
from museflow.element.inventory import html, head, body, div, h1
from museflow.museflow import Museflow
root = html().adopt([
head(),
body().adopt(
div(_id='container').adopt(
h1(content='Hello World')
)
)
])
Museflow.render(root=root)
"""
Output:
<html>
<head></head>
<body>
<div id="container">
<h1>
Hello World
</h1>
</div>
</body>
</html>
"""
2. Render to HTML File
The Museflow.render_file() method generates a complete HTML file from a root element tree
You provide the root of your tree and a target filename, and Museflow writes the fully rendered HTML
from pathlib import Path
from museflow.element.inventory import html, head, body, div, h1
from museflow.museflow import Museflow
root = html().adopt([
head(),
body().adopt(
div(_id='container').adopt(
h1(content='Hello World')
)
)
])
Museflow.render_file(root=root, target_file=Path('index.html'))
3. Custom Indentation
The Museflow.render() method converts an element tree into an HTML string
By setting the indent parameter, you can control the formatting: indent=0 -
produces compact HTML without extra spaces while higher values create nicely indented, readable output
from museflow.element.inventory import html, head, body, div, h1
from museflow.museflow import Museflow
root = html().adopt([
head(),
body().adopt(
div(_id='container').adopt(
h1(content='Hello World')
)
)
])
Museflow.render(root=root, indent=0)
"""
Output:
<html>
<head></head>
<body>
<div id="container">
<h1>
Hello World
</h1>
</div>
</body>
</html>
"""
4. Style Object
The Style object is a Python representation of CSS styles, encapsulating all CSS properties as attributes
It allows developers to define and manage styling programmatically -
while benefiting from Python’s type checking and IDE autocomplete features
* It is recommended to store 'Style' objects in dedicated files, to enhance reusability and maintainability
from museflow.element.inventory import html, head, body, div, h1
from museflow.element.style import Style
from museflow.museflow import Museflow
root = html().adopt([
head(),
body().adopt(
div().adopt(
h1(
content='Hello World',
style=Style(
font_size='16px',
color='red'
)
)
)
)
])
Museflow.render(root=root)
"""
Output:
<html>
<head></head>
<body>
<div>
<h1 style="color: red; font-size: 16px">
Hello World
</h1>
</div>
</body>
</html>
"""
5. Style Updating
The Style.update() method provides a clean way to extend or override existing style definitions
It returns a new Style object, combining the attributes of the original style with those of another
This makes it easy to reuse base style configurations and adapt them for specific elements without mutation
from museflow.element.style import Style
base_style = Style(font_size='14px', color='black')
highlight_style = Style(color='red', font_weight='bold')
updated_style = base_style.update(highlight_style)
"""
Updated style attributes:
font_size='14px'
color='red'
font_weight='bold'
"""
6. Dynamic Rendering
Dynamic rendering is the ability to manipulate the HTML tree using Python logic -
such as loops, conditionals, and functions, to generate content programmatically
This allows you to create flexible, data-driven UI structures, apply conditional styling, and reuse components -
without manually writing repetitive HTML
from museflow.element.inventory import html, head, body, div, h1, ul, li
from museflow.element.style import Style
from museflow.museflow import Museflow
items = ['Apple', 'Banana', 'Cherry']
highlight_fruit = 'Banana'
def generate_list(items, highlight=None):
""" Create a <ul> with <li> items, optionally highlighting one item """
children = []
for item in items:
style = Style(color='red') if item == highlight else Style()
children.append(li(content=item, style=style))
return ul().adopt(children)
root = html().adopt([
head(),
body().adopt([
div().adopt(
h1(
content='Fruit List',
style=Style(font_size='18px', color='blue')
)
),
div().adopt(
generate_list(items, highlight=highlight_fruit)
)
])
])
Museflow.render(root=root)
"""
Output:
<html>
<head></head>
<body>
<div>
<h1 style="color: blue; font-size: 18px">
Fruit List
</h1>
</div>
<div>
<ul>
<li style="">
Apple
</li>
<li style="color: red">
Banana
</li>
<li style="">
Cherry
</li>
</ul>
</div>
</body>
</html>
"""
7. Simple Embedded Script
Embedded Script is a Python script that is compiled to JavaScript using the compiler and injected into a tree
This enables you to write logic in Python—such as handling events, updating content, or manipulating the DOM -
while keeping your code modular and maintainable
By embedding scripts, you can seamlessly integrate Python logic with your HTML for fully interactive trees
# - - - embedded_script.py - - - #
# <-PY_SKIP->
from museflow.toolkit.pyjs_stubs import console
# <-PY_SKIP_END->
console.log('Hello World')
# - - - main.py - - - #
from museflow.element.inventory import html, head, body, div, h1
from museflow.museflow import Museflow
from pathlib import Path
root = html().adopt([
head(),
body().adopt(
div(_id='container').adopt(
h1(content='Hello World')
)
)
])
py_script = Museflow.load_py_script(Path('./embedded_script.py'))
Museflow.render(root=root, script=py_script)
"""
Output:
<html>
<head>
<script>
console.log ('Hello World');
</script>
</head>
<body>
<div id="container">
<h1>
Hello World
</h1>
</div>
</body>
</html>
"""
8. Embedded Script - load_py_script
Scripts in Museflow must be loaded via load_py_script() rather than directly passed as text
This is because it resolves the absolute path of the script and correctly handles relative imports -
ensuring that any # <-PY_IMPORT-> statements are properly inlined and namespaced
* Embedding a script as a string could lead to missing dependencies or naming conflicts !
Using load_py_script() guarantees that the full dependency tree is processed and integrated correctly
9. Embedded Script - PY SKIP
Code placed between # <-PY_SKIP-> and # <-PY_SKIP_END-> will not be compiled or executed
This is typically used for offline development or stubbing
The toolkit.pyjs_stubs module provides fake browser objects such as document, console, and alert -
that simulate the DOM and browser APIs in Python, allowing you to write code without a browser -
while still supporting autocomplete and type checking in your IDE
10. Embedded Script - PY IMPORT
Museflow supports custom script imports using the # <-PY_IMPORT-> <namespace>:<relative path> syntax.
This allows you to modularize your Python code and include other scripts
The <namespace> is used to prefix all top-level functions, classes, and variables from the imported script -
to prevent naming collisions
The <relative path> specifies the location of the script relative to the importing file
When the page is rendered, Museflow automatically inlines and namespaces the imported script -
making it available for use in your main code
Remember !
# <-PY_IMPORT-> is not intended for IDE autocomplete or syntax checking
For those purposes, you should use a standard Python import inside a # <-PY_SKIP-> … <-PY_SKIP_END-> block,
which allows your editor to understand the module without affecting the Museflow compilation
"""
scripts/
├── main.py
├── script_a.py
└── script_b.py
"""
# - - - script_a.py - - - #
# <-PY_IMPORT-> b:./script_b.py
def greet_a():
return 'Hello from Script A!'
# - - - script_b.py - - - #
def greet_b():
return 'Hello from Script B!'
# - - - main.py - - - #
# <-PY_IMPORT-> a:./script_a.py
# <-PY_IMPORT-> b:./script_b.py
def main():
greet_a()
greet_b()
11. Development Server
MuseflowDevServer is a development server that automatically watches your Python source files and re-runs the specified render module whenever changes are detected
It serves the rendered HTML on a local web server and supports live-reloading in the browser, ensuring that updates to your code are reflected immediately
The server isolates itself from the caller module to prevent recursion !
# - - - Project Structure - - - #
Museflow/
├── museflow_webpage/
│ ├── dev_server.py # The dev server starter script
│ ├── main.py # The main module that defines `render_file(root, target_file)`
│ ├── index.html # The generated HTML (output)
│ ├── page/ # Subpages or components
│ │ ├── __init__.py
│ │ └── home.py # Example page module
│ ├── script/ # Any JS modules (optional)
│ │ └── __init__.py
│ ├── style/ # CSS/Palette modules
│ │ ├── palette.py
│ │ ├── style.py
│ │ └── __init__.py
│ └── assets/ # Static assets like images or fonts
│ └── logo.png
└── .venv/ # Your Python virtual environment
# - - - main.py - - - #
from pathlib import Path
from museflow.element.inventory import html, head, body, div, h1
from museflow.element.style import Style
from museflow.museflow import Museflow
root = html().adopt([
head(),
body().adopt(
div().adopt(
h1(
content='Hello World',
style=Style(font_size='16px')
)
)
)
])
Museflow.render_file(root=root, script=None, target_file=Path('index.html'))
# - - - dev_server.py - - - #
from pathlib import Path
from museflow.museflow_dev_server import MuseflowDevServer
"""
Parameters:
- project_to_watch:
Path to the root of your project to watch for changes
All Python files under this path will be monitored for automatic reload
- render_module_file:
The Python module that the development server executes whenever a change is detected in the project
This module must call render_file(root, target_file) during its execution
If render_file is not called, the server will not generate or update the HTML, and nothing will be served to the browser
Path must be relative to the project directory !
- target_file:
The HTML file that will be generated/rendered by render_file and served in the browser
Path must be relative to the project directory !
- port:
The port number where the dev server will listen for HTTP requests
- log:
Enable logging for server events, file changes, and module reloads
- watch_interval:
Interval (in seconds) to check for file changes in the project
"""
MuseflowDevServer().serve(
project_to_watch=Path(__file__).parent.resolve(),
render_module_file=Path('main.py'),
target_file=Path('index.html'),
port=8001,
log=True,
watch_interval=0.5
)
FileNotFoundError: [Errno 2] No such file or directory: '<File>' - Indicates:
- Invalid render_module_file (Missing
render_file()) - Invalid
target_file - Invalid
project_to_watch
12. Inject
Unlike adopt(), inject() inserts an item at the top of the element's content
If the element has no content, the item becomes the content
If the content is a list, the item is inserted at index 0 (top)
If the content is a single item, it is converted into a list with
the new item placed first
from museflow.element.inventory import html, h1
root = html().inject(h1(content='Hello World'))
FlexibleServer Guide
1. Instantiating Flexible Server
import logging
from museflow.flexible_server import FlexibleServer
logger = logging.getLogger('Flexible Server')
server = FlexibleServer(
logger=logger, # Optional: Custom logger (Default: None)
log=True, # Enable server logging (Default: True)
request_size_limit=1024 * 1024, # Optional: Max request size in bytes (Default: None - Unlimited)
max_concurrent_requests=10, # Optional: Max concurrent requests (Default: None - Unlimited)
request_timeout=5.0, # Optional: Per-request timeout in seconds (Default: None - Unlimited)
global_response_headers=None # Optional: Dict of headers added to server responses (Default: None)
)
* FlexibleServer must be instantiated in the main thread due to Python’s signal handling limitations
Attempting to create it in a background thread will raise an exception
import threading
from museflow.flexible_server import FlexibleServer, FlexibleServerException
def create_server_in_thread():
try:
_ = FlexibleServer() # ❌ Raises FlexibleServerException
except FlexibleServerException as e:
print(f'Error: {e}')
thread = threading.Thread(target=create_server_in_thread)
thread.start()
thread.join()
2. Serving the FlexibleServer
from museflow.flexible_server import FlexibleServer
server = FlexibleServer()
server.serve(
host='localhost', # Host to bind to (Default: "localhost")
port=8001, # Port to listen on (Default: 8001)
cert_file=None, # Optional: Path to SSL certificate for HTTPS (Default: None)
key_file=None, # Optional: Path to private key for HTTPS (Default: None)
ca_file=None # Optional: Path to CA bundle for HTTPS client verification (Default: None)
)
HTTPS Support:
If both cert_file and key_file are provided when calling server.serve() -
FlexibleServer automatically runs in HTTPS mode
The server will use the provided certificate and private key to encrypt all connections
If either parameter is None, the server defaults to plain HTTP
You can optionally provide ca_file for client certificate verification in mutual TLS setups
* Always use HTTPS in production to protect sensitive data
from museflow.flexible_server import FlexibleServer
server = FlexibleServer()
# Start HTTPS server
server.serve(
host='localhost',
port=8443,
cert_file='path/to/server.crt', # Server certificate
key_file='path/to/server.key', # Private key
ca_file=None # Optional: CA file for client verification
)
Running in Background:
import threading
from museflow.flexible_server import FlexibleServer
server = FlexibleServer()
# Run the server in a thread
thread = threading.Thread(
target=server.serve,
kwargs={'host': 'localhost', 'port': 8001},
daemon=True
)
thread.start()
3. Routing
@route_handler decorator simplifies implementing request handlers for FlexibleServer
It automatically adapts the raw HTTP request data into Python-friendly arguments
Route-Handler Key Features:
- Automatic Request Parsing:
body: Converts raw bytes to a string
query: Converts query parameters into a flat dictionary
request: Optionally gives access to the request object
files: Optionally gives access to uploaded files - Signature-Based Mapping:
Only passes body, query, files, and request if your handler declares them - Consistent Return:
Handlers return a (body, status_code) tuple
import json
import os
import tempfile
from http import HTTPStatus
import requests
from museflow.flexible_server import FlexibleServer, route_handler
server = FlexibleServer()
# --- Query Example (GET /hello?name=Alice)
@route_handler
def hello_get(query):
name = query.get('name', 'World')
return f'<h1>Hello (GET), {name}!</h1>', HTTPStatus.OK
# --- GET with Query and Request
@route_handler
def hello_get(request, query):
client_ip = request.client_address[0]
name = query.get('name', 'World')
return f'<h1>Hello {name}!</h1><p>From {client_ip}</p>', HTTPStatus.OK
# --- JSON Body Example (POST /hello)
@route_handler
def hello_post(body):
data = json.loads(body) if body else {}
return f'<h1>Hello (POST), body={data}</h1>', HTTPStatus.OK
# --- File Upload Example (POST /upload)
@route_handler
def upload_file(files):
""" Save uploaded file to temp dir """
upload_dir = tempfile.gettempdir()
saved_paths = []
for filename, filepath in files.items():
dest = os.path.join(upload_dir, filename)
os.replace(filepath, dest)
saved_paths.append(dest)
return {'uploaded_files': saved_paths}, HTTPStatus.OK
# --- File Download Example (GET /download?file=example.txt)
@route_handler
def download_file(query):
filename = query.get('file')
if not filename:
return 'Missing "file" parameter', HTTPStatus.BAD_REQUEST
filepath = os.path.join(tempfile.gettempdir(), filename)
if not os.path.exists(filepath):
return 'File not found', HTTPStatus.NOT_FOUND
with open(filepath, 'rb') as fd:
content = fd.read()
return content, HTTPStatus.OK
# Register routes
server.add_route('GET', '/hello', hello_get)
server.add_route('POST', '/hello', hello_post)
server.add_route('POST', '/upload', upload_file)
server.add_route('GET', '/download', download_file)
# --- GET query example
r = requests.get('http://localhost:8001/hello', params={'name': 'Alice'})
print(r.text) # <h1>Hello (GET), Alice!</h1>
# --- POST body example
r = requests.post('http://localhost:8001/hello', data=json.dumps({'lang': 'Python'}))
print(r.text) # <h1>Hello (POST), body={'lang': 'Python'}</h1>
# --- File upload example
tmp = tempfile.NamedTemporaryFile(delete=False)
tmp.write(b'example content')
tmp.close()
with open(tmp.name, 'rb') as f:
files = {'file': ('example.txt', f)}
res = requests.post('http://localhost:8001/upload', files=files)
print(res.json())
# --- File download example
filename = os.path.basename(tmp.name)
res = requests.get('http://localhost:8001/download', params={'file': 'example.txt'})
print(res.status_code, len(res.content))
Project details
Release history Release notifications | RSS feed
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 python_museflow-0.0.3.tar.gz.
File metadata
- Download URL: python_museflow-0.0.3.tar.gz
- Upload date:
- Size: 72.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
189cc5fbb03a58ad10506b19e23b3709227df4c7296427b7ab0ca612b7ff222b
|
|
| MD5 |
dfa1bcdbf73fddf01db3993318e22ac0
|
|
| BLAKE2b-256 |
829d70931ad37d224278ebfe229f3d2781e37a8d9f7d434438dc4404abf8fac9
|
File details
Details for the file python_museflow-0.0.3-py3-none-any.whl.
File metadata
- Download URL: python_museflow-0.0.3-py3-none-any.whl
- Upload date:
- Size: 107.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e2630a5f926436dd93265d8d36f454a730f5aac2f9b62729ca1a7dd8535259dc
|
|
| MD5 |
2ec382300ff691e31a5aab9453884458
|
|
| BLAKE2b-256 |
dba981420f39add3458349de67b358aa71476355018c9c4899d1ee9755e2812d
|