Python with braces instead of significant indentation -- transpiles .spy to .py, line for line.
Project description
SmartPython
Python with braces instead of significant indentation. Write .spy, get .py — line for line, so every traceback, breakpoint and coverage report still points at the file you actually wrote. Think TypeScript → JavaScript, but the only thing that changes is how blocks are delimited.
A VS code is provided for files *.spy + a command to run a spy file + a command to transform spy -> py
# hello.spy
def greet(names) {
for name in names {
if name {
print(f"hello {name}")
} else {
print("hello, whoever you are")
}
}
}
$ spy run hello.spy
$ spy build hello.spy # writes hello.py
Install
$ pip install -e .
Requires Python 3.9+. Without installing, python -m smartpython works the same as spy.
The language
It is Python. Every rule you know still applies — the transpiler only rewrites block delimiters:
| SmartPython | Python |
|---|---|
if x { y() } |
if x: y() |
if x { … } |
if x: + indented body |
} else { |
else: |
def f() { } |
def f(): pass |
Indentation inside braces is yours to choose; the generated Python is indented with four spaces (--indent N to change it). A trailing : on a header is an error — braces replace it, they don't accompany it.
Both brace styles work:
def read(path)
{
with open(path) as fh { return fh.read() }
}
Semicolons are optional and pass straight through, exactly as in Python.
Braces that aren't blocks
Dict displays, set displays, comprehensions, f-string fields and match patterns all use {} too. The scanner tells them apart by looking at the token in front of the brace: a brace after a value opens a block, a brace after an operator or keyword opens a display. So this parses the way you'd expect:
if value in {1, 2, 3} { # first brace: a set. second: the block.
return "small"
}
match record {
case {"kind": kind} { return kind }
case _ { return None }
}
Braces inside strings, f-strings (including nested quotes, {{/}} escapes and format specs) and comments are never touched.
Commands
spy build PATH... # transpile to .py next to the source
-o OUT # output file, or directory when building several
--stdout # print instead of writing
--bare # omit the "generated by" footer
-f, --force # overwrite a hand-written .py
spy run FILE.spy [ARGS] # transpile and execute, tracebacks mapped back
spy check PATH... # transpile and compile, write nothing
build and check accept directories and recurse into them. - reads source from stdin; pair it with --filename NAME so errors name the real file:
$ spy check - --filename src/app.spy < src/app.spy
Editor support
A VS Code extension lives in editors/vscode — highlighting, live diagnostics as you type, a side-by-side preview of the generated Python, and build/run commands. No build step; symlink it into ~/.vscode/extensions and reload. See its README.
run installs an import hook, so a .spy file can import other .spy modules sitting next to it. To use that hook in a normal Python process:
import smartpython.importer as spy_hook
spy_hook.install()
import my_spy_module # loads my_spy_module.spy
Line-for-line output
The rewrites are chosen so the line count never changes for idiomatic input:
if x { -> if x: the brace becomes the colon
y() -> y()
} -> the closing brace becomes a blank line
} else { -> else: dedent and re-open in place
def f() { } -> def f(): pass short blocks stay inline
def f() { -> def f(): an empty body borrows the '}' line
} -> pass
A few styles can't be preserved — a nested block written inline, or code after a closing brace on the same line. Those expand, and Result.line_map records the real mapping; spy run uses it to rewrite tracebacks.
Library API
from smartpython import transpile, transpile_file, SpyError
result = transpile(source, filename="thing.spy")
result.code # the Python source
result.identity # True when lines map 1:1
result.line_map # generated line -> .spy line
result.source_line(42)
SpyError carries filename, line, col and text, and renders a caret diagram via .pretty().
What it deliberately does not do
No type checking, no new operators, no // comments, no &&/||. SmartPython is exactly Python with a different block delimiter — anything else would make the generated code stop looking like the code you wrote.
Development
$ python -m pytest
$ spy run examples/tour.spy # a tour of the syntax and the tricky cases
Known limits
- A statement whose first token is a bare set/dict display, sitting where an Allman-style
{could go, is ambiguous; the brace wins. - Generated files pick up a blank line wherever a
}stood. That is the price of the 1:1 line mapping. - Continuation lines inside brackets keep their original indentation rather than being re-indented.
License
Author
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 smartpython-0.1.0.tar.gz.
File metadata
- Download URL: smartpython-0.1.0.tar.gz
- Upload date:
- Size: 22.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c988cf78428d3564fa6cf97687b79cdd06e7a42ee903065c9c355c03490e0c2
|
|
| MD5 |
cb58e5f7372e3f3057797a0df31a989b
|
|
| BLAKE2b-256 |
bcfc98c5cfbabec81df91afbc02f6c54f1602b42af98a80d6372b91b158d620a
|
File details
Details for the file smartpython-0.1.0-py3-none-any.whl.
File metadata
- Download URL: smartpython-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
547275c4091974ab84996fb73707745d1dce0c95862b3eb569d4cae93bb28908
|
|
| MD5 |
3228e67e86b91dde748912902004b7b8
|
|
| BLAKE2b-256 |
787ba184a776665fd00fb75ebc8a78a6c3c4698f60db845a5afc5eba96ef10cb
|