flo-python 0.1.0__tar.gz → 0.1.0.dev3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. flo_python-0.1.0.dev3/.gitignore +142 -0
  2. flo_python-0.1.0.dev3/LICENSE +21 -0
  3. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/PKG-INFO +40 -33
  4. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/README.md +38 -32
  5. flo_python-0.1.0.dev3/examples/stream_worker.py +76 -0
  6. flo_python-0.1.0.dev3/examples/test_decorator.py +44 -0
  7. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/examples/worker.py +11 -5
  8. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/pyproject.toml +3 -1
  9. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/src/flo/__init__.py +96 -14
  10. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/src/flo/actions.py +44 -15
  11. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/src/flo/client.py +171 -8
  12. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/src/flo/exceptions.py +21 -0
  13. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/src/flo/kv.py +6 -6
  14. flo_python-0.1.0.dev3/src/flo/processing.py +341 -0
  15. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/src/flo/streams.py +60 -12
  16. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/src/flo/types.py +455 -194
  17. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/src/flo/wire.py +116 -48
  18. flo_python-0.1.0.dev3/src/flo/worker.py +979 -0
  19. flo_python-0.1.0.dev3/src/flo/workflows.py +463 -0
  20. flo_python-0.1.0.dev3/tests/conftest.py +186 -0
  21. flo_python-0.1.0.dev3/tests/order-workflow.yaml +74 -0
  22. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/tests/test_wire.py +8 -6
  23. flo_python-0.1.0.dev3/tests/test_workflows.py +747 -0
  24. flo_python-0.1.0/src/flo/worker.py +0 -421
  25. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/.github/workflows/ci.yml +0 -0
  26. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/.github/workflows/release.yml +0 -0
  27. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/examples/basic.py +0 -0
  28. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/src/flo/queue.py +0 -0
  29. {flo_python-0.1.0 → flo_python-0.1.0.dev3}/tests/__init__.py +0 -0
@@ -0,0 +1,142 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Unit test / coverage reports
34
+ htmlcov/
35
+ .tox/
36
+ .nox/
37
+ .coverage
38
+ .coverage.*
39
+ .cache
40
+ nosetests.xml
41
+ coverage.xml
42
+ *.cover
43
+ *.log
44
+ .hypothesis/
45
+ .pytest_cache/
46
+ cover/
47
+
48
+ # Translations
49
+ *.mo
50
+ *.pot
51
+
52
+ # Django stuff:
53
+ local_settings.py
54
+ db.sqlite3
55
+ db.sqlite3-journal
56
+
57
+ # Flask stuff:
58
+ instance/
59
+ .webassets-cache
60
+
61
+ # Scrapy stuff:
62
+ .scrapy
63
+
64
+ # Sphinx documentation
65
+ docs/_build/
66
+
67
+ # PyBuilder
68
+ .pybuilder/
69
+ target/
70
+
71
+ # Jupyter Notebook
72
+ .ipynb_checkpoints
73
+
74
+ # IPython
75
+ profile_default/
76
+ ipython_config.py
77
+
78
+ # pyenv
79
+ .python-version
80
+
81
+ # pipenv
82
+ Pipfile.lock
83
+
84
+ # poetry
85
+ poetry.lock
86
+
87
+ # pdm
88
+ .pdm.toml
89
+
90
+ # PEP 582
91
+ __pypackages__/
92
+
93
+ # Celery stuff
94
+ celerybeat-schedule
95
+ celerybeat.pid
96
+
97
+ # SageMath parsed files
98
+ *.sage.py
99
+
100
+ # Environments
101
+ .env
102
+ .venv
103
+ env/
104
+ venv/
105
+ ENV/
106
+ env.bak/
107
+ venv.bak/
108
+
109
+ # Spyder project settings
110
+ .spyderproject
111
+ .spyproject
112
+
113
+ # Rope project settings
114
+ .ropeproject
115
+
116
+ # mkdocs documentation
117
+ /site
118
+
119
+ # mypy
120
+ .mypy_cache/
121
+ .dmypy.json
122
+ dmypy.json
123
+
124
+ # Pyre type checker
125
+ .pyre/
126
+
127
+ # pytype static type analyzer
128
+ .pytype/
129
+
130
+ # Cython debug symbols
131
+ cython_debug/
132
+
133
+ # ruff
134
+ .ruff_cache/
135
+
136
+ # IDE
137
+ .vscode/
138
+ .idea/
139
+ *.swp
140
+ *.swo
141
+ *~
142
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Flo Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flo-python
3
- Version: 0.1.0
3
+ Version: 0.1.0.dev3
4
4
  Summary: Python SDK for the Flo distributed systems platform
