{"id":26070031,"url":"https://github.com/memodb-io/drive-flow","last_synced_at":"2025-08-03T18:34:51.151Z","repository":{"id":256532593,"uuid":"855642878","full_name":"memodb-io/drive-flow","owner":"memodb-io","description":"Build event-driven workflows with python async functions","archived":false,"fork":false,"pushed_at":"2024-09-18T06:50:56.000Z","size":67,"stargazers_count":33,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-03-24T19:50:20.107Z","etag":null,"topics":["agent","asynchronous","event-driven","framework","python","workflow"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/memodb-io.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"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":"2024-09-11T08:06:50.000Z","updated_at":"2025-02-18T15:56:27.000Z","dependencies_parsed_at":"2024-09-15T14:43:37.742Z","dependency_job_id":null,"html_url":"https://github.com/memodb-io/drive-flow","commit_stats":null,"previous_names":["memodb-io/drive-events","memodb-io/drive-flow"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/memodb-io%2Fdrive-flow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/memodb-io%2Fdrive-flow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/memodb-io%2Fdrive-flow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/memodb-io%2Fdrive-flow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/memodb-io","download_url":"https://codeload.github.com/memodb-io/drive-flow/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248065287,"owners_count":21041871,"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":["agent","asynchronous","event-driven","framework","python","workflow"],"created_at":"2025-03-08T23:07:03.830Z","updated_at":"2025-04-11T18:50:50.118Z","avatar_url":"https://github.com/memodb-io.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ch1\u003edrive-flow\u003c/h1\u003e\n  \u003cp\u003e\u003cstrong\u003eBuild event-driven workflows with python async functions\u003c/strong\u003e\u003c/p\u003e\n  \u003cp\u003e\n    \u003ca href=\"https://pypi.org/project/drive-flow/\" \u003e \n    \t\u003cimg src=\"https://img.shields.io/badge/python-\u003e=3.9.11-blue\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://codecov.io/github/memodb-io/drive-flow\" \u003e \n     \u003cimg src=\"https://codecov.io/github/memodb-io/drive-flow/graph/badge.svg?token=T1Q1JB1NGM\"/\u003e \n\t \u003c/a\u003e\n    \u003ca href=\"https://pypi.org/project/drive-flow/\"\u003e\n      \u003cimg src=\"https://img.shields.io/pypi/v/drive-flow.svg\"\u003e\n    \u003c/a\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n\n\n🌬️ [Zero dependency](./requirements.txt). No trouble, no loss.\n\n🍰 With **intuitive decorators**, write your async workflow like a piece of cake. \n\n🔄 Support dynamic dispatch(`goto`, `abort`). Create a **looping or if-else workflow with ease**. \n\n🔜 **Fully asynchronous**. Events are always triggered at the same time if they listen to the same group!\n\n\n\n\n\n## Install\n\n**Install from PyPi**\n\n```shell\npip install drive-flow\n```\n\n**Install from source**\n\n```shell\n# clone this repo first\ncd drive-flow\npip install -e .\n```\n\n\n\n## Quick Start\n\nA hello world example:\n\n```python\nimport asyncio\nfrom drive_flow import EventInput, default_drive\n\n\n@default_drive.make_event\nasync def hello(event: EventInput, global_ctx):\n    print(\"hello\")\n\n@default_drive.listen_group([hello])\nasync def world(event: EventInput, global_ctx):\n    print(\"world\")\n\n# display the dependencies of 'world' event\nprint(world.debug_string()) \nasyncio.run(default_drive.invoke_event(hello))\n```\n\nIn this example, The return of `hello` event will trigger `world` event.\n\n\u003e [!TIP]\n\u003e\n\u003e Hello world is not cool enough? Try to build a [ReAct Agent Workflow](./examples/6_llm_agent_ReAct.py) with `drive-flow`\n\n### Break-down\n\nTo make an event function, there are few elements:\n\n* Input Signature: must be `(event: EventInput, global_ctx)`. `EventInput` is the returns of the listening groups. `global_ctx` is set by you when invoking events, it can be anything and default to `None`.\n\n  This [example](./examples/3_use_event_output.py) shows how to get returns from `EventInput` .\n* Make sure you decorate the function with `@default_drive.make_event` or `@default_drive.listen_group([EVENT,...])`\n\nThen, run your workflow from any event:\n\n```python\nawait default_drive.invoke_event(EVENT, EVENT_INPUT, GLOBAL_CTX)\n```\n\nCheck out [examples](./examples) for more detailed usages and features!\n\n## Features\n\n### Multi-Recv\n\n`drive_flow` allow an event to be triggered only when a group of events are produced:\n\n\u003cdetails\u003e\n\u003csummary\u003e code snippet\u003c/summary\u003e\n\n```python\n@default_drive.make_event\nasync def start(event: EventInput, global_ctx):\n    print(\"start\")\n    \n@default_drive.listen_group([start])\nasync def hello(event: EventInput, global_ctx):\n    return 1\n\n\n@default_drive.listen_group([start])\nasync def world(event: EventInput, global_ctx):\n    return 2\n\n\n@default_drive.listen_group([hello, world])\nasync def adding(event: EventInput, global_ctx):\n    results = event.results\n    print(\"adding\", hello, world)\n    return results[hello.id] + results[world.id]\n\n\nresults = asyncio.run(default_drive.invoke_event(start))\nassert results[adding.id] == 3\n```\n\n`adding` will be triggered at first time as long as `hello` and `world` are done.\n\u003c/details\u003e\n\n#### Re-trigger the event\n\n`drive_flow` suppports different behaviors for multi-event retriggering:\n\n- `all`: retrigger this event only when all the listening events are updated.\n- `any`: retrigger this event as long as one of the listening events is updated.\n\nCheck out this [example](./examples/5_retrigger_type.py) for more details\n\n### Parallel\n\n`drive_flow` is perfect for workflows that have many network IO that can be awaited in parallel. If two events are listened to the same group of events, then they will be triggered at the same time:\n\n\u003cdetails\u003e\n\u003csummary\u003e code snippet\u003c/summary\u003e\n\n```python\n@default_drive.make_event\nasync def start(event: EventInput, global_ctx):\n    print(\"start\")\n\n@default_drive.listen_group([start])\nasync def hello(event: EventInput, global_ctx):\n    print(datetime.now(), \"hello\")\n    await asyncio.sleep(0.2)\n    print(datetime.now(), \"hello done\")\n\n\n@default_drive.listen_group([start])\nasync def world(event: EventInput, global_ctx):\n    print(datetime.now(), \"world\")\n    await asyncio.sleep(0.2)\n    print(datetime.now(), \"world done\")\n\nasyncio.run(default_drive.invoke_event(start))\n```\n\n\u003c/details\u003e\n\n\n\n### Dynamic\n\n`drive_flow` is dynamic. You can use `goto` and `abort` to change the workflow at runtime:\n\n\u003cdetails\u003e\n\u003csummary\u003e code snippet for abort_this\u003c/summary\u003e\n\n```python\nfrom drive_flow.dynamic import abort_this\n\n@default_drive.make_event\nasync def a(event: EventInput, global_ctx):\n    return abort_this()\n# abort_this is not exiting the whole workflow,\n# only abort this event's return and not causing any other influence\n# `a` chooses to abort its return. So no more events in this invoking.\n# this invoking then will end\n@default_drive.listen_group([a])\nasync def b(event: EventInput, global_ctx):\n    assert False, \"should not be called\"\n    \nasyncio.run(default_drive.invoke_event(a))\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e code snippet for goto\u003c/summary\u003e\n\n```python\nfrom drive_flow.types import ReturnBehavior\nfrom drive_flow.dynamic import goto_events, abort_this\n\ncall_a_count = 0\n@default_drive.make_event\nasync def a(event: EventInput, global_ctx):\n    global call_a_count\n    if call_a_count == 0:\n        assert event is None\n    elif call_a_count == 1:\n        assert event.behavior == ReturnBehavior.GOTO\n        assert event.results == {b.id: 2}\n        return abort_this()\n    call_a_count += 1\n    return 1\n\n@default_drive.listen_group([a])\nasync def b(event: EventInput, global_ctx):\n    return goto_events([a], 2)\n\n@default_drive.listen_group([b])\nasync def c(event: EventInput, global_ctx):\n    assert False, \"should not be called\"\n    \nasyncio.run(default_drive.invoke_event(a))\n```\n\n\u003c/details\u003e\n\n\n\n## TODO\n\n- [x] fix: streaming event executation\n- [x] fix: an event never receive the listened events' results twice (de-duplication), unless the group is totally updated for `retrigger_type='all'`\n- [x] Add ReAct workflow example\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmemodb-io%2Fdrive-flow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmemodb-io%2Fdrive-flow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmemodb-io%2Fdrive-flow/lists"}