Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SymPy Olympiad API

A small Flask service that puts a few SymPy operations behind HTTP endpoints. You POST a math expression as JSON and get the simplified, solved, or numerically evaluated result back as a string. It exists so a program that cannot import SymPy directly — an agent loop, a notebook on a locked-down host, a spreadsheet — can still call out for a symbolic answer.

It is one file, 338 lines. There is no database, no auth, no user model, and no queue. If you need symbolic math inside a Python process, import SymPy instead; this is only useful when the caller is somewhere else.

Security: read this before deploying it

SymPy's parse_expr tokenizes the string you give it and then calls eval() on the result. On unvalidated input that is remote code execution, not a theoretical one — before the checks below existed, posting ().__class__ returned Python's tuple class, which is the first step of the usual walk to object.__subclasses__().

The service now validates every expression before it reaches parse_expr:

  • a 500 character cap
  • a character allow-list — no underscore (blocks dunder names), no brackets (blocks indexing and comprehensions), no colon (blocks lambda), no quotes
  • a reject on attribute access (. followed by a letter; 1.5 still parses)
  • an explicit function allow-list — any name used as a call must be one of the 23 SymPy functions in SAFE_GLOBALS, and parse_expr is given that restricted global_dict, so an unlisted name can only ever become a plain Symbol
  • variable names must match ^[A-Za-z][A-Za-z0-9]*$ before going to symbols()

That is defence in depth, not a sandbox. It closes the paths that are easy to reach from a JSON body; it is not a proof that none remain. Do not put this on the public internet without a real isolation boundary — a container with no network egress, a seccomp profile, a CPU and memory limit. There is also no authentication and no rate limiting, and simplify and integrate can be made to run for a very long time on a hostile input.

It is built to run locally, or on a private instance you control. That is the intended use.

Endpoints

All take POST with a JSON body. Errors come back as {"error": "..."} with a 400.

Endpoint Body Returns
GET / service name and endpoint list
/algebra/solve {"expression": "x^2 - 4", "variable": "x"} {"solutions": ["-2", "2"]}
/algebra/simplify {"expression": "(x^2 - 1)/(x - 1)"} {"result": "x + 1"}
/geometry/solid {"expression": "V = 4/3*pi*r^3; r = 2"} {"result": "33.5103216382911"}
/combinatorics {"expression": "binomial(10, 3)"} {"result": "120.000000000000"}
/number-theory {"expression": "gcd(48, 18)"} {"result": "6.00000000000000"}
/olympiad/advanced see below {"result": "2*x + 2"}

^ means exponentiation, not XOR — convert_xor is enabled because that is what people write in a math expression.

/geometry/solid takes a formula, then semicolon-separated numeric substitutions. The left-hand side of the first part (V =) is discarded; only the right-hand side is parsed.

/olympiad/advanced applies up to 20 operations in order, carrying the result forward:

{
  "steps": [
    {"operation": "expand", "expression": "(x + 1)^2"},
    {"operation": "differentiate", "variable": "x"}
  ]
}

Operations: simplify, solve, substitute, expand, factor, differentiate, integrate.

Example call:

curl -X POST http://localhost:5000/algebra/solve \
  -H "Content-Type: application/json" \
  -d "{\"expression\": \"x^2 - 4\", \"variable\": \"x\"}"

There is no /geometry/plane. It used to exist and was a character-for-character copy of /algebra/simplify, so it was removed; plane-geometry expressions go through /algebra/simplify like anything else.

Running it

pip install -r requirements.txt
pip install pytest              # tests only, not a runtime dependency
python -m pytest test_app.py    # 22 tests
python app.py                   # dev server on 127.0.0.1:5000

python app.py is for local work only. It binds to loopback and runs with debug=False — the Werkzeug debugger exposes an interactive Python console to anyone who can reach the port, so it stays off.

Deployment goes through gunicorn. render.yaml starts gunicorn app:app rather than python app.py, which would have executed the __main__ block and started the development server in production.

Limits

  • Results are returned as strings from str(), not structured math. The caller has to parse them back.
  • evalf() output carries SymPy's default 15 digits, so gcd(48, 18) reads as 6.00000000000000.
  • No auth, no rate limiting, no request timeout beyond gunicorn's.
  • The endpoint split is mostly cosmetic. /combinatorics and /number-theory differ only in that the first defers evaluation while parsing; the names document intent for the caller, nothing more.

About

A small Flask service that puts SymPy solve, simplify, and numeric evaluation behind HTTP endpoints, with input validation in front of parse_expr.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages