Skip to main content

PySEL

The Python Simple Expression Language (PySEL) is a simple but powerful expression language that supports manipulation and creation of basic python literals (str, float, int) using a majority of the available operators, but also offers notable features such as attribute/property accessors and method invocation, as well as a global scope at runtime in which you can inject any python object.

This is somewhat influenced by the Spring Expression Language (SpEL), widely used by the Spring portfolio.

Installation

PySEL can be installed using pip:

$ pip install pysel-lang

Feature Overview

The expression language supports the following functionality:

  • Literal expressions
  • Logical, relational and mathematical operators
  • Attribute/property access
  • Method invocation
  • Getitem (list indexing, slicing, dict accessing, etc)
  • Ternary operator
  • Environment value substitution

Expression Evaluation

PySEL provides a utility class, Expression to allow you to run a valid expression in very few lines of code.

For example, the following expression, when evaluated, would return a string literal with value Hello World!:

>>> import pysel
>>> exp = pysel.Expression("'Hello World!'")

To evaluate any expression, call the evaluate() method of the Expression object:

>>> import pysel
>>> exp = pysel.Expression("'Hello World!")
>>> exp.evaluate()
'Hello World!'

You can also pass a mapping containing the execution environment for the expression. Any keys contained in the environment can be accessed from within an expression using an identifier - the name of the key:

>>> import pysel
>>> exp = pysel.Expression("foo")
>>> exp.evaluate({"foo": "bar"})
'bar'

If you do not wish to evaluate the expression immediately but would still like to know if it is syntactically valid, you can call the compile() method of the Expression class. This will parse and validate the expression and return the compiled code object.

Invalid Expression:

