Skip to main content

Regular Expressions Made Simple

Project description

Cover Image

Documentation Status GitHub Actions Build Status Coverage Status PyPI Package latest release PyPI Wheel Supported versions Supported implementations Commits since latest release

Edify (/ˈɛdɪfaɪ/, “ed-uh-fahy”) is a Python library that allows you to easily create regular expressions for matching text in a programmatically-friendly way. It is designed to be used in conjunction with the re module.

It also allows you to verify a string quickly by providing commonly used regex patterns in its extensive set of built-in patterns. To tap into a pattern, simply import the pattern function from the edify.library module.

Quick Start

To get started make sure you have Python 3.8-3.14 installed and then, install Edify from pip:

pip install edify

You can also install the in-development version with:

pip install https://github.com/luciferreeves/edify/archive/main.zip

Then go on to import the RegexBuilder class from the edify module.

Using Pre-Built Patterns

The following example recognises and captures any email like email@example.com.

from edify.library import email

email_addr = "email@example.com"
assert email(email_addr) == True

Building Regex Example: Validating 16-bit Hexadecimal Number

The following example recognises and captures the value of a 16-bit hexadecimal number like 0xC0D3.

from edify import RegexBuilder

expr = (
    RegexBuilder()
    .start_of_input()
    .optional().string("0x")                # match an optional "0x" string
    .capture()
        .exactly(4).any_of()                # let x = any characters between (a-f, A-F, and 0-9)
            .range("A", "F")                # now capture exactly 4 such groups of "x"
            .range("a", "f")                # this will give the match for the number like "C0D3"
            .range("0", "9")                # which when combined with "0x" becomes a 16-bit hexadecimal number
        .end()
    .end()
    .end_of_input()
    .to_regex()                             # used to convert to `re` compatible form
)

"""
Produces the following regular expression:
re.compile(^(?:0x)?([A-Fa-f0-9]{4})$)

Using `to_regex_string()` instead of `to_regex()` at the end
will give the compiled regex string.
"""

assert expr.match("0xC0D3")

Building Regex Example: Validating Signed Integer

The following example recognises and checks if a number is a valid signed integer or not (eg. -45 or +45).

from edify import RegexBuilder

# expression for matching any signed integer. compiles to '^[\+\-]{1}\d+$'
expr = (
    RegexBuilder()
        .start_of_input()
            .exactly(1).any_of()            # capture either '+' or '-', exactly once
                .char('+')
                .char('-')
            .end()
            .one_or_more().digit()          # capture any number of digits
        .end_of_input()
    .to_regex()
)

if expr.match('-69'):
    print("Matched")                        # prints matched

Building Regex Example: Simple URL Validator

The following example checks if a string is a valid url in the form of https://www.example.com/path/to/file.ext

from edify import RegexBuilder

# expression for validating URLs
validate_urls = (
    RegexBuilder()
        .optional().string('http://')       # look for an optional "http://"
        .optional().string('https://')      # or "https://"
        .one_or_more().any_of()             # let x = any characters between (a-z, 0-9, '-', and '.')
            .range('a', 'z')                # now capture one or more groups of "x"
            .range('0', '9')                # essentially capturing all url patterns in the form of
            .any_of_chars('.-')             # xxx.yyyyyyy.abra-cadabra.com
        .end()
        .at_least(2).any_of()               # this is to make sure we get at least 2 characters in
            .range('a', 'z')                # the end of the string, which would be our domain
        .end()
        .zero_or_more().any_of()
            .range('a', 'z')                # same logic as capturing the url in step 1, but now
            .range('0', '9')                # we are essentially looking for an optional path
            .any_of_chars('/.-_%')          # and we add some more characters supported in path
        .end()
    )

# compiles to '^(?:http://)?(?:https://)?([a-z0-9\\.]+[a-z]{2,}[a-z0-9/\\.\\-_%]*)$'
expr = (
    RegexBuilder()
        .ignore_case()                      # case does not matter
        .subexpression(validate_urls)       # directly writing the subexpression works too
    .to_regex()                             # convert to regex finally
)


if expr.match('https://SOMETHING.www.exam-ple.com/path/to/file.txt'):
    print("Matched")                        # prints matched

Building Regex Example: Simple Password Validator

The regular expression below cheks that a password:

  • Has minimum 8 characters in length.

  • At least one uppercase English letter.

  • At least one lowercase English letter.

  • At least one digit.

  • At least one special character from #?!@$%^&*-.

from edify import RegexBuilder

# expression for validating passwords - complies to '^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$'
expr = (
    RegexBuilder()
        .start_of_input()   # asserts position at start of a line
            .assert_ahead() # positive look ahead
                # matches any character in range 'A' to 'Z' zero and unlimited times,
                # as few times as possible, expanding as needed (lazy) - matching at least 1 uppercase character
                .zero_or_more_lazy().any_char().range('A', 'Z')
            .end()
            .assert_ahead()
                 # at least 1 lowercase character
                .zero_or_more_lazy().any_char().range('a', 'z')
            .end()
            .assert_ahead()
                # at least 1 number
                .zero_or_more_lazy().any_char().range('0', '9')
            .end()
            .assert_ahead()
                # at least 1 special character present in the list
                .zero_or_more_lazy().any_char().any_of_chars('#?!@$%^&*-')
            .end()
            .at_least(8).any_char() # must be at least 8 characters long
    .end_of_input()
    .to_regex()
)

if expr.match('-Secr3t!'):
  print("Matched")  # prints matched

Further Documentation

Further API documentation is available on edify.rftd.io.

Why Edify?

Regex is a powerful tool, but its syntax is not very intuitive and can be difficult to build, understand, and use. It gets even more difficult when you have to deal with backtracking, look-ahead, and other features that make regex difficult.