5
5
  Project-URL: Homepage, https://github.com/floruntime/flo-python
6
6
  Project-URL: Documentation, https://github.com/floruntime/flo-python#readme
7
7
  Project-URL: Repository, https://github.com/floruntime/flo-python
8
8
  Author: Flo Team
9
9
  License-Expression: MIT
10
+ License-File: LICENSE
10
11
  Keywords: async,distributed-systems,flo,kv-store,queue
11
12
  Classifier: Development Status :: 3 - Alpha
12
13
  Classifier: Framework :: AsyncIO
@@ -320,7 +321,7 @@ for record in result.records:
320
321
  try:
321
322
  process(record.payload)
322
323
  # Acknowledge successful processing
323
- await client.stream.group_ack("events", "processors", [record.offset])
324
+ await client.stream.group_ack("events", "processors", [record.id])
324
325
  except Exception:
325
326
  # Record will be redelivered to another consumer
326
327
  pass
@@ -452,43 +453,49 @@ await client.worker.touch(
452
453
 
453
454
  ```python
454
455
  import asyncio
455
- from flo import FloClient, WorkerAwaitOptions, WorkerFailOptions
456
+ from flo import FloClient
456
457
 
457
458
  async def run_worker():
458
- async with FloClient("localhost:9000") as client:
459
- worker_id = "worker-1"
460
- task_types = ["process-image"]
461
-
462
- # Register the worker
463
- await client.worker.register(worker_id, task_types)
464
-
465
- while True:
466
- # Wait for tasks
467
- result = await client.worker.await_task(
468
- worker_id,
469
- task_types,
470
- WorkerAwaitOptions(block_ms=30000)
471
- )
472
-
473
- if not result.task:
474
- continue
475
-
476
- task = result.task
477
- try:
478
- # Process task
479
- output = await process_image(task.input)
480
- await client.worker.complete(worker_id, task.task_id, output)
481
- except Exception as e:
482
- await client.worker.fail(
483
- worker_id,
484
- task.task_id,
485
- str(e),
486
- WorkerFailOptions(retry=True)
487
- )
459
+ async with FloClient("localhost:9000", namespace="myapp") as client:
460
+ # Create an action worker from the client
461
+ worker = client.new_action_worker(concurrency=5)
462
+
463
+ @worker.action("process-image")
464
+ async def process_image(ctx):
465
+ data = ctx.json()
466
+ result = await do_processing(data)
467
+ return ctx.to_bytes({"status": "done"})
468
+
469
+ await worker.start()
488
470
 
489
471
  asyncio.run(run_worker())
490
472
  ```
491
473
 
474
+ ### StreamWorker Example
475
+
476
+ ```python
477
+ import asyncio
478
+ from flo import FloClient, StreamContext
479
+
480
+ async def process_event(ctx: StreamContext) -> None:
481
+ event = ctx.json()
482
+ print(f"Got event: {event}")
483
+ # Return normally → auto-ack
484
+ # Raise an exception → auto-nack (redelivery)
485
+
486
+ async def run_stream_worker():
487
+ async with FloClient("localhost:9000", namespace="myapp") as client:
488
+ worker = client.new_stream_worker(
489
+ stream="events",
490
+ group="processors",
491
+ handler=process_event,
492
+ concurrency=5,
493
+ )
494
+ await worker.start()
495
+
496
+ asyncio.run(run_stream_worker())
497
+ ```
498
+
492
499
  ## Configuration
493
500
 
494
501
  ### Client Options
@@ -291,7 +291,7 @@ for record in result.records:
291
291
  try:
292
292
  process(record.payload)
293
293
  # Acknowledge successful processing
294
- await client.stream.group_ack("events", "processors", [record.offset])
294
+ await client.stream.group_ack("events", "processors", [record.id])
295
295
  except Exception:
296
296
  # Record will be redelivered to another consumer
297
297
  pass
@@ -423,43 +423,49 @@ await client.worker.touch(
423
423
 
424
424
  ```python
425
425
  import asyncio
426
- from flo import FloClient, WorkerAwaitOptions, WorkerFailOptions
426
+ from flo import FloClient
427
427
 
428
428
  async def run_worker():
429
- async with FloClient("localhost:9000") as client:
430
- worker_id = "worker-1"
431
- task_types = ["process-image"]
432
-
433
- # Register the worker
434
- await client.worker.register(worker_id, task_types)
435
-
436
- while True:
437
- # Wait for tasks
438
- result = await client.worker.await_task(
439
- worker_id,
440
- task_types,
441
- WorkerAwaitOptions(block_ms=30000)
442
- )
443
-
444
- if not result.task:
445
- continue
446
-
447
- task = result.task
448
- try:
449
- # Process task
450
- output = await process_image(task.input)
451
- await client.worker.complete(worker_id, task.task_id, output)
452
- except Exception as e:
453
- await client.worker.fail(
454
- worker_id,
455
- task.task_id,
456
- str(e),
457
- WorkerFailOptions(retry=True)
458
- )
429
+ async with FloClient("localhost:9000", namespace="myapp") as client:
430
+ # Create an action worker from the client
431
+ worker = client.new_action_worker(concurrency=5)
432
+
433
+ @worker.action("process-image")
434
+ async def process_image(ctx):
435
+ data = ctx.json()
436
+ result = await do_processing(data)
437
+ return ctx.to_bytes({"status": "done"})
438
+
439
+ await worker.start()
459
440
 
460
441
  asyncio.run(run_worker())
461
442
  ```
462
443
 
444
+ ### StreamWorker Example
445
+
446
+ ```python
447
+ import asyncio
448
+ from flo import FloClient, StreamContext
449
+
450
+ async def process_event(ctx: StreamContext) -> None:
451
+ event = ctx.json()
452
+ print(f"Got event: {event}")
453
+ # Return normally → auto-ack
454
+ # Raise an exception → auto-nack (redelivery)
455
+
456
+ async def run_stream_worker():
457
+ async with FloClient("localhost:9000", namespace="myapp") as client:
458
+ worker = client.new_stream_worker(
459
+ stream="events",
460
+ group="processors",
461
+ handler=process_event,
462
+ concurrency=5,
463
+ )
464
+ await worker.start()
465
+
466
+ asyncio.run(run_stream_worker())
467
+ ```
468
+
463
469
  ## Configuration
464
470
 
465
471
  ### Client Options
@@ -0,0 +1,76 @@
1
+ """Example: StreamWorker usage with the Flo Python SDK
2
+
3
+ Demonstrates how to use StreamWorker to process stream records
4
+ via consumer groups with automatic ack/nack handling.
5
+ """
6
+
7
+ import asyncio
8
+ import logging
9
+ import os
10
+ import signal
11
+
12
+ from flo import FloClient, StreamContext
13
+
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
17
+ )
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ async def process_event(ctx: StreamContext) -> None:
22
+ """Process a stream record.
23
+
24
+ Return normally to auto-ack. Raise an exception to auto-nack
25
+ (the record will be redelivered).
26
+ """
27
+ event = ctx.json()
28
+ logger.info(
29
+ f"Processing event (stream={ctx.stream}, id={ctx.stream_id}): {event}"
30
+ )
31
+
32
+ # Simulate work
33
+ await asyncio.sleep(0.1)
34
+
35
+ # If processing fails, just raise — the worker will nack for you:
36
+ # raise RuntimeError("transient failure")
37
+
38
+
39
+ async def main():
40
+ client = FloClient(
41
+ os.getenv("FLO_ENDPOINT", "localhost:3000"),
42
+ namespace=os.getenv("FLO_NAMESPACE", "myapp"),
43
+ debug=os.getenv("FLO_DEBUG", "").lower() in ("1", "true"),
44
+ )
45
+ await client.connect()
46
+
47
+ worker = client.new_stream_worker(
48
+ stream="events",
49
+ group="processors",
50
+ handler=process_event,
51
+ concurrency=5,
52
+ batch_size=10,
53
+ )
54
+
55
+ # Handle shutdown signals
56
+ def signal_handler():
57
+ logger.info("Received shutdown signal")
58
+ worker.stop()
59
+
60
+ loop = asyncio.get_running_loop()
61
+ for sig in (signal.SIGINT, signal.SIGTERM):
62
+ loop.add_signal_handler(sig, signal_handler)
63
+
64
+ logger.info("Starting stream worker...")
65
+ try:
66
+ await worker.start()
67
+ except KeyboardInterrupt:
68
+ logger.info("Interrupted")
69
+ finally:
70
+ await worker.close()
71
+ await client.close()
72
+ logger.info("Stream worker shutdown complete")
73
+
74
+
75
+ if __name__ == "__main__":
76
+ asyncio.run(main())
@@ -0,0 +1,44 @@
1
+ """Minimal test: register an action using only the @worker.action decorator."""
2
+ import asyncio
3
+ import logging
4
+ import signal
5
+ from flo import FloClient, ActionContext
6
+
7
+ logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
8
+
9
+
10
+ async def main():
11
+ client = FloClient("localhost:9000", namespace="default", debug=True)
12
+ await client.connect()
13
+ print(f"Connected: {client.is_connected}")
14
+
15
+ worker = client.new_action_worker(concurrency=1)
16
+
17
+ @worker.action("health-check")
18
+ async def health_check(ctx: ActionContext) -> bytes:
19
+ return ctx.to_bytes({"status": "healthy"})
20
+
21
+ stop = asyncio.Event()
22
+
23
+ def on_signal():
24
+ print("Shutting down...")
25
+ worker.stop()
26
+ stop.set()
27
+
28
+ loop = asyncio.get_running_loop()
29
+ for sig in (signal.SIGINT, signal.SIGTERM):
30
+ loop.add_signal_handler(sig, on_signal)
31
+
32
+ print("Starting worker with @worker.action('health-check')...")
33
+ try:
34
+ await worker.start()
35
+ except KeyboardInterrupt:
36
+ pass
37
+ finally:
38
+ await worker.close()
39
+ await client.close()
40
+ print("Done.")
41
+
42
+
43
+ if __name__ == "__main__":
44
+ asyncio.run(main())
@@ -9,7 +9,7 @@ import os
9
9
  import signal
10
10
  from dataclasses import dataclass
11
11
 
12
- from flo import ActionContext, Worker
12
+ from flo import ActionContext, FloClient
13
13
 
14
14
  # Configure logging
15
15
  logging.basicConfig(
@@ -158,13 +158,18 @@ async def generate_report(ctx: ActionContext) -> bytes:
158
158
 
159
159
 
160
160
  async def main():
161
- # Create worker with configuration
162
- worker = Worker(
163
- endpoint=os.getenv("FLO_ENDPOINT", "localhost:3000"),
161
+ # Create and connect client (shared configuration)
162
+ client = FloClient(
163
+ os.getenv("FLO_ENDPOINT", "localhost:3000"),
164
164
  namespace=os.getenv("FLO_NAMESPACE", "myapp"),
165
+ debug=os.getenv("FLO_DEBUG", "").lower() in ("1", "true"),
166
+ )
167
+ await client.connect()
168
+
169
+ # Create a worker from the client
170
+ worker = client.new_action_worker(
165
171
  concurrency=5,
166
172
  action_timeout=300, # 5 minutes
167
- debug=os.getenv("FLO_DEBUG", "").lower() in ("1", "true"),
168
173
  )
169
174
 
170
175
  # Register action handlers using register_action()
@@ -202,6 +207,7 @@ async def main():
202
207
  logger.info("Interrupted")
203
208
  finally:
204
209
  await worker.close()
210
+ await client.close()
205
211
  logger.info("Worker shutdown complete")
206
212
 
207
213
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "flo-python"
7
- version = "0.1.0"
7
+ version = "0.1.0.dev3"
8
8
  description = "Python SDK for the Flo distributed systems platform"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -45,6 +45,8 @@ packages = ["src/flo"]
45
45
 
46
46
  [tool.pytest.ini_options]
47
47
  asyncio_mode = "auto"
48
+ asyncio_default_fixture_loop_scope = "session"
49
+ asyncio_default_test_loop_scope = "session"
48
50
  testpaths = ["tests"]
49
51
 
50
52
  [tool.mypy]