Skip to main content

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_handler decorator 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

python_museflow-0.0.3.tar.gz (72.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

python_museflow-0.0.3-py3-none-any.whl (107.1 kB view details)

Uploaded Python 3

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

Hashes for python_museflow-0.0.3.tar.gz
Algorithm Hash digest
SHA256 189cc5fbb03a58ad10506b19e23b3709227df4c7296427b7ab0ca612b7ff222b
MD5 dfa1bcdbf73fddf01db3993318e22ac0
BLAKE2b-256 829d70931ad37d224278ebfe229f3d2781e37a8d9f7d434438dc4404abf8fac9

See more details on using hashes here.

File details

Details for the file python_museflow-0.0.3-py3-none-any.whl.

File metadata

File hashes

Hashes for python_museflow-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 e2630a5f926436dd93265d8d36f454a730f5aac2f9b62729ca1a7dd8535259dc
MD5 2ec382300ff691e31a5aab9453884458
BLAKE2b-256 dba981420f39add3458349de67b358aa71476355018c9c4899d1ee9755e2812d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page