That’s where Edify becomes extremely useful. It allows you to create regular expressions in a programmatic way by invoking the RegexBuilder class [1]. The API uses the fluent builder pattern, and is completely immutable. It is built to be discoverable and predictable.

  • Properties and methods describe what they do in plain English.

  • Order matters! Quantifiers are specified before the thing they change, just like in English (e.g. RegexBuilder().exactly(5).digit()).

  • If you make a mistake, you’ll know how to fix it. Edify will guide you towards a fix if your expression is invalid.

  • subexpressions can be used to create meaningful, reusable components.

Edify turns those complex and unwieldy regexes that appear in code reviews into something that can be read, understood, and properly reviewed by your peers - and maintained by anyone!

License & Contributing

This project is licensed under Apache Software License 2.0. See Contributing Guidelines for information on how to contribute to this project.

Contributors

https://contrib.rocks/image?repo=luciferreeves/edify

Footnotes

Changelog

0.3.0 (2026-04-29)

A maintenance release: Edify is dragged out of 2022 and back into modern shape. No new patterns or builder API. The minimum supported Python rises to 3.8.

Breaking

  • Dropped support for Python 3.7. Edify now requires Python 3.8 or newer (32).

Added

  • Support for Python 3.12, 3.13, and 3.14, with the matrix and Read the Docs build configuration updated to match (31).

Tooling and CI

  • Bumped GitHub Actions to current major versions: actions/checkout@v5, actions/setup-python@v5, github/codeql-action@v3. All workflow jobs now run on the Node 20 runtime instead of the deprecated Node 16. pypa/gh-action-pypi-publish SHA refreshed to v1.14.0 (35).

  • PyPy CI matrix moved off the EOL pypy-3.8 line to the maintained pypy-3.10 and pypy-3.11 (37).

  • .pre-commit-config.yaml modernised: every hook pinned to an immutable tag instead of a floating branch ref, dead repo URLs corrected (gitlab.com/pycqa/flake8github.com/PyCQA/flake8, timothycrosley/isortPyCQA/isort), and psf/black swapped for the upstream-recommended psf/black-pre-commit-mirror (33).

Dependencies

  • Bumped minimum versions: setuptools >=75.0, pip >=26.0.1, virtualenv >=21.3.0, six >=1.17.0, sphinx >=7.4.7 (25, 26, 27, 28, 29).

Repository infrastructure

  • Added .github/CODEOWNERS and natsuoto to AUTHORS.rst for the new agent-driven contribution flow (39).

  • Locked main: 1 approving code-owner review required, 31 status-check contexts required (the full matrix), linear history enforced, no force pushes or deletions, applies to administrators.

  • Repo-level allow_auto_merge enabled — PRs auto-merge once review and CI gates pass.

Housekeeping

  • Dropped a dead py37/pypy37 exclusion from the cookiecutter Jinja template and refreshed the docs copyright year (43).

  • Updated author website URL in AUTHORS.rst and .cookiecutterrc (41).

  • Removed all cookiecutter regeneration scaffolding — .cookiecutterrc, ci/bootstrap.py, and ci/templates/ (containing dead AppVeyor config and a workflow template that lagged the live one) — along with the [testenv:bootstrap] env, related MANIFEST.in / setup.cfg / .pre-commit-config.yaml exclusions, and the dead Python 3.7 branch in tests.local.sh. The local-test script’s per-version if/elif chain is now a single programmatic tox -e py$VERSION lookup, which adapts automatically when Python versions are added or removed from the matrix (47).

0.2.2 (2022-12-22)

  • Added Support for Python 3.11

  • Added more RegexBuilder Examples

  • Fixed Documentation Typos

0.2.1 (2022-11-27)

  • This is a Quick Fix Release to fix the incomplete release of 0.2.0. The release was intended to drop support for 3.6, but the metadata was not updated to reflect this. This release fixes that. v0.2.0 remains available on PyPI, but is incompatible with Python 3.6. Using it with other versions of Python is not a problem. Other than the metadata, the two releases are identical.

0.2.0 (2022-11-27)

This is a minor release with a few new built-in validators along with some small changes and bug fixes.

Validators added:

  • URL Validator

  • UUID Validator

  • GUID Validator

  • SSN Validator

  • Mac Address (IEEE 802) Validator

  • Zip Code Validator

  • Password Validator

Documentation:

  • Added documentation for new validators

  • Add warning for trade-offs in email regex validation

Bug Fixes:

  • Fixed Phone pattern failing for service numbers and 4 digit numbers (See #16 for more information)

0.1.0 (2022-09-10)

  • First release on PyPI.

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

edify-0.3.0.tar.gz (609.9 kB view details)

Uploaded Source

Built Distribution

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

edify-0.3.0-py3-none-any.whl (27.1 kB view details)

Uploaded Python 3

File details

Details for the file edify-0.3.0.tar.gz.

File metadata

  • Download URL: edify-0.3.0.tar.gz
  • Upload date:
  • Size: 609.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for edify-0.3.0.tar.gz
Algorithm Hash digest
SHA256 c32641d539beca6d89017d337393c176d7ac8844c3defd4d538425ab7ea673e6
MD5 049560af2298aed2f9938f9a590ada04
BLAKE2b-256 5150f48237a43c712ef65c300e07698cc145c16b68a58a87825af750e30a3576

See more details on using hashes here.

File details

Details for the file edify-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: edify-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 27.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for edify-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7699571a63371a781dd1e4b5e063750762e2464f604294a8afed33eb2a17501e
MD5 0173a45732b144cd130dce5fbbde71c3
BLAKE2b-256 7718f2f71e984aeaa459cdad082895e80cd157ebf716b33e8aa994032b9b677e

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