log-foundry 0.10.2.dev127__tar.gz → 0.10.2.dev128__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 (59) hide show
  1. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/PKG-INFO +6 -3
  2. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/README.md +5 -2
  3. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/pyproject.toml +1 -1
  4. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/decorator.py +76 -4
  5. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/LICENSE +0 -0
  6. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/__init__.py +0 -0
  7. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/_diag.py +0 -0
  8. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/_fork.py +0 -0
  9. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/_lifecycle.py +0 -0
  10. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/api.py +0 -0
  11. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/config.py +0 -0
  12. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/console.py +0 -0
  13. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/context.py +0 -0
  14. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/ids.py +0 -0
  15. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/model.py +0 -0
  16. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/py.typed +0 -0
  17. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/results.py +0 -0
  18. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sanitize.py +0 -0
  19. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/__init__.py +0 -0
  20. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/_batch.py +0 -0
  21. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/_chunk.py +0 -0
  22. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/_retry.py +0 -0
  23. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/_socket.py +0 -0
  24. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/_time.py +0 -0
  25. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/base.py +0 -0
  26. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/callback.py +0 -0
  27. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/clickhouse.py +0 -0
  28. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/datadog.py +0 -0
  29. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/elasticsearch.py +0 -0
  30. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/eventhubs.py +0 -0
  31. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/file.py +0 -0
  32. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/filtering.py +0 -0
  33. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/firehose.py +0 -0
  34. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/honeycomb.py +0 -0
  35. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/http.py +0 -0
  36. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/kafka.py +0 -0
  37. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/kinesis.py +0 -0
  38. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/logging_sink.py +0 -0
  39. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/logstash.py +0 -0
  40. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/loki.py +0 -0
  41. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/memory.py +0 -0
  42. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/mongodb.py +0 -0
  43. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/multi.py +0 -0
  44. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/nats.py +0 -0
  45. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/newrelic.py +0 -0
  46. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/null.py +0 -0
  47. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/postgres.py +0 -0
  48. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/pubsub.py +0 -0
  49. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/rabbitmq.py +0 -0
  50. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/redis.py +0 -0
  51. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/sentry.py +0 -0
  52. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/sns.py +0 -0
  53. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/splunk.py +0 -0
  54. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/sqlite.py +0 -0
  55. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/sqs.py +0 -0
  56. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/stdout.py +0 -0
  57. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/syslog.py +0 -0
  58. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/sinks/transform.py +0 -0
  59. {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev128}/src/log_foundry/worker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: log-foundry
3
- Version: 0.10.2.dev127
3
+ Version: 0.10.2.dev128
4
4
  Summary: Generate logs for your console and JSON events for downstream consumption.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -347,7 +347,7 @@ lands can arrive at a sink whose close has already run, and nothing owes that si
347
347
 
348
348
  ### `@trace`
349
349
 
350
- Decorate any **synchronous** function. Usable bare or with arguments:
350
+ Decorate a function, sync or async. Usable bare or with arguments:
351
351
 
352
352
  ```python
353
353
  @lf.trace # span name = func.__qualname__
@@ -363,7 +363,10 @@ def process(): ...
363
363
 
364
364
  `@trace` refuses at decoration what it cannot trace, with a `TypeError` naming the function:
365
365
  a `@classmethod` or `@staticmethod` written *below* it (put them above), a bare string
366
- (`@trace("checkout")` meant `@trace(name="checkout")`), and anything not callable.
366
+ (`@trace("checkout")` meant `@trace(name="checkout")`), anything not callable, and a
367
+ **generator function** (sync or async, including one behind `@contextmanager`, `@lru_cache` or
368
+ any `functools.wraps` wrapper) — its body runs after the wrapper has returned, so the span would
369
+ close before it started. Trace the consumer instead: the function that iterates it.
367
370
 
368
371
  The **outermost** decorated call starts a new trace; every nested decorated call becomes a
369
372
  child span within it. On an exception, the decorator records `status="error"` plus an `error`
@@ -296,7 +296,7 @@ lands can arrive at a sink whose close has already run, and nothing owes that si
296
296
 
297
297
  ### `@trace`
298
298
 
299
- Decorate any **synchronous** function. Usable bare or with arguments:
299
+ Decorate a function, sync or async. Usable bare or with arguments:
300
300
 
301
301
  ```python
302
302
  @lf.trace # span name = func.__qualname__
@@ -312,7 +312,10 @@ def process(): ...
312
312
 
313
313
  `@trace` refuses at decoration what it cannot trace, with a `TypeError` naming the function:
314
314
  a `@classmethod` or `@staticmethod` written *below* it (put them above), a bare string
315
- (`@trace("checkout")` meant `@trace(name="checkout")`), and anything not callable.
315
+ (`@trace("checkout")` meant `@trace(name="checkout")`), anything not callable, and a
316
+ **generator function** (sync or async, including one behind `@contextmanager`, `@lru_cache` or
317
+ any `functools.wraps` wrapper) — its body runs after the wrapper has returned, so the span would
318
+ close before it started. Trace the consumer instead: the function that iterates it.
316
319
 
317
320
  The **outermost** decorated call starts a new trace; every nested decorated call becomes a
318
321
  child span within it. On an exception, the decorator records `status="error"` plus an `error`
