Package: observatory
ObservatoryObservatory is a suite of tools for event-driven and observable python, including:Standaloneevent emittersinspired by Qt's SignalsEvent decoratorsfor adding observability to otherwise-opaque functionsData Typesfor observable assignment and mutationA basicPublish-SubscribeimplementationEventHooksEventHooks can be connected to one or more "observers", callables that are
invoked when the EventHook is emitted (with or without arguments):defsay_hello(x):# <-- function to call when events occurprint(f"hello{x}")an_event_hook=EventHook()# <-- event hook as a standalone objectan_event_hook.connect(print_it)# <-- add observerevent_triggered.emit("events")# <-- emit event hook# output: hello eventsFunctions (or static methods) can also be connected to a specific event hook at
definition by using the "observes" decorator:@observes(an_event_hook)defprint_it(x):print(f"hello{x}")Type HintingEventHooks 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.EventsEvent 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:defstart(event_data):print("hello!")defstop(event_data):print("goodbye!")@event()deffunction_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: goodbyeAdding Context to EventsThere 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"})defmy_event():..."tags", on the other hand, are assigned toone particular executionof 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()defmy_event(asset_name):my_event["asset"]=asset_name...Observable TypesThe provided observable data types allow us to connect callbacks to changes to our data.AssignmentNot strictly a data type itself, theObservableAttrobject emits an event hook every time the given attribute name is assigned a new value.classX:attr=ObservableAttr(default=5)x=X()@observes(x.assigned)defprint_it(new_value):print(f"new value is{new_value}")x.attr=10# output: "new value is 10"Observable Lists and DictsObservatory currently provides two observable data types -ObservableListandObservableDict.  These allow us to connect observers to changes that mutate the data in-place.x=ObservableList([1,2])@observes(x.appended)defcorrection(value):ifvalue==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 SafetyA 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 notguaranteed, but should be sufficient for most use cases.