a framework to help you stay on top of what data is flowing through your code.
Project description
:moyai: pyalert
pyalert
is a lightweight framework that empowers you to stay on top of what data is flowing through your Python code.
pyalert
is designed to:
- mimic the feeling of writing unit tests
- unobtrusively add runtime alerting to your Python code
:mag: Table of Contents
- :gear: How Does It Work?
- :memo: In Summary
- :brain: Further Thinking
- :construction: Roadmap
- :handshake: Contribution
:gear: How Does It Work?
Suppose you are building software that involves processing data from a bike race...
class Biker:
def __init__(self, name: str, age: int, avg_mph: int):
self.name = name
self.age = age
self.avg_mph = avg_mph
def bike_race(bikers: List[Biker]) -> str:
"""A bike race that takes three Bikers and returns a winner announcement"""
winner = max(bikers, key=lambda biker: biker.avg_mph)
return f"{winner.name} wins with an avg mph of {winner.avg_mph}!"
Instead of cluttering your code with complex catching logic and log
/print
statements to indicate when certain occurances arise, pyalert
facilitates an intuitive segregation of such concerns with a unit-test-like design pattern...
As is often done with unit tests, let's define a class that contains our alerts...
Note how we use the @alert_conf
decorator to configure the runtime behavior of each alert method...
from pyalert import PyAlerts, alert_conf
# must inherit from PyAlerts
class BikeRaceAlerts(PyAlerts):
"""Alerts for the bike_race function"""
# takes="input" configures the alert method to take the input args as its args
@alert_conf(takes="input", raise_error=False)
def suspiciously_fast_bikers(self, *args, **kwargs):
bikers = args[0] if args else kwargs["bikers"]
msg = "Suspicious speeds--implement compulsory drug testing after the race!"
assert not any(b.avg_mph > 30), msg
# raise_error=True will cause an error to be raised if the assertion fails
@alert_conf(takes="input", raise_error=True)
def impossibly_fast_bikers(self, *args, **kwargs):
bikers = args[0] if args else kwargs["bikers"]
msg = "Stop the race! we need to recalibrate our speedometers!"
assert not any(b.avg_mph > 100 for b in bikers), msg
# takes="output" configures the alert method to take the return value as its arg
@alert_conf(takes="output", raise_error=False)
def verbose_winner_announcement(self, return_value):
msg = "The winner announcement came out unusually verbose!"
assert len(return_value) < 120, msg
Lastly, we'll decorate our original bike_race
function with the @pyalert
decorator and pass BikeRaceAlerts
as the argument:
from pyalert import pyalert
@pyalert(pyalerts=BikeRaceAlerts)
def bike_race(bikers: List[Biker]) -> str:
...
And voila, pyalerts
will take care of our alerts at runtime!
:memo: In Summary
pyalert
has 3 fundamental abstractions...
flowchart TD
pkg("📦 pyalert")
pa("1️⃣\n@pyalert\n(decorator)")
PA("2️⃣\nPyAlerts\n(base class)")
ac("3️⃣\n@alert_conf\n(decorator)")
pkg --- pa
pkg --- PA
pkg --- ac
They are used as follows:
flowchart LR
pa("1️⃣\n@pyalert\n(decorator)\n📦")
yPA("PyAlerts\nchild\nclass\n👤")
PA("2️⃣\nPyAlerts\n(base class)\n📦")
am("alert\nmethods\n👤")
yf("your\nfunction\n👤")
as("assert\nstatements\n🐍")
ac("3️⃣\n@alert_conf\n(decorator)\n📦")
pa -. takes a --> yPA
pa -. and\ndecorates --> yf
yPA -. and which\ncontains --> am
yPA -. which\ninherits\nfrom --> PA
am -. that use --> as
am -. and are\nconfigured\nby --> ac
:brain: Further Thinking
====== :thought_balloon: :one: ======
pyalerts
is designed to enhance your project by:
- :desktop_computer: - helping drive development with quicker insights
- :eye: - helping with the monitoring of data phenomena in production
...all while introducing minimal boilerplate to your source code.
====== :thought_balloon: :two: ======
pyalerts
can be used minimally as a mere seperation-of-tasks practice within a single file...
or
...since it feels so much like unit testing, why not create an alerts
folder that mirrors your src
code folder structure?
📦project
┣ 📂src
┣ 📂tests
┣ 📂alerts
┣ 📜README.md
┗ 📜requirements.txt
====== :thought_balloon: :three: ======
Are you inheriting a codebase that is already jam-packed with verbosity and complexity?
Since the only boilerplate required to use pyalerts
is a simple @pyalert
decorator, you can easily add it to your codebase without worrying about the repurcussions.
:construction: Roadmap
- :exclamation: Conceive/implement integration strategy with logging
- :exclamation: CI pipeline
- :exclamation: Docs
:handshake: Contribution
If you think there is open-source merit to this idea, or have any thoughts on how its proposed utility might be nullified by other existing tools, please let me know!
Here is a single-question form where you can voice your enthusiasm about the idea of pyalert being developed/maintained on a scale of 1-10.
If you would like contribute and collaborate, please reach out to me on LinkedIn!
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.