observatory


Nameobservatory JSON
Version 2.0.0 PyPI version JSON
download
home_page
SummaryObservable, event-driven python!
upload_time2023-03-23 04:00:12
maintainer
docs_urlNone
authorEd Whetstone
requires_python>=3.11
license
keywords event observer observability observables pubsub pub-sub publish subscribe
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Observatory
Observatory is a suite of tools for event-driven and observable python, including:

- Standalone [event emitters](#EventHooks) inspired by Qt's Signals
- [Event decorators](#Events) for adding observability to otherwise-opaque functions
- [Data Types](#Observable-Types) for observable assignment and mutation
- A basic [Publish-Subscribe](#Publish/Subscribe) implementation

## EventHooks

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

```python
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:

```python
@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:

```python
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:
```python
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:

```python
@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::
```python
@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.

```python
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.

```python
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:

```python
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:

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

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

```python
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.


            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "observatory",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.11",
    "maintainer_email": "",
    "keywords": "event,observer,observability,observables,pubsub,pub-sub,publish,subscribe",
    "author": "Ed Whetstone",
    "author_email": "",
    "download_url": "https://files.pythonhosted.org/packages/16/48/ad09762d5d58331025c7538be8aaeee2bba8c820624e1d440b455ce128f0/observatory-2.0.0.tar.gz",
    "platform": null,
    "description": "# Observatory\nObservatory is a suite of tools for event-driven and observable python, including:\n\n- Standalone [event emitters](#EventHooks) inspired by Qt's Signals\n- [Event decorators](#Events) for adding observability to otherwise-opaque functions\n- [Data Types](#Observable-Types) for observable assignment and mutation\n- A basic [Publish-Subscribe](#Publish/Subscribe) implementation\n\n## EventHooks\n\nEventHooks can be connected to one or more \"observers\", callables that are\ninvoked when the EventHook is emitted (with or without arguments):\n\n```python\ndef say_hello(x):                # <-- function to call when events occur\n    print(f\"hello {x}\")\n\nan_event_hook = EventHook()     # <-- event hook as a standalone object\n\nan_event_hook.connect(print_it) # <-- add observer\n\nevent_triggered.emit(\"events\")  # <-- emit event hook\n\n# output: hello events\n```\n\nFunctions (or static methods) can also be connected to a specific event hook at\ndefinition by using the \"observes\" decorator:\n\n```python\n@observes(an_event_hook)\ndef print_it(x):\n    print(f\"hello {x}\")\n```\n\n### Type Hinting\n\nEventHooks can be annotated to indicate the arguments that are expected to be\nemitted and passed to the observer:\n\n```python\nan_event_hook: EventHook[str] = EventHook()\n```\n\nThis should give static type checkers a good clue when emitting from and\nconnecting to the event hook. Type hinting only supports positional arguments\ncurrently -- static type checking may break if you need to emit keyword\narguments directly to observers.\n\n## Events\n\nEvent objects use EventHooks to add observability to otherwise opaque methods\nand functions.  The EventHooks attached to an Event always emit an EventData\nobject with information about the Event.  See the Event class for more details.\n\nExample:\n```python\ndef start(event_data):\n    print(\"hello!\")\n\ndef stop(event_data):\n    print(\"goodbye!\")\n\n@event()\ndef function_one():\n    print(\"in some_function\")\n\nsome_function.about_to_run.connect(start)\nsome_function.completed.connect(stop)\n\nsome_function()\n# output: hello\n# output: in some_function\n# output: goodbye\n```\n\n### Adding Context to Events\n\nThere are two ways to add additional information to an event -- \"extra\" data,\nand \"tags\".\n\n\"extra\" data is a globally-defined dictionary that is shared between all calls\nto a given event:\n\n```python\n@event(extra={\"project\": \"SuperProject\"})\ndef my_event():\n    ...\n```\n\n\"tags\", on the other hand, are assigned to *one particular execution* of an\nevent.  Tags should be used when the data is likely to change each time an\nevent is called.  Tags can be assigned using dictionary-style keys on an event,\nwithin the function call.\n\nThis can be useful to get additional information into your logging, telemetry,\nor other event-based systems::\n```python\n@event()\ndef my_event(asset_name):\n    my_event[\"asset\"] = asset_name\n    ...\n```\n\n## Observable Types\n\nThe provided observable data types allow us to connect callbacks to changes to our data.\n\n### Assignment\nNot strictly a data type itself, the `ObservableAttr` object emits an event hook every time the given attribute name is assigned a new value.\n\n```python\nclass X:\n    attr = ObservableAttr(default=5)\n\nx = X()\n\n@observes(x.assigned)\ndef print_it(new_value):\n\tprint(f\"new value is {new_value}\")\n\nx.attr = 10\n# output: \"new value is 10\"\n```\n\n### Observable Lists and Dicts\nObservatory currently provides two observable data types - `ObservableList` and `ObservableDict`.  These allow us to connect observers to changes that mutate the data in-place.\n\n```python\nx = ObservableList([1, 2])\n\n@observes(x.appended)\ndef correction(value):\n    if value == 5:\n        print(\"three, sir!\")\n\nx.append(5)\n# output: \"three, sir!\"\n```\n\n## Publish/Subscribe\n\"Publish-subscribe\" is a special case of the observer\npattern, where subjects and observers are mediated by a third object.\n\nAn Event Broker is a middle-man object between events and their callbacks,\nallowing event-generating objects (publishers) and callbacks (subscribers)\nto never know about each-other's existence.  A subscriber can subscribe to\nan event broker that has no publishers, and a publisher can publish to an\nevent broker with no subscribers.\n\nIn order to make event filtering easier, event brokers can be organized into a\nhierarchy:\n\n```python\ntop_level_broker = get_event_broker(\"top\")\nchild_broker = top_level_broker.child(\"middle\")\ngrandchild_broker = child_broker.child(\"bottom\")\n```\n\nA publisher can send data to subscribers via the broker broadcast function:\n\n```python\nbroker.broadcast(\"positional arg\", keyword_arg=None)\n```\n\nA subscriber can receive data from publishers by connecting to the broker's\nbroadcast_sent event:\n\n```python\nbroker.broadcast_sent.connect(subscriber_function)\n```\n\n\nThread Safety\n-------------\n\nA primitive (hah) attempt has been made at thread-safety by using a single\nre-entrant lock for all functions that modify state in events.  Thread-safety\nis not *guaranteed*, but should be sufficient for most use cases.\n\n",
    "bugtrack_url": null,
    "license": "",
    "summary": "Observable, event-driven python!",
    "version": "2.0.0",
    "split_keywords": [
        "event",
        "observer",
        "observability",
        "observables",
        "pubsub",
        "pub-sub",
        "publish",
        "subscribe"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "3fe1007fc1fbeaed08250f137bfc7e6c6caf36a721afc2ada25dd18349d73dd2",
                "md5": "78d38309bb9e14643fcaafaad37da76e",
                "sha256": "42ff9307752db01b850a6d4d2ba950700eaaaf0787fd9173d21370486c472835"
            },
            "downloads": -1,
            "filename": "observatory-2.0.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "78d38309bb9e14643fcaafaad37da76e",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.11",
            "size": 13834,
            "upload_time": "2023-03-23T04:00:10",
            "upload_time_iso_8601": "2023-03-23T04:00:10.486249Z",
            "url": "https://files.pythonhosted.org/packages/3f/e1/007fc1fbeaed08250f137bfc7e6c6caf36a721afc2ada25dd18349d73dd2/observatory-2.0.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "1648ad09762d5d58331025c7538be8aaeee2bba8c820624e1d440b455ce128f0",
                "md5": "4fd5b8d060ffddcfaa6931cbd35ccd42",
                "sha256": "5ee3c6f409cdfc9a2ae567bc34632a0d447f7cbbd7d9da04031647281217b7bd"
            },
            "downloads": -1,
            "filename": "observatory-2.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "4fd5b8d060ffddcfaa6931cbd35ccd42",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.11",
            "size": 18786,
            "upload_time": "2023-03-23T04:00:12",
            "upload_time_iso_8601": "2023-03-23T04:00:12.532188Z",
            "url": "https://files.pythonhosted.org/packages/16/48/ad09762d5d58331025c7538be8aaeee2bba8c820624e1d440b455ce128f0/observatory-2.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-03-23 04:00:12",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "lcname": "observatory"
}
        
Elapsed time: 0.74994s