flashlite 0.1.1__tar.gz → 0.2.0__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 (68) hide show
  1. {flashlite-0.1.1 → flashlite-0.2.0}/PKG-INFO +1 -1
  2. {flashlite-0.1.1 → flashlite-0.2.0}/pyproject.toml +5 -2
  3. flashlite-0.2.0/src/flashlite/_spinner.py +91 -0
  4. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/client.py +6 -2
  5. flashlite-0.2.0/src/flashlite/conversation/multi_agent.py +744 -0
  6. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/middleware/rate_limit.py +31 -8
  7. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/observability/inspect_compat.py +18 -6
  8. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/observability/logging.py +4 -0
  9. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/types.py +1 -1
  10. {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_multi_agent.py +249 -1
  11. flashlite-0.2.0/tests/test_rate_limit_regression.py +128 -0
  12. {flashlite-0.1.1 → flashlite-0.2.0}/uv.lock +1 -1
  13. flashlite-0.1.1/src/flashlite/conversation/multi_agent.py +0 -378
  14. {flashlite-0.1.1 → flashlite-0.2.0}/.gitignore +0 -0
  15. {flashlite-0.1.1 → flashlite-0.2.0}/.python-version +0 -0
  16. {flashlite-0.1.1 → flashlite-0.2.0}/.vscode/settings.json +0 -0
  17. {flashlite-0.1.1 → flashlite-0.2.0}/DEV.md +0 -0
  18. {flashlite-0.1.1 → flashlite-0.2.0}/LICENSE.md +0 -0
  19. {flashlite-0.1.1 → flashlite-0.2.0}/QUICK_START.md +0 -0
  20. {flashlite-0.1.1 → flashlite-0.2.0}/README.md +0 -0
  21. {flashlite-0.1.1 → flashlite-0.2.0}/examples/basic_example.py +0 -0
  22. {flashlite-0.1.1 → flashlite-0.2.0}/examples/multi_agent_chat.py +0 -0
  23. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/__init__.py +0 -0
  24. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/cache/__init__.py +0 -0
  25. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/cache/base.py +0 -0
  26. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/cache/disk.py +0 -0
  27. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/cache/memory.py +0 -0
  28. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/config.py +0 -0
  29. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/conversation/__init__.py +0 -0
  30. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/conversation/context.py +0 -0
  31. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/conversation/manager.py +0 -0
  32. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/core/__init__.py +0 -0
  33. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/core/completion.py +0 -0
  34. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/core/messages.py +0 -0
  35. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/middleware/__init__.py +0 -0
  36. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/middleware/base.py +0 -0
  37. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/middleware/cache.py +0 -0
  38. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/middleware/logging.py +0 -0
  39. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/middleware/retry.py +0 -0
  40. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/observability/__init__.py +0 -0
  41. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/observability/callbacks.py +0 -0
  42. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/observability/metrics.py +0 -0
  43. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/py.typed +0 -0
  44. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/structured/__init__.py +0 -0
  45. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/structured/outputs.py +0 -0
  46. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/structured/schema.py +0 -0
  47. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/templating/__init__.py +0 -0
  48. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/templating/engine.py +0 -0
  49. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/templating/filters.py +0 -0
  50. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/templating/registry.py +0 -0
  51. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/tools/__init__.py +0 -0
  52. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/tools/definitions.py +0 -0
  53. {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/tools/execution.py +0 -0
  54. {flashlite-0.1.1 → flashlite-0.2.0}/tests/__init__.py +0 -0
  55. {flashlite-0.1.1 → flashlite-0.2.0}/tests/conftest.py +0 -0
  56. {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_cache.py +0 -0
  57. {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_cache_integration.py +0 -0
  58. {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_client.py +0 -0
  59. {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_config.py +0 -0
  60. {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_conversation.py +0 -0
  61. {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_integration.py +0 -0
  62. {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_messages.py +0 -0
  63. {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_middleware.py +0 -0
  64. {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_observability.py +0 -0
  65. {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_structured.py +0 -0
  66. {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_templating.py +0 -0
  67. {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_tools.py +0 -0
  68. {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flashlite
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Batteries-included wrapper for litellm with rate limiting, retries, templating, and more
5
5
  Author-email: ndalton12 <niall.dalton12@gmail.com>
6
6
  License-File: LICENSE.md
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "flashlite"
3
- version = "0.1.1"
3
+ version = "0.2.0"
4
4
  description = "Batteries-included wrapper for litellm with rate limiting, retries, templating, and more"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -35,7 +35,10 @@ build-backend = "hatchling.build"
35
35
  asyncio_mode = "auto"
36
36
  asyncio_default_fixture_loop_scope = "function"
37
37
  testpaths = ["tests"]
38
- addopts = "-v --tb=short"
38
+ addopts = "-v --tb=short -m 'not slow'"
39
+ markers = [
40
+ "slow: marks tests that may require real waiting (deselected by default, run with -m slow)",
41
+ ]
39
42
 
40
43
  [tool.mypy]
41
44
  python_version = "3.13"
@@ -0,0 +1,91 @@
1
+ """Terminal spinner for user-visible progress during async waits.
2
+
3
+ Provides a lightweight, non-blocking spinner that renders to stderr
4
+ when — and only when — the output is an interactive terminal. Multiple
5
+ concurrent ``Spinner`` instances (e.g. from ``complete_many``) are
6
+ gracefully collapsed so only one animation is visible at a time.
7
+ """
8
+
9
+ import asyncio
10
+ import sys
11
+ import time
12
+
13
+ # Braille-dot frames — smooth and compact.
14
+ _FRAMES = ("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏")
15
+ _INTERVAL = 0.08 # seconds between frame updates
16
+
17
+ # ANSI helpers
18
+ _CYAN = "\033[36m"
19
+ _DIM = "\033[2m"
20
+ _RESET = "\033[0m"
21
+ _CLEAR_LINE = "\r\033[K"
22
+
23
+ # Module-level guard — only one spinner renders at a time.
24
+ _active: bool = False
25
+
26
+
27
+ class Spinner:
28
+ """Async context manager that shows a terminal spinner on stderr.
29
+
30
+ The spinner only appears when stderr is a TTY **and** no other
31
+ ``Spinner`` is already active, making it safe for concurrent use
32
+ inside ``complete_many``.
33
+
34
+ Args:
35
+ message: Text displayed next to the spinner.
36
+ delay: Grace period (seconds) before the spinner appears.
37
+ If the wrapped operation finishes within this window the
38
+ spinner is never rendered, avoiding flicker for fast calls.
39
+ """
40
+
41
+ def __init__(self, message: str = "Working...", *, delay: float = 0.3) -> None:
42
+ self.message = message
43
+ self.delay = delay
44
+ self._task: asyncio.Task[None] | None = None
45
+ self._owns_active = False
46
+ self._start: float = 0.0
47
+
48
+ # -- internal -----------------------------------------------------
49
+
50
+ async def _render(self) -> None:
51
+ """Background coroutine that draws frames until cancelled."""
52
+ await asyncio.sleep(self.delay)
53
+ idx = 0
54
+ while True:
55
+ elapsed = time.monotonic() - self._start
56
+ frame = _FRAMES[idx % len(_FRAMES)]
57
+ sys.stderr.write(
58
+ f"{_CLEAR_LINE}{_CYAN}{frame}{_RESET} {self.message} "
59
+ f"{_DIM}({elapsed:.1f}s){_RESET}"
60
+ )
61
+ sys.stderr.flush()
62
+ idx += 1
63
+ await asyncio.sleep(_INTERVAL)
64
+
65
+ @staticmethod
66
+ def _clear() -> None:
67
+ sys.stderr.write(_CLEAR_LINE)
68
+ sys.stderr.flush()
69
+
70
+ # -- context manager ----------------------------------------------
71
+
72
+ async def __aenter__(self) -> "Spinner":
73
+ global _active # noqa: PLW0603
74
+ if sys.stderr.isatty() and not _active:
75
+ _active = True
76
+ self._owns_active = True
77
+ self._start = time.monotonic()
78
+ self._task = asyncio.create_task(self._render())
79
+ return self
80
+
81
+ async def __aexit__(self, *_: object) -> None:
82
+ global _active # noqa: PLW0603
83
+ if self._task is not None:
84
+ self._task.cancel()
85
+ try:
86
+ await self._task
87
+ except asyncio.CancelledError:
88
+ pass
89
+ self._clear()
90
+ if self._owns_active:
91
+ _active = False
@@ -7,6 +7,7 @@ from typing import Any, TypeVar, overload
7
7
 
8
8
  from pydantic import BaseModel
9
9
 
10
+ from ._spinner import Spinner
10
11
  from .cache import CacheBackend, MemoryCache
11
12
  from .config import FlashliteConfig, load_env_files
12
13
  from .conversation import ContextManager, Conversation
@@ -223,7 +224,8 @@ class Flashlite:
223
224
  if self._config.log_requests:
224
225
  logger.info(f"Completion request: model={request.model}")
225
226
 
226
- response = await core_complete(request)
227
+ async with Spinner(f"Waiting for {request.model}...", delay=0.2):
228
+ response = await core_complete(request)
227
229
 
228
230
  if self._config.log_requests:
229
231
  logger.info(
@@ -395,10 +397,12 @@ class Flashlite:
395
397
  else:
396
398
  extra_kwargs["tools"] = tools_to_openai(tools)
397
399
 
398
- # Build request
400
+ # Build request (template/variables stored for middleware traceability)
399
401
  request = CompletionRequest(
400
402
  model=resolved_model,
401
403
  messages=final_messages,
404
+ template=template,
405
+ variables=variables,
402
406
  temperature=temperature,
403
407
  max_tokens=max_tokens,
404
408
  max_completion_tokens=max_completion_tokens,