>>> import pysel
>>> exp = pysel.Expression("'foo")  # Invalid due to unclosed quotes
>>> exp.compile()
Traceback (most recent call last):
  ...
    raise errors.ExpressionSyntaxError(
pysel.errors.ExpressionSyntaxError: Unexpected EOF while parsing
    "'foo"
     ^   

Valid Expression:

>>> import pysel
>>> exp = pysel.Expression("'foo'")
>>> exp.compile()
<code object <...? at 0x..., file "pysel_expr", line 1>

Language Reference

Literals

PySEL currently supports four different types of literals - string literals, integer literals, float literals, and None.

A string literal is any set of characters surrounded by a matching pair of quotes. Quotes can be either single quotation marks (') or double quotation marks ("). You can also include quotation marks in the string by escaping them using a backslash:

>>> pysel.Expression("'foo'").evaluate()
'foo'
>>> pysel.Expression('"foo"').evaluate()
'foo'
>>> pysel.Expression("'foo\\'s'").evaluate()
"foo's"

An integer literal is any set of consecutive digits (0-9). Unlike Python, PySEL allows integer literals to begin with a 0 digit:

>>> pysel.Expression("1234").evaluate()
1234
>>> pysel.Expression("01234").evaluate()
1234

A float literal is any set of consecutive digits 0-9 followed by a dot .. Float literals can also optionally include digits after the decimal place - if none are specified, e.g. 2. then the number will still parse correctly:

>>> pysel.Expression("1.5").evaluate()
1.5
>>> pysel.Expression("1.").evaluate()
1.0

None is implemented identically to Python.

Logical, relational and mathematical operators

The logical operators that are supported are && (and), || (or) and ! (not). Their use is shown below:

# --- NOT ---
>>> pysel.Expression("!foo").evaluate({"foo": True})
False
>>> pysel.Expression("!foo").evaluate({"foo": False})
True
# --- AND ---
>>> pysel.Expression("foo && bar").evaluate({"foo": True, "bar": False})
False
>>> pysel.Expression("foo && bar").evaluate({"foo": True, "bar": True})
True
# --- OR ---
>>> pysel.Expression("foo || bar").evaluate({"foo": False, "bar": False})
False
>>> pysel.Expression("foo || bar").evaluate({"foo": False, "bar": True})
True

The relational operators that are supported are ==, !=, >, <, >=, and <= - all using standard operator notation:

>>> pysel.Expression("2 == 2").evaluate()
True
>>> pysel.Expression("2 < 5").evaluate()
True
>>> pysel.Expression("2 != 2").evaluate()
False
...

PySEL supports all the same mathematical operators supported by Python, excluding the bitwise operators (for now). Operator precedence follows the order specified in the "Operator Precedence" section.

>>> pysel.Expression("2 + 2").evaluate()
4
>>> pysel.Expression("2 * 3").evaluate()
6
>>> pysel.Expression("5 // 2").evaluate()
2
...

Attribute/property access

Attribute access in PySEL functions identically to that of Python - using the . operator:

>>> pysel.Expression("'foo'.__class__").evaluate()
<class 'str'>

You can also chain attribute accessors to an unlimited depth, as with python:

>>> pysel.Expression("'foo'.__class__.__name__").evaluate()
'str'

Method invocation

As with attribute access, methods are invoked identically to the way you would using Python:

>>> pysel.Expression("str()").evaluate()
''

PySEL also supports calling methods with an infinite number of arguments - however you should note that all arguments will be passed positionally. Keyword arguments are not implemented:

>>> pysel.Expression("str(10)").evaluate()
'10'

Getitem (indexing, slicing, dict accessing)

PySEL's syntax for this is completely identical to Python's. A pair of square brackets immediately following any expression are intepreted as a call to object.__getitem__, as with Python. This allows you to perform indexing, slicing, and dictionary accessing as you would normally:

>>> pysel.Expression("'foobar'[0]").evaluate()
'f'
>>> pysel.Expression("'foobar'[::-1]").evaluate()
'raboof'
>>> pysel.Expression("dict['foo']").evaluate({"dict": {"foo": "bar"}})
'bar'

Ternary operator

PySEL supports the standard ternary operator found in many other languages including but not limited to: C, JS, Java, etc.

The syntax is: condition ? when_true : when_false.

This is functionally equivalent to:

when_true if some_condition else when_false

Example:

>>> pysel.Expression("cond ? 'foo' : 'bar'").evaluate({"cond": True})
'foo'
>>> pysel.Expression("cond ? 'foo' : 'bar'").evaluate({"cond": False})
'bar'

Environment value substitution

As you may have seen in the previous sections, PySEL allows values to be substituted in place of identifiers in any given expression. When calling Expression.evaluate(), you can optionally pass a mapping of identifier name to value which will be accessible from the expression when it is run:

>>> pysel.Expression("foo").evaluate({"foo": "bar"})
'bar'

The default environment contains four identifiers - str, int, float and bool - which are intended to be used for casting values to different types within expressions, but of course you can use them for whatever you wish.

If you pass a mapping to the evaluate method, and some of the identifier names conflict with the ones mentioned above, then the default identifiers will be overridden with the value that you passed:

>>> pysel.Expression("str").evaluate()
<class 'str'>
>>> pysel.Expression("str").evaluate({"str": "foo"})
'foo'

Operator Precedence

  • Literals, parentheses
  • Accessor (., i.e. foo.bar)
  • Method call (i.e. foo()), getitem (i.e. 'foo'[0])
  • ** (exponent)
  • Unary -, +
  • *, /, //, %
  • Binary -, +
  • ==, !=, >, <, >=, <=
  • !
  • &&
  • ||
  • Ternary (i.e. expr ? expr : expr)

Grammar Specification

Note that this does not cover operator precedence, see the above section for that information.

The below is written using Extended Backus-Naur form:

all characters = ? All characters valid in a python string ?;

letter = ? All characters in set [a-zA-Z] ?;

digit = ? All characters in set [0-9] ?;

unop = "-" | "+" | "!";

binop = "==" | "!=" | ">" | "<" | ">=" | "<=" | "&&" | "||" |
        "+" | "-" | "*" | "/" | "%" | "//" | "**";

int = { digit };

float = int, "." [, int];

str = ("'" [, { all characters - "'" | "\'" }], "'") |
        ('"' [, { all characters - '"' | '\"' }], '"');

expr = int | str | float | expr binop expr | unop expr | ternary | identifier | accessor | methodcall | getitem;

ternary = expr, "?", expr, ":", expr;

identifier = (letter | "_") [, { letter | digit | "_" }];

accessor = expr, ".", identifier;

methodcall = expr, "(", expr [, { ",", expr }], ")";

slice = [expr, ] ":" [, expr] [, ":" [expr]]

getitem = expr "[", slice | expr, "]"

Metadata

Release files for pysel-lang 0.0.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pysel-lang 0.0.6
File Size Uploaded Provenance Actions
pysel_lang-0.0.6.tar.gz 19.8 kB Without provenance Details for pysel_lang-0.0.6.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pysel-lang 0.0.6
File Interpreter ABI Platform Actions
pysel_lang-0.0.6-py3-none-any.whl Python 3 none any Details for pysel_lang-0.0.6-py3-none-any.whl

Total release size: 37.3 kB

Release files / pysel_lang-0.0.6.tar.gz

Download URL pysel_lang-0.0.6.tar.gz
Size 19.8 kB
Tags Source
SHA-256 checksum
How to use checksums
09cb7d756332f547e05005520a62e770ca4829d6f53fb6fc6453f449dc334478
BLAKE2b-256 checksum
How to use checksums
6ceed0464c9123b3af1076bef1a67a4ae8acf89bb4ce303bcd27106958ed659c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via python-requests/2.32.3

Release files / pysel_lang-0.0.6-py3-none-any.whl

Download URL pysel_lang-0.0.6-py3-none-any.whl
Size 17.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5d435233c783815cdfc5c577df7406b50b59513c0fd7755eb3fd81257314a5e6
BLAKE2b-256 checksum
How to use checksums
805b148a01fb14f4c34d85e1ab9c8838e4f6aded5c6458d9b95b7d6d36e49fd3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via python-requests/2.32.3

Security for pysel-lang 0.0.6

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.0.6 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page