{"id":27148056,"url":"https://github.com/rotationalio/pyensign","last_synced_at":"2025-06-19T05:39:14.921Z","repository":{"id":145162390,"uuid":"607399644","full_name":"rotationalio/pyensign","owner":"rotationalio","description":"Ensign driver, SDK, and helpers for Python","archived":false,"fork":false,"pushed_at":"2024-06-14T17:21:40.000Z","size":320,"stargazers_count":14,"open_issues_count":8,"forks_count":3,"subscribers_count":5,"default_branch":"develop","last_synced_at":"2025-06-15T14:22:20.373Z","etag":null,"topics":["data-science","event-driven","event-driven-architecture","eventing","hacktoberfest","microservices"],"latest_commit_sha":null,"homepage":"https://ensign.rotational.dev/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rotationalio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-27T22:33:00.000Z","updated_at":"2025-01-17T12:10:43.000Z","dependencies_parsed_at":"2023-10-14T20:03:37.869Z","dependency_job_id":"fa6e2c9b-3206-4191-8250-5a29e08be3d3","html_url":"https://github.com/rotationalio/pyensign","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/rotationalio/pyensign","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rotationalio%2Fpyensign","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rotationalio%2Fpyensign/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rotationalio%2Fpyensign/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rotationalio%2Fpyensign/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rotationalio","download_url":"https://codeload.github.com/rotationalio/pyensign/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rotationalio%2Fpyensign/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260696050,"owners_count":23048186,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-science","event-driven","event-driven-architecture","eventing","hacktoberfest","microservices"],"created_at":"2025-04-08T11:52:19.878Z","updated_at":"2025-06-19T05:39:09.906Z","avatar_url":"https://github.com/rotationalio.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pyensign\nWelcome to pyensign!\n\nThis repository contains the Ensign driver, SDK, and helpers for Python. For the main ensign repo, go [here](https://github.com/rotationalio/ensign). We also have SDKs for [Javascript](https://github.com/rotationalio/ensignjs) and [Go](https://github.com/rotationalio/goensign).\n\n## Installation\n\nPyEnsign is compatible with Python \u003e= 3.7 (Note: we can't guarantee PyEnsign's compatibility with earlier versions of Python due to PyEnsign's dependence on the [`grpcio` package](https://pypi.org/project/grpcio/)). The simplest way to install PyEnsign and its dependencies is from PyPI with pip, Python's preferred package installer.\n\n```\npip install pyensign\n```\n\n## Configuration\n\nThe `Ensign` client provides access to the unified API for managing topics and publishing/subscribing to topics. Creating a client requires a client ID and client secret (your API key).\n\n```python\nfrom pyensign.ensign import Ensign\n\nclient = Ensign(client_id=\u003cyour client ID\u003e, client_secret=\u003cyour client secret\u003e)\n```\n\nIf not provided the client ID and client secret will be obtained from the `ENSIGN_CLIENT_ID` and `ENSIGN_CLIENT_SECRET` environment variables.\n\n## Getting to know the PyEnsign API\n\nThe sample code below describes some of the core PyEnsign API, but if you're looking for a minimal end-to-example, [check this out first](https://github.com/rotationalio/ensign-examples/tree/main/python/minimal).\n\n### Publishing\n\nUse `Ensign.publish()` to publish events to a topic. All events must contain some data (the event payload) in binary format and a mimetype. The mimetype helps subscribers consuming the event determine how to decode the payload.\n\n```python\nfrom pyensign.events import Event\n\n# Publishing a single event\nevent = Event(b'{\"temp\": 72, \"units\": \"fahrenheit\"}', \"application/json\")\nawait client.publish(\"weather\", event)\n\n# Publishing multiple events\nevents = [\n    Event(b'{\"temp\": 72, \"units\": \"fahrenheit\"}', \"application/json\"),\n    Event(b'{\"temp\": 76, \"units\": \"fahrenheit\"}', \"application/json\")\n]\nawait client.publish(\"weather\", events)\n```\n\nThis will raise an exception if the topic doesn't exist. If you aren't sure that a topic exists, you can use `Ensign.ensure_topic_exists()` to create the topic if it doesn't exist.\n\n```python\nawait client.ensure_topic_exists(\"weather\")\n```\n\nHow do you know if an event was actually published? `Ensign.publish` allows callbacks to be specified when the client receives acks and nacks from the server. The first argument in the callback is the `Ack` or `Nack`. An `Ack` contains the timestamp when the event was committed. A `Nack` is returned if the event couldn't be committed and contains the ID of the event along with an error describing what went wrong.\n\n\n```python\nasync def handle_ack(self, ack):\n    ts = datetime.fromtimestamp(ack.committed.seconds + ack.committed.nanos / 1e9)\n    print(f\"Event committed at {ts}\")\n\nasync def handle_nack(self, nack):\n    print(f\"Could not commit event {nack.id} with error {nack.code}: {nack.error}\")\n\nawait client.publish(\"weather\", event, on_ack=handle_ack, on_nack=handle_nack)\n```\n\n### Subscribing\n\nUse `Ensign.subscribe()` to subscribe to one or more topics.\n\n```python\nasync for event in client.subscribe(\"weather\", \"forecast\"):\n    print(event)\n    await event.ack()\n```\n\n```\nEvent:\n\tid: b'\\x01\\x89\\xd2\\x1a?,A\\x03\\xf2\\x04\\xa6yd\\xdf\\x0b\u003c'\n\tdata: b'{\"temp\": \"72\", \"units\": \"fahrenheit\"}'\n\tmimetype: application/json\n\tschema: WeatherUpdate v1.0.0\n\tstate: EventState.SUBSCRIBED\n\tcreated: 2023-08-07 17:24:41\n\tcommitted: 2023-08-07 17:24:42.930920\n```\n\nThe `Event` object contains coroutines for acking and nacking an event back to the Ensign service. Subscribers should normally invoke `Event.ack()` once the event has been successfully consumed, or `Event.nack()` if the event needs to be redelivered.\n\n## Decorators\n\nPyEnsign has decorators to convert your existing async functions into `publishers` and `subscribers`. For example, if you have a common function that you use to retrieve weather data, you could mark it with `@publisher` to automatically publish the returned object to Ensign.\n\n```python\nfrom pyensign.ensign import authenticate, publisher\n\n@authenticate()\n@publisher(\"weather\")\nasync def current_weather():\n    return {\n        \"temp\": 72,\n        \"units\": \"fahrenheit\"\n    }\n\nawait current_weather()\n```\n\nThis is equivalent to:\n\n```python\nfrom pyensign.ensign import Ensign\n\nclient = Ensign()\nevent = Event(b'{\"temp\": 72, \"units\": \"fahrenheit\"}', \"application/json\")\nawait client.publish(\"weather\", event)\n```\n\nYou can also specify an alternative mimetype for the byte encoding. For example, pickle is a common serialization format that's an alternative to JSON.\n\n```python\n@publish(\"weather\", mimetype=\"application/python-pickle\")\n```\n\nSimilarly you can use `@subscriber` to mark a subscriber which processes the weather data directly from the topic, e.g. to serve up weather updates in real time.\n\n```python\nimport json\nfrom pyensign.ensign import \n\n@authenticate()\n@subscriber(\"weather\")\nasync def process_weather(events):\n    for event in events:\n        update = json.loads(event.data)\n        print(update)\n        await event.ack()\n\nawait process_weather()\n```\n\nThis is equivalent to:\n\n```python\nfrom pyensign.ensign import Ensign\n\nclient = Ensign()\nasync for event in client.subscribe():\n    update = json.loads(event.data)\n    print(update)\n    await event.ack()\n```\n\n`@authenticate` should be specified at least once, usually on your `main` function or at the entry point of your application. By default it uses credentials from your environment, but you can also specify them directly or load them from a JSON file.\n\n```python\n@authenticate(client_id=\"my-client-id\", client_secret=\"my-client_secret\")\n\n@authenticate(cred_path=\"my-project-credentials.json\")\n```\n\n### Design patterns\n\nMost event-driven applications require some form of concurrency. Therefore, the `Ensign` class is designed to be used asynchronously by defining coroutines. You can use Python's builtin `asyncio` package to schedule and run coroutines from the main thread.\n\n```python\nimport asyncio\nfrom pyensign.ensign import Ensign\n\nasync def subscriber(topic):\n    ...\n\n    async for event in client.subscribe(topic):\n        # Handle the event\n\ndef main():\n    asyncio.run(subscriber(topic))\n```\n\nIf you aren't comfortable with `asyncio` or need a more object-oriented interface, you can use the `Publisher` and `Subscriber` classes to implement your own publisher and subscriber apps.\n\n```python\nimport time\nfrom pyensign.events import Event\nfrom pyensign.publisher import Publisher\n\nclass MyPublisher(Publisher):\n    def source_events(self):\n        while True:\n            # Call an API and yield some events!\n            data = self.fetch_data()\n            yield Event(data=data, mimetype=\"application/json\")\n            time.sleep(60)\n\n    def run_forever(self):\n        self.run(self.source_events())\n\npublisher = MyPublisher(\"my-topic\")\npublisher.run_forever()\n```\n\n```python\nfrom pyensign.subscriber import Subscriber\n\nclass MySubscriber(Subscriber):\n    async def on_event(self, event):\n        # Process the event\n        ...\n\n        # Ack the event back to Ensign\n        event.ack()\n\nsubscriber = MySubscriber(\"my-topic\")\nsubscriber.run()\n```\n\n\n## Contributing to PyEnsign\n\nWow, you want to contribute to PyEnsign? 😍 We would absolutely love that!\n\nPyEnsign is an open source project that is supported by a community who will gratefully and humbly accept any contributions you might make to the project. Large or small, any contribution makes a big difference; and if you've never contributed to an open source project before, we hope you will start with PyEnsign!\n\nPlease check out our Contributor's Guide in `CONTRIBUTING.md` to get a quick orientation first.\n\nWe can't wait to hear from you!","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frotationalio%2Fpyensign","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frotationalio%2Fpyensign","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frotationalio%2Fpyensign/lists"}