@@ -74,7 +74,7 @@ keywords = [
74
74
  # vulnerability-reporting channel. The repository is still named `log-forge` — the ORIGINAL name,
75
75
  # which PyPI rejected for the distribution — so these URLs deliberately do not match the package
76
76
  # name. See the note on `name` above before "correcting" them.
77
- version = "0.10.2.dev127"
77
+ version = "0.10.2.dev128"
78
78
 
79
79
  [project.urls]
80
80
  Homepage = "https://github.com/agriffi10/log-forge"
@@ -639,6 +639,21 @@ def _refuse_unusable(fn: object) -> None:
639
639
  ``self`` to a function declared without one. A ``str`` is the slip ``@trace("checkout")``,
640
640
  which meant ``name=``. Anything else that is not callable is refused by type.
641
641
 
642
+ A generator function or an async generator function is refused too (SPEC-055 FR-003): its
643
+ body runs after the wrapper has returned the generator object, so the span would open and
644
+ close before a single line of it ran, and every event the body logged would be an orphan on
645
+ a fresh trace — measured, ``['span.start', 'span.end']`` before the first ``next()``. The
646
+ check is the code flags, on ``fn``, on whatever ``fn`` advertises through ``__wrapped__``
647
+ — so ``@trace`` above ``@contextmanager``, ``@lru_cache`` or any ``functools.wraps`` wrapper
648
+ of a generator is refused too, each measured recording the same orphaned ``inside`` event —
649
+ and on the type's ``__call__`` for a callable instance; all of them see through a
650
+ ``functools.partial``. A plain function that merely *returns* a generator object without
651
+ advertising it is indistinguishable here and is not detected, which is a stated limit
652
+ rather than a gap to close at call time, where invariant 13 does not refuse. Wrapping the
653
+ iteration instead — a span per generator, pushed around every resumption — was considered
654
+ and deferred: a refusal can be lifted into a wrap later without breaking anyone, while a
655
+ wrap shipped first freezes its semantics at 1.0.
656
+
642
657
  Args:
643
658
  fn: Whatever was handed to the decorator.
644
659
 
@@ -646,8 +661,8 @@ def _refuse_unusable(fn: object) -> None:
646
661
  None.
647
662
 
648
663
  Raises:
649
- TypeError: If ``fn`` is a ``classmethod`` or ``staticmethod`` object, a ``str``, or not
650
- callable at all.
664
+ TypeError: If ``fn`` is a ``classmethod`` or ``staticmethod`` object, a ``str``, not
665
+ callable at all, or a generator function or async generator function.
651
666
  Exception: Whatever the callable's own attribute access raises — a ``__class__`` property,
652
667
  say — at decoration, in the caller's frame, which is invariant 13's moment.
653
668
  """
@@ -664,6 +679,61 @@ def _refuse_unusable(fn: object) -> None:
664
679
  )
665
680
  if not callable(fn):
666
681
  raise TypeError(f"@trace needs a callable, got {type(fn).__name__}")
682
+ if _is_generator_function(fn):
683
+ raise TypeError(
684
+ f"@trace cannot trace the iteration of the generator function {_span_name(fn)}: its "
685
+ f"body runs after the wrapper has returned, so the span would close before it "
686
+ f"starts. Trace the consumer instead: the function that iterates it"
687
+ )
688
+
689
+
690
+ def _is_generator_function(fn: object) -> bool:
691
+ """Reports whether a callable's body is a generator or async generator (SPEC-055 FR-003).
692
+
693
+ The same shape as :func:`_is_async`: the code flags of ``fn`` itself, which see through a
694
+ ``functools.partial``; then of whatever ``fn`` advertises through ``__wrapped__``, which is
695
+ how ``@contextmanager``, ``@lru_cache`` and every ``functools.wraps`` wrapper point back at
696
+ the generator they wrap; and for anything that is not a plain function the type's
697
+ ``__call__`` as well, so a callable instance whose ``__call__`` yields is caught too. The
698
+ unwrap is guarded because :func:`inspect.unwrap` raises ``ValueError`` on a ``__wrapped__``
699
+ cycle, and a cycle is not a generator.
700
+
701
+ Args:
702
+ fn: The callable being decorated.
703
+
704
+ Returns:
705
+ Whether ``fn`` would return a generator or async generator object when called.
706
+
707
+ Raises:
708
+ Exception: Whatever the callable's own ``__class__``, ``__call__`` or ``__wrapped__``
709
+ lookup raises, at decoration, in the caller's frame.
710
+ """
711
+ if _yields(fn):
712
+ return True
713
+ try:
714
+ target = inspect.unwrap(cast("Callable[..., Any]", fn))
715
+ except ValueError:
716
+ target = fn
717
+ if target is not fn and _yields(target):
718
+ return True
719
+ if inspect.isfunction(fn):
720
+ return False
721
+ return _yields(type(fn).__call__)
722
+
723
+
724
+ def _yields(fn: object) -> bool:
725
+ """Reads a callable's code flags for a generator or async generator body.
726
+
727
+ Args:
728
+ fn: The callable to inspect.
729
+
730
+ Returns:
731
+ Whether its body is a generator or an async generator.
732
+
733
+ Raises:
734
+ None.
735
+ """
736
+ return inspect.isgeneratorfunction(fn) or inspect.isasyncgenfunction(fn)
667
737
 
668
738
 
669
739
  def _span_name(fn: object) -> str:
@@ -749,8 +819,10 @@ def trace(
749
819
 
750
820
  Raises:
751
821
  TypeError: At decoration, for a ``classmethod`` or ``staticmethod`` object (the decorators
752
- are in the wrong order), a ``str`` (``name=`` was meant), anything not callable, or a
753
- ``name=`` that is not a ``str`` (SPEC-055 FR-002, invariant 13).
822
+ are in the wrong order), a ``str`` (``name=`` was meant), anything not callable, a
823
+ ``name=`` that is not a ``str`` (SPEC-055 FR-002), or a generator function, whose
824
+ iteration cannot be traced by a wrapper around the call (SPEC-055 FR-003) — invariant
825
+ 13.
754
826
  """
755
827
  span_defaults = None if defaults is None else dict(defaults)
756
828