Skip to main content

Observatory

Observatory is a suite of tools for event-driven and observable python, including:

EventHooks

EventHooks can be connected to one or more "observers", callables that are invoked when the EventHook is emitted (with or without arguments):

def say_hello(x):                # <-- function to call when events occur
    print(f"hello {x}")

an_event_hook = EventHook()     # <-- event hook as a standalone object

an_event_hook.connect(print_it) # <-- add observer

event_triggered.emit("events")  # <-- emit event hook

# output: hello events

Functions (or static methods) can also be connected to a specific event hook at definition by using the "observes" decorator:

@observes(an_event_hook)
def print_it(x):
    print(f"hello {x}")

Type Hinting

EventHooks can be annotated to indicate the arguments that are expected to be emitted and passed to the observer:

an_event_hook: EventHook[str] = EventHook()

This should give static type checkers a good clue when emitting from and connecting to the event hook. Type hinting only supports positional arguments currently -- static type checking may break if you need to emit keyword arguments directly to observers.

Events

Event objects use EventHooks to add observability to otherwise opaque methods and functions. The EventHooks attached to an Event always emit an EventData object with information about the Event. See the Event class for more details.

Example:

def start(event_data):
    print("hello!")

def stop(event_data):
    print("goodbye!")

@event()
def function_one():
    print("in some_function")

some_function.about_to_run.connect(start)
some_function.completed.connect(stop)

some_function()
# output: hello
# output: in some_function
# output: goodbye

Adding Context to Events

There are two ways to add additional information to an event -- "extra" data, and "tags".

"extra" data is a globally-defined dictionary that is shared between all calls to a given event:

@event(extra={"project": "SuperProject"})
def my_event():
    ...

"tags", on the other hand, are assigned to one particular execution of an event. Tags should be used when the data is likely to change each time an event is called. Tags can be assigned using dictionary-style keys on an event, within the function call.

This can be useful to get additional information into your logging, telemetry, or other event-based systems::

@event()
def my_event(asset_name):
    my_event["asset"] = asset_name
    ...

Observable Types

The provided observable data types allow us to connect callbacks to changes to our data.

Assignment

Not strictly a data type itself, the ObservableAttr object emits an event hook every time the given attribute name is assigned a new value.

class X:
    attr = ObservableAttr(default=5)

x = X()

@observes(x.assigned)
def print_it(new_value):
	print(f"new value is {new_value}")

x.attr = 10
# output: "new value is 10"

Observable Lists and Dicts

Observatory currently provides two observable data types - ObservableList and ObservableDict. These allow us to connect observers to changes that mutate the data in-place.

x = ObservableList([1, 2])

@observes(x.appended)
def correction(value):
    if value == 5:
        print("three, sir!")

x.append(5)
# output: "three, sir!"

Publish/Subscribe

"Publish-subscribe" is a special case of the observer pattern, where subjects and observers are mediated by a third object.

An Event Broker is a middle-man object between events and their callbacks, allowing event-generating objects (publishers) and callbacks (subscribers) to never know about each-other's existence. A subscriber can subscribe to an event broker that has no publishers, and a publisher can publish to an event broker with no subscribers.

In order to make event filtering easier, event brokers can be organized into a hierarchy:

top_level_broker = get_event_broker("top")
child_broker = top_level_broker.child("middle")
grandchild_broker = child_broker.child("bottom")

A publisher can send data to subscribers via the broker broadcast function:

broker.broadcast("positional arg", keyword_arg=None)

A subscriber can receive data from publishers by connecting to the broker's broadcast_sent event:

broker.broadcast_sent.connect(subscriber_function)

Thread Safety

A primitive (hah) attempt has been made at thread-safety by using a single re-entrant lock for all functions that modify state in events. Thread-safety is not guaranteed, but should be sufficient for most use cases.

Metadata

Release files for observatory 2.0.0

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

Source distribution (sdist)

Source distribution for observatory 2.0.0
File Size Uploaded Provenance Actions
observatory-2.0.0.tar.gz 18.8 kB Without provenance Details for observatory-2.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for observatory 2.0.0
File Interpreter ABI Platform Actions
observatory-2.0.0-py3-none-any.whl Python 3 none any Details for observatory-2.0.0-py3-none-any.whl

Total release size: 32.6 kB

Release files / observatory-2.0.0.tar.gz

Download URL observatory-2.0.0.tar.gz
Size 18.8 kB
Tags Source
SHA-256 checksum
How to use checksums
5ee3c6f409cdfc9a2ae567bc34632a0d447f7cbbd7d9da04031647281217b7bd
BLAKE2b-256 checksum
How to use checksums
1648ad09762d5d58331025c7538be8aaeee2bba8c820624e1d440b455ce128f0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via pdm/2.4.9 CPython/3.9.13

Release files / observatory-2.0.0-py3-none-any.whl

Download URL observatory-2.0.0-py3-none-any.whl
Size 13.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
42ff9307752db01b850a6d4d2ba950700eaaaf0787fd9173d21370486c472835
BLAKE2b-256 checksum
How to use checksums
3fe1007fc1fbeaed08250f137bfc7e6c6caf36a721afc2ada25dd18349d73dd2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via pdm/2.4.9 CPython/3.9.13

Security for observatory 2.0.0

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.

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