tusk-drift-python-sdk 0.1.22__tar.gz → 0.1.24__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 (167) hide show
  1. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/PKG-INFO +12 -10
  2. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/README.md +5 -5
  3. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/communication/__init__.py +0 -13
  4. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/communication/communicator.py +73 -11
  5. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/communication/types.py +3 -29
  6. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/content_type_utils.py +1 -1
  7. tusk_drift_python_sdk-0.1.24/drift/core/coverage_server.py +274 -0
  8. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/drift_sdk.py +51 -16
  9. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/mode_utils.py +3 -1
  10. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/rust_core_binding.py +18 -7
  11. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/tracing/otel_converter.py +3 -6
  12. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/aiohttp/e2e-tests/src/app.py +23 -0
  13. tusk_drift_python_sdk-0.1.24/drift/instrumentation/django/e2e-tests/src/views.py +216 -0
  14. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/django/instrumentation.py +43 -5
  15. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/django/middleware.py +3 -0
  16. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/e2e_common/base_runner.py +6 -6
  17. tusk_drift_python_sdk-0.1.24/drift/instrumentation/e2e_common/external_http.py +55 -0
  18. tusk_drift_python_sdk-0.1.24/drift/instrumentation/e2e_common/mock_upstream/mock_server.py +348 -0
  19. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/fastapi/e2e-tests/src/app.py +31 -11
  20. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/fastapi/instrumentation.py +3 -0
  21. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/flask/e2e-tests/src/app.py +39 -9
  22. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/grpc/e2e-tests/src/greeter_pb2_grpc.py +1 -1
  23. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/httpx/e2e-tests/src/app.py +40 -0
  24. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/psycopg/e2e-tests/src/app.py +30 -8
  25. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/psycopg/instrumentation.py +3 -1
  26. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/psycopg2/e2e-tests/src/app.py +64 -0
  27. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/psycopg2/e2e-tests/src/test_requests.py +3 -0
  28. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/psycopg2/instrumentation.py +2 -1
  29. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/requests/e2e-tests/src/app.py +25 -0
  30. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/socket/instrumentation.py +18 -3
  31. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/urllib/e2e-tests/src/app.py +25 -2
  32. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/urllib/instrumentation.py +11 -7
  33. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/urllib3/e2e-tests/src/app.py +147 -22
  34. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/urllib3/e2e-tests/src/test_requests.py +22 -11
  35. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/urllib3/instrumentation.py +207 -90
  36. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/utils/psycopg_utils.py +63 -0
  37. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/utils/serialization.py +11 -1
  38. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/wsgi/handler.py +3 -0
  39. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/pyproject.toml +8 -5
  40. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/tusk_drift_python_sdk.egg-info/PKG-INFO +12 -10
  41. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/tusk_drift_python_sdk.egg-info/SOURCES.txt +3 -0
  42. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/tusk_drift_python_sdk.egg-info/requires.txt +7 -4
  43. tusk_drift_python_sdk-0.1.22/drift/instrumentation/django/e2e-tests/src/views.py +0 -153
  44. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/LICENSE +0 -0
  45. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/__init__.py +0 -0
  46. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/__init__.py +0 -0
  47. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/batch_processor.py +0 -0
  48. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/config.py +0 -0
  49. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/data_normalization.py +0 -0
  50. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/json_schema_helper.py +0 -0
  51. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/logger.py +0 -0
  52. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/metrics.py +0 -0
  53. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/mock_utils.py +0 -0
  54. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/protobuf_utils.py +0 -0
  55. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/resilience.py +0 -0
  56. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/sampling.py +0 -0
  57. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/span_serialization.py +0 -0
  58. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/trace_blocking_manager.py +0 -0
  59. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/tracing/__init__.py +0 -0
  60. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/tracing/adapters/__init__.py +0 -0
  61. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/tracing/adapters/api.py +0 -0
  62. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/tracing/adapters/base.py +0 -0
  63. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/tracing/adapters/filesystem.py +0 -0
  64. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/tracing/adapters/memory.py +0 -0
  65. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/tracing/span_exporter.py +0 -0
  66. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/tracing/span_utils.py +0 -0
  67. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/tracing/td_attributes.py +0 -0
  68. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/tracing/td_span_processor.py +0 -0
  69. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/core/types.py +0 -0
  70. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/__init__.py +0 -0
  71. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/aiohttp/__init__.py +0 -0
  72. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/aiohttp/e2e-tests/entrypoint.py +0 -0
  73. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/aiohttp/e2e-tests/src/test_requests.py +0 -0
  74. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/aiohttp/instrumentation.py +0 -0
  75. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/base.py +0 -0
  76. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/datetime/__init__.py +0 -0
  77. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/datetime/instrumentation.py +0 -0
  78. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/django/__init__.py +0 -0
  79. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/django/e2e-tests/entrypoint.py +0 -0
  80. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/django/e2e-tests/src/app.py +0 -0
  81. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/django/e2e-tests/src/settings.py +0 -0
  82. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/django/e2e-tests/src/test_requests.py +0 -0
  83. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/django/e2e-tests/src/urls.py +0 -0
  84. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/django/html_utils.py +0 -0
  85. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/e2e_common/__init__.py +0 -0
  86. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/e2e_common/test_utils.py +0 -0
  87. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/fastapi/__init__.py +0 -0
  88. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/fastapi/e2e-tests/entrypoint.py +0 -0
  89. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/fastapi/e2e-tests/src/test_requests.py +0 -0
  90. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/flask/__init__.py +0 -0
  91. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/flask/e2e-tests/entrypoint.py +0 -0
  92. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/flask/e2e-tests/src/test_requests.py +0 -0
  93. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/flask/instrumentation.py +0 -0
  94. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/grpc/__init__.py +0 -0
  95. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/grpc/e2e-tests/entrypoint.py +0 -0
  96. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/grpc/e2e-tests/src/app.py +0 -0
  97. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/grpc/e2e-tests/src/greeter_pb2.py +0 -0
  98. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/grpc/e2e-tests/src/grpc_server.py +0 -0
  99. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/grpc/e2e-tests/src/test_requests.py +0 -0
  100. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/grpc/instrumentation.py +0 -0
  101. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/grpc/types.py +0 -0
  102. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/grpc/utils.py +0 -0
  103. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/http/__init__.py +0 -0
  104. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/http/transform_engine.py +0 -0
  105. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/httpx/__init__.py +0 -0
  106. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/httpx/e2e-tests/entrypoint.py +0 -0
  107. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/httpx/e2e-tests/src/test_requests.py +0 -0
  108. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/httpx/instrumentation.py +0 -0
  109. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/kinde/__init__.py +0 -0
  110. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/kinde/instrumentation.py +0 -0
  111. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/psycopg/__init__.py +0 -0
  112. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/psycopg/e2e-tests/entrypoint.py +0 -0
  113. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/psycopg/e2e-tests/src/test_requests.py +0 -0
  114. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/psycopg/mocks.py +0 -0
  115. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/psycopg/wrappers.py +0 -0
  116. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/psycopg2/__init__.py +0 -0
  117. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/psycopg2/e2e-tests/entrypoint.py +0 -0
  118. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/pyjwt/__init__.py +0 -0
  119. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/pyjwt/instrumentation.py +0 -0
  120. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/redis/__init__.py +0 -0
  121. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/redis/e2e-tests/entrypoint.py +0 -0
  122. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/redis/e2e-tests/src/app.py +0 -0
  123. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/redis/e2e-tests/src/test_requests.py +0 -0
  124. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/redis/instrumentation.py +0 -0
  125. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/registry.py +0 -0
  126. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/requests/__init__.py +0 -0
  127. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/requests/e2e-tests/entrypoint.py +0 -0
  128. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/requests/e2e-tests/src/test_requests.py +0 -0
  129. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/requests/instrumentation.py +0 -0
  130. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/socket/__init__.py +0 -0
  131. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/sqlalchemy/__init__.py +0 -0
  132. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/sqlalchemy/context.py +0 -0
  133. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/sqlalchemy/e2e-tests/entrypoint.py +0 -0
  134. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/sqlalchemy/e2e-tests/src/app.py +0 -0
  135. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/sqlalchemy/e2e-tests/src/test_requests.py +0 -0
  136. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/sqlalchemy/instrumentation.py +0 -0
  137. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/urllib/__init__.py +0 -0
  138. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/urllib/e2e-tests/entrypoint.py +0 -0
  139. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/urllib/e2e-tests/src/test_requests.py +0 -0
  140. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/urllib3/__init__.py +0 -0
  141. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/urllib3/e2e-tests/entrypoint.py +0 -0
  142. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/wsgi/__init__.py +0 -0
  143. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/wsgi/instrumentation.py +0 -0
  144. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/wsgi/response_capture.py +0 -0
  145. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/instrumentation/wsgi/utilities.py +0 -0
  146. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/django-postgres/entrypoint.py +0 -0
  147. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/django-postgres/src/app.py +0 -0
  148. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/django-postgres/src/settings.py +0 -0
  149. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/django-postgres/src/test_requests.py +0 -0
  150. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/django-postgres/src/urls.py +0 -0
  151. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/django-postgres/src/views.py +0 -0
  152. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/django-redis/entrypoint.py +0 -0
  153. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/django-redis/src/app.py +0 -0
  154. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/django-redis/src/settings.py +0 -0
  155. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/django-redis/src/test_requests.py +0 -0
  156. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/django-redis/src/urls.py +0 -0
  157. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/django-redis/src/views.py +0 -0
  158. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/fastapi-postgres/entrypoint.py +0 -0
  159. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/fastapi-postgres/src/app.py +0 -0
  160. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/fastapi-postgres/src/test_requests.py +0 -0
  161. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/fastapi-sqlalchemy/entrypoint.py +0 -0
  162. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/fastapi-sqlalchemy/src/app.py +0 -0
  163. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/stack-tests/fastapi-sqlalchemy/src/test_requests.py +0 -0
  164. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/drift/version.py +0 -0
  165. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/setup.cfg +0 -0
  166. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/tusk_drift_python_sdk.egg-info/dependency_links.txt +0 -0
  167. {tusk_drift_python_sdk-0.1.22 → tusk_drift_python_sdk-0.1.24}/tusk_drift_python_sdk.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tusk-drift-python-sdk
3
- Version: 0.1.22
3
+ Version: 0.1.24
4
4
  Summary: Python SDK for Tusk Drift instrumentation and replay
5
5
  Author-email: Tusk <support@usetusk.ai>
6
6
  License: Apache-2.0
@@ -23,10 +23,10 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Requires-Python: >=3.9
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
- Requires-Dist: protobuf>=6.0
26
+ Requires-Dist: protobuf>=3.20.0
27
27
  Requires-Dist: PyYAML>=6.0
28
- Requires-Dist: requests>=2.32.5
29
- Requires-Dist: tusk-drift-schemas>=0.1.24
28
+ Requires-Dist: requests>=2.28.0
29
+ Requires-Dist: tusk-drift-schemas>=0.1.34
30
30
  Requires-Dist: aiohttp>=3.9.0
31
31
  Requires-Dist: aiofiles>=23.0.0
32
32
  Requires-Dist: opentelemetry-api>=1.20.0
@@ -41,8 +41,10 @@ Requires-Dist: uvicorn>=0.34.2; extra == "fastapi"
41
41
  Requires-Dist: starlette<0.42.0; extra == "fastapi"
42
42
  Provides-Extra: django
43
43
  Requires-Dist: Django>=4.2; extra == "django"
44
+ Provides-Extra: coverage
45
+ Requires-Dist: coverage>=7.0.0; extra == "coverage"
44
46
  Provides-Extra: rust
45
- Requires-Dist: drift-core-python>=0.1.7; extra == "rust"
47
+ Requires-Dist: drift-core-python>=0.1.9; extra == "rust"
46
48
  Provides-Extra: dev
47
49
  Requires-Dist: Flask>=3.1.2; extra == "dev"
48
50
  Requires-Dist: fastapi>=0.115.6; extra == "dev"
@@ -61,13 +63,13 @@ Dynamic: license-file
61
63
 
62
64
  <p align="center">
63
65
  <a href="https://pypi.org/project/tusk-drift-python-sdk/"><img src="https://img.shields.io/pypi/v/tusk-drift-python-sdk" alt="PyPI version"></a>
66
+ <a href="https://pypi.org/project/tusk-drift-python-sdk/"><img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/tusk-drift-python-sdk"></a>
64
67
  <a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg" alt="License: Apache 2.0"></a>
65
- <a href="https://github.com/Use-Tusk/drift-python-sdk/commits/main/"><img src="https://img.shields.io/github/last-commit/Use-Tusk/drift-python-sdk" alt="GitHub last commit"></a>
66
68
  <a href="https://x.com/usetusk"><img src="https://img.shields.io/twitter/url?url=https%3A%2F%2Fx.com%2Fusetusk&style=flat&logo=x&label=Tusk&color=BF40BF" alt="Tusk X account"></a>
67
69
  <a href="https://join.slack.com/t/tusk-community/shared_invite/zt-3fve1s7ie-NAAUn~UpHsf1m_2tdoGjsQ"><img src="https://img.shields.io/badge/slack-badge?style=flat&logo=slack&label=Tusk&color=BF40BF" alt="Tusk Community Slack"></a>
68
70
  </p>
69
71
 
70
- The Python Tusk Drift SDK enables fast and deterministic API testing by capturing and replaying API calls made to/from your service. Automatically record real-world API calls, then replay them as tests using the [Tusk CLI](https://github.com/Use-Tusk/tusk-drift-cli) to find regressions. During replay, all outbound requests are intercepted with recorded data to ensure consistent behavior without side-effects.
72
+ The Python Tusk Drift SDK enables fast and deterministic API testing by capturing and replaying API calls made to/from your service. Automatically record real-world API calls, then replay them as tests using the [Tusk CLI](https://github.com/Use-Tusk/tusk-cli) to find regressions. During replay, all outbound requests are intercepted with recorded data to ensure consistent behavior without side-effects.
71
73
 
72
74
  <div align="center">
73
75
 
@@ -124,7 +126,7 @@ If you're using packages or versions not listed above, please create an issue wi
124
126
 
125
127
  ### Step 1: Install the CLI
126
128
 
127
- First, install the Tusk Drift CLI by following our [CLI installation guide](https://github.com/Use-Tusk/tusk-drift-cli?tab=readme-ov-file#install).
129
+ First, install the Tusk Drift CLI by following our [CLI installation guide](https://github.com/Use-Tusk/tusk-cli?tab=readme-ov-file#install).
128
130
 
129
131
  ### Step 2: Set up Tusk Drift
130
132
 
@@ -135,7 +137,7 @@ Use our AI agent to automatically set up Tusk Drift for your service:
135
137
  ```bash
136
138
  cd path/to/your/service
137
139
  export ANTHROPIC_API_KEY=your-api-key
138
- tusk setup
140
+ tusk drift setup
139
141
  ```
140
142
 
141
143
  The agent will analyze your codebase, install the SDK, instrument it into your application, create configuration files, and test the setup with recording and replay.
@@ -156,7 +158,7 @@ Alternatively, you can set up Tusk Drift manually:
156
158
 
157
159
  *For more information about Rust acceleration, refer to [this doc](docs/rust-core-bindings).*
158
160
 
159
- 2. Create configuration: Run `tusk init` to create your `.tusk/config.yaml` config file interactively, or create it manually per the [configuration docs](https://github.com/Use-Tusk/tusk-drift-cli/blob/main/docs/configuration.md).
161
+ 2. Create configuration: Run `tusk init` to create your `.tusk/config.yaml` config file interactively, or create it manually per the [configuration docs](https://github.com/Use-Tusk/tusk-cli/blob/main/docs/drift/configuration.md).
160
162
 
161
163
  3. Initialize the SDK: Refer to the [initialization guide](docs/initialization.md) to instrument the SDK in your service.
162
164
 
@@ -4,13 +4,13 @@
4
4
 
5
5
  <p align="center">
6
6
  <a href="https://pypi.org/project/tusk-drift-python-sdk/"><img src="https://img.shields.io/pypi/v/tusk-drift-python-sdk" alt="PyPI version"></a>
7
+ <a href="https://pypi.org/project/tusk-drift-python-sdk/"><img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/tusk-drift-python-sdk"></a>
7
8
  <a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg" alt="License: Apache 2.0"></a>
8
- <a href="https://github.com/Use-Tusk/drift-python-sdk/commits/main/"><img src="https://img.shields.io/github/last-commit/Use-Tusk/drift-python-sdk" alt="GitHub last commit"></a>
9
9
  <a href="https://x.com/usetusk"><img src="https://img.shields.io/twitter/url?url=https%3A%2F%2Fx.com%2Fusetusk&style=flat&logo=x&label=Tusk&color=BF40BF" alt="Tusk X account"></a>
10
10
  <a href="https://join.slack.com/t/tusk-community/shared_invite/zt-3fve1s7ie-NAAUn~UpHsf1m_2tdoGjsQ"><img src="https://img.shields.io/badge/slack-badge?style=flat&logo=slack&label=Tusk&color=BF40BF" alt="Tusk Community Slack"></a>
11
11
  </p>
12
12
 
13
- The Python Tusk Drift SDK enables fast and deterministic API testing by capturing and replaying API calls made to/from your service. Automatically record real-world API calls, then replay them as tests using the [Tusk CLI](https://github.com/Use-Tusk/tusk-drift-cli) to find regressions. During replay, all outbound requests are intercepted with recorded data to ensure consistent behavior without side-effects.
13
+ The Python Tusk Drift SDK enables fast and deterministic API testing by capturing and replaying API calls made to/from your service. Automatically record real-world API calls, then replay them as tests using the [Tusk CLI](https://github.com/Use-Tusk/tusk-cli) to find regressions. During replay, all outbound requests are intercepted with recorded data to ensure consistent behavior without side-effects.
14
14
 
15
15
  <div align="center">
16
16
 
@@ -67,7 +67,7 @@ If you're using packages or versions not listed above, please create an issue wi
67
67
 
68
68
  ### Step 1: Install the CLI
69
69
 
70
- First, install the Tusk Drift CLI by following our [CLI installation guide](https://github.com/Use-Tusk/tusk-drift-cli?tab=readme-ov-file#install).
70
+ First, install the Tusk Drift CLI by following our [CLI installation guide](https://github.com/Use-Tusk/tusk-cli?tab=readme-ov-file#install).
71
71
 
72
72
  ### Step 2: Set up Tusk Drift
73
73
 
@@ -78,7 +78,7 @@ Use our AI agent to automatically set up Tusk Drift for your service:
78
78
  ```bash
79
79
  cd path/to/your/service
80
80
  export ANTHROPIC_API_KEY=your-api-key
81
- tusk setup
81
+ tusk drift setup
82
82
  ```
83
83
 
84
84
  The agent will analyze your codebase, install the SDK, instrument it into your application, create configuration files, and test the setup with recording and replay.
@@ -99,7 +99,7 @@ Alternatively, you can set up Tusk Drift manually:
99
99
 
100
100
  *For more information about Rust acceleration, refer to [this doc](docs/rust-core-bindings).*
101
101
 
102
- 2. Create configuration: Run `tusk init` to create your `.tusk/config.yaml` config file interactively, or create it manually per the [configuration docs](https://github.com/Use-Tusk/tusk-drift-cli/blob/main/docs/configuration.md).
102
+ 2. Create configuration: Run `tusk init` to create your `.tusk/config.yaml` config file interactively, or create it manually per the [configuration docs](https://github.com/Use-Tusk/tusk-cli/blob/main/docs/drift/configuration.md).
103
103
 
104
104
  3. Initialize the SDK: Refer to the [initialization guide](docs/initialization.md) to instrument the SDK in your service.
105
105
 
@@ -6,28 +6,18 @@ for replay testing. Communication uses Protocol Buffers over Unix sockets or TCP
6
6
 
7
7
  from .communicator import CommunicatorConfig, ProtobufCommunicator
8
8
  from .types import (
9
- CliMessage,
10
- CLIMessageType,
11
9
  ConnectRequest,
12
10
  ConnectResponse,
13
11
  GetMockRequest,
14
12
  GetMockResponse,
15
- MessageType,
16
13
  MockRequestInput,
17
14
  MockResponseOutput,
18
- # Protobuf types (re-exported)
19
- SdkMessage,
20
- SDKMessageType,
21
15
  dict_to_span,
22
16
  extract_response_data,
23
17
  span_to_proto,
24
18
  )
25
19
 
26
20
  __all__ = [
27
- # Message types
28
- "MessageType",
29
- "SDKMessageType",
30
- "CLIMessageType",
31
21
  # Request/Response types
32
22
  "ConnectRequest",
33
23
  "ConnectResponse",
@@ -35,9 +25,6 @@ __all__ = [
35
25
  "GetMockResponse",
36
26
  "MockRequestInput",
37
27
  "MockResponseOutput",
38
- # Protobuf types
39
- "SdkMessage",
40
- "CliMessage",
41
28
  # Utilities
42
29
  "span_to_proto",
43
30
  "dict_to_span",
@@ -10,24 +10,31 @@ import traceback
10
10
  from dataclasses import dataclass
11
11
  from typing import Any
12
12
 
13
- from tusk.drift.core.v1 import GetMockRequest as ProtoGetMockRequest
13
+ from tusk.drift.core.v1 import (
14
+ BranchInfo,
15
+ CliMessage,
16
+ CoverageSnapshotResponse,
17
+ FileCoverageData,
18
+ InstrumentationVersionMismatchAlert,
19
+ MessageType,
20
+ SdkMessage,
21
+ SendAlertRequest,
22
+ SendInboundSpanForReplayRequest,
23
+ SetTimeTravelResponse,
24
+ UnpatchedDependencyAlert,
25
+ )
26
+ from tusk.drift.core.v1 import (
27
+ GetMockRequest as ProtoGetMockRequest,
28
+ )
14
29
 
15
30
  from ...version import MIN_CLI_VERSION, SDK_VERSION
16
31
  from ..span_serialization import clean_span_to_proto
17
32
  from ..types import CleanSpanData, calling_library_context
18
33
  from .types import (
19
- CliMessage,
20
34
  ConnectRequest,
21
35
  GetMockRequest,
22
- InstrumentationVersionMismatchAlert,
23
- MessageType,
24
36
  MockRequestInput,
25
37
  MockResponseOutput,
26
- SdkMessage,
27
- SendAlertRequest,
28
- SendInboundSpanForReplayRequest,
29
- SetTimeTravelResponse,
30
- UnpatchedDependencyAlert,
31
38
  span_to_proto,
32
39
  )
33
40
 
@@ -750,6 +757,10 @@ class ProtobufCommunicator:
750
757
  self._handle_set_time_travel_sync(cli_message)
751
758
  continue
752
759
 
760
+ if cli_message.type == MessageType.COVERAGE_SNAPSHOT:
761
+ self._handle_coverage_snapshot_sync(cli_message)
762
+ continue
763
+
753
764
  # Route responses to waiting callers by request_id
754
765
  request_id = cli_message.request_id
755
766
  if request_id:
@@ -774,8 +785,8 @@ class ProtobufCommunicator:
774
785
 
775
786
  def _handle_set_time_travel_sync(self, cli_message: CliMessage) -> None:
776
787
  """Handle SetTimeTravel request from CLI and send response."""
777
- request = cli_message.set_time_travel_request
778
- if not request:
788
+ request = getattr(cli_message, "set_time_travel_request", None)
789
+ if request is None:
779
790
  return
780
791
 
781
792
  logger.debug(
@@ -809,6 +820,57 @@ class ProtobufCommunicator:
809
820
  except Exception as e:
810
821
  logger.error(f"Failed to send SetTimeTravel response: {e}")
811
822
 
823
+ def _handle_coverage_snapshot_sync(self, cli_message: CliMessage) -> None:
824
+ """Handle CoverageSnapshot request from CLI and send response."""
825
+ request = getattr(cli_message, "coverage_snapshot_request", None)
826
+ if request is None:
827
+ return
828
+
829
+ logger.debug(f"Received CoverageSnapshot request: baseline={request.baseline}")
830
+
831
+ try:
832
+ from ..coverage_server import take_coverage_snapshot
833
+
834
+ result = take_coverage_snapshot(request.baseline)
835
+
836
+ # Convert to protobuf
837
+ coverage: dict[str, FileCoverageData] = {}
838
+ for file_path, file_data in result.items():
839
+ branches: dict[str, BranchInfo] = {}
840
+ for line, branch_info in file_data.get("branches", {}).items():
841
+ branches[line] = BranchInfo(
842
+ total=branch_info.get("total", 0),
843
+ covered=branch_info.get("covered", 0),
844
+ )
845
+
846
+ coverage[file_path] = FileCoverageData(
847
+ lines=file_data.get("lines", {}),
848
+ total_branches=file_data.get("totalBranches", 0),
849
+ covered_branches=file_data.get("coveredBranches", 0),
850
+ branches=branches,
851
+ )
852
+
853
+ response = CoverageSnapshotResponse(
854
+ success=True,
855
+ error="",
856
+ coverage=coverage,
857
+ )
858
+ except Exception as e:
859
+ logger.error(f"Failed to take coverage snapshot: {e}")
860
+ response = CoverageSnapshotResponse(success=False, error=str(e))
861
+
862
+ sdk_message = SdkMessage(
863
+ type=MessageType.COVERAGE_SNAPSHOT,
864
+ request_id=cli_message.request_id,
865
+ coverage_snapshot_response=response,
866
+ )
867
+
868
+ try:
869
+ self._send_message_sync(sdk_message)
870
+ logger.debug(f"Sent CoverageSnapshot response: success={response.success}")
871
+ except Exception as e:
872
+ logger.error(f"[coverage] Failed to send response: {e}")
873
+
812
874
  def _send_message_sync(self, message: SdkMessage) -> None:
813
875
  """Send a message synchronously on the main socket."""
814
876
  if not self._socket:
@@ -12,20 +12,6 @@ The protocol uses:
12
12
  from __future__ import annotations
13
13
 
14
14
  __all__ = [
15
- # Re-exported protobuf types
16
- "CliMessage",
17
- "InstrumentationVersionMismatchAlert",
18
- "MessageType",
19
- "Runtime",
20
- "SdkMessage",
21
- "SendAlertRequest",
22
- "SendInboundSpanForReplayRequest",
23
- "SetTimeTravelRequest",
24
- "SetTimeTravelResponse",
25
- "UnpatchedDependencyAlert",
26
- # Aliases
27
- "SDKMessageType",
28
- "CLIMessageType",
29
15
  # Dataclasses
30
16
  "ConnectRequest",
31
17
  "ConnectResponse",
@@ -42,18 +28,6 @@ __all__ = [
42
28
  from dataclasses import dataclass, field
43
29
  from typing import Any
44
30
 
45
- from tusk.drift.core.v1 import (
46
- CliMessage,
47
- InstrumentationVersionMismatchAlert,
48
- MessageType,
49
- Runtime,
50
- SdkMessage,
51
- SendAlertRequest,
52
- SendInboundSpanForReplayRequest,
53
- SetTimeTravelRequest,
54
- SetTimeTravelResponse,
55
- UnpatchedDependencyAlert,
56
- )
57
31
  from tusk.drift.core.v1 import (
58
32
  ConnectRequest as ProtoConnectRequest,
59
33
  )
@@ -66,6 +40,9 @@ from tusk.drift.core.v1 import (
66
40
  from tusk.drift.core.v1 import (
67
41
  GetMockResponse as ProtoGetMockResponse,
68
42
  )
43
+ from tusk.drift.core.v1 import (
44
+ Runtime,
45
+ )
69
46
  from tusk.drift.core.v1 import (
70
47
  Span as ProtoSpan,
71
48
  )
@@ -79,9 +56,6 @@ from tusk.drift.core.v1 import (
79
56
  StatusCode as ProtoStatusCode,
80
57
  )
81
58
 
82
- SDKMessageType = MessageType
83
- CLIMessageType = MessageType
84
-
85
59
 
86
60
  def _python_to_value(value: Any) -> Any:
87
61
  """Convert Python value to protobuf Value."""
@@ -111,7 +111,7 @@ CONTENT_TYPE_MAPPING: dict[str, DecodedType] = {
111
111
  "application/binary": DecodedType.BINARY,
112
112
  }
113
113
 
114
- ACCEPTABLE_DECODED_TYPES = {DecodedType.JSON, DecodedType.PLAIN_TEXT, DecodedType.HTML}
114
+ ACCEPTABLE_DECODED_TYPES = {DecodedType.JSON, DecodedType.PLAIN_TEXT}
115
115
 
116
116
 
117
117
  def get_decoded_type(content_type: str | None) -> DecodedType | None:
@@ -0,0 +1,274 @@
1
+ """Coverage collection for Python SDK.
2
+
3
+ Manages coverage.py for collecting per-test code coverage data.
4
+ Coverage data is accessed via take_coverage_snapshot() which is called
5
+ from the protobuf communicator handler.
6
+
7
+ Flow:
8
+ 1. start_coverage_collection() initializes coverage.py at SDK startup
9
+ 2. Between tests, CLI sends CoverageSnapshotRequest via protobuf
10
+ 3. Communicator calls take_coverage_snapshot(baseline)
11
+ 4. For baseline: returns ALL coverable lines (including uncovered at count=0)
12
+ 5. For per-test: returns only executed lines since last snapshot, then resets
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ import os
19
+ import threading
20
+
21
+ logger = logging.getLogger("TuskDrift")
22
+
23
+ # Shared state for coverage collection
24
+ _cov_instance = None
25
+ _source_root: str | None = None
26
+ _lock = threading.Lock()
27
+
28
+ # Cache branch structure from baseline to ensure deterministic branch counts.
29
+ # Branch detection via _analyze() depends on observed arcs, which vary with
30
+ # thread timing. By caching from the baseline (which has the fullest data),
31
+ # per-test snapshots report consistent totals.
32
+ _branch_cache: dict[str, dict] | None = None
33
+
34
+
35
+ def start_coverage_collection() -> bool:
36
+ """Initialize coverage.py collection if TUSK_COVERAGE is set.
37
+
38
+ TUSK_COVERAGE is set by the CLI when coverage is enabled.
39
+ This is the language-agnostic signal (Node uses NODE_V8_COVERAGE additionally).
40
+
41
+ Returns True if coverage was started, False otherwise.
42
+ """
43
+ global _cov_instance, _source_root
44
+
45
+ # TUSK_COVERAGE is the language-agnostic signal from the CLI
46
+ if not os.environ.get("TUSK_COVERAGE"):
47
+ return False
48
+
49
+ # Coverage collection only makes sense in REPLAY mode.
50
+ # If TUSK_DRIFT_MODE is not set we still proceed for backwards compatibility.
51
+ mode = os.environ.get("TUSK_DRIFT_MODE", "").upper()
52
+ if mode and mode != "REPLAY":
53
+ logger.debug("Coverage collection skipped: not in REPLAY mode (mode=%s)", mode)
54
+ return False
55
+
56
+ try:
57
+ import coverage as coverage_module
58
+ except ImportError:
59
+ logger.warning(
60
+ "Coverage requested but 'coverage' package is not installed. Install it with: pip install coverage"
61
+ )
62
+ return False
63
+
64
+ with _lock:
65
+ # Guard against double initialization — stop existing instance first
66
+ if _cov_instance is not None:
67
+ try:
68
+ _cov_instance.stop()
69
+ except Exception:
70
+ pass
71
+
72
+ _source_root = os.path.realpath(os.getcwd())
73
+
74
+ _cov_instance = coverage_module.Coverage(
75
+ source=[_source_root],
76
+ branch=True,
77
+ omit=[
78
+ "*/site-packages/*",
79
+ "*/venv/*",
80
+ "*/.venv/*",
81
+ "*/tests/*",
82
+ "*/test_*.py",
83
+ "*/__pycache__/*",
84
+ ],
85
+ )
86
+ _cov_instance.start()
87
+
88
+ logger.info("Coverage collection started")
89
+ return True
90
+
91
+
92
+ def stop_coverage_collection() -> None:
93
+ """Stop coverage collection and clean up. Thread-safe."""
94
+ global _cov_instance, _branch_cache, _source_root
95
+ with _lock:
96
+ if _cov_instance is not None:
97
+ try:
98
+ _cov_instance.stop()
99
+ except Exception:
100
+ pass
101
+ _cov_instance = None
102
+ _branch_cache = None
103
+ _source_root = None
104
+
105
+
106
+ def take_coverage_snapshot(baseline: bool = False) -> dict:
107
+ """Take a coverage snapshot.
108
+
109
+ Called from the protobuf communicator handler between tests.
110
+
111
+ Args:
112
+ baseline: If True, returns ALL coverable lines (including uncovered at count=0)
113
+ for computing the total coverage denominator.
114
+ If False, returns only lines executed since the last snapshot.
115
+
116
+ Returns:
117
+ dict of { filePath: { "lines": {...}, "totalBranches": N, "coveredBranches": N, "branches": {...} } }
118
+ """
119
+ with _lock:
120
+ if _cov_instance is None:
121
+ raise RuntimeError("Coverage not initialized")
122
+ _cov_instance.stop()
123
+ coverage = {}
124
+
125
+ try:
126
+ global _branch_cache
127
+ if baseline:
128
+ # Baseline: compute fresh branch data and cache it for per-test reuse
129
+ _branch_cache = {}
130
+ data = _cov_instance.get_data()
131
+ for filename in data.measured_files():
132
+ if not _is_user_file(filename):
133
+ continue
134
+ try:
135
+ _, statements, _, missing, _ = _cov_instance.analysis2(filename)
136
+ missing_set = set(missing)
137
+ lines_map = {}
138
+ for line in statements:
139
+ lines_map[str(line)] = 0 if line in missing_set else 1
140
+ branch_data = _get_branch_data(data, filename)
141
+ _branch_cache[filename] = branch_data
142
+ if lines_map:
143
+ coverage[filename] = {"lines": lines_map, **branch_data}
144
+ except Exception as e:
145
+ logger.debug(f"Failed to analyze {filename}: {e}")
146
+ continue
147
+ else:
148
+ data = _cov_instance.get_data()
149
+ for filename in data.measured_files():
150
+ if not _is_user_file(filename):
151
+ continue
152
+ lines = data.lines(filename)
153
+ if lines:
154
+ # Use cached branch data from baseline for stable totals.
155
+ # Fall back to live _analyze() if no cache (e.g., no baseline taken).
156
+ if _branch_cache is not None and filename in _branch_cache:
157
+ branch_data = _get_per_test_branch_data(data, filename, _branch_cache[filename])
158
+ else:
159
+ branch_data = _get_branch_data(data, filename)
160
+ coverage[filename] = {
161
+ "lines": {str(line): 1 for line in lines},
162
+ **branch_data,
163
+ }
164
+ finally:
165
+ _cov_instance.erase()
166
+ _cov_instance.start()
167
+
168
+ return coverage
169
+
170
+
171
+ def _is_user_file(filename: str) -> bool:
172
+ """Check if a file is a user source file (not third-party)."""
173
+ if "site-packages" in filename or "lib/python" in filename:
174
+ return False
175
+ # Resolve symlinks for consistent path comparison
176
+ resolved = os.path.realpath(filename)
177
+ # Use trailing separator to avoid prefix collisions (/app matching /application)
178
+ return not _source_root or resolved.startswith(_source_root + os.sep) or resolved == _source_root
179
+
180
+
181
+ def _group_arcs_by_line(arcs: set) -> dict[int, list[int]]:
182
+ """Group executed arcs by from_line, skipping negative entry arcs."""
183
+ by_line: dict[int, list[int]] = {}
184
+ for from_line, to_line in arcs:
185
+ if from_line < 0:
186
+ continue
187
+ by_line.setdefault(from_line, []).append(to_line)
188
+ return by_line
189
+
190
+
191
+ def _get_branch_data(data, filename: str) -> dict:
192
+ """Extract branch coverage data for a file.
193
+
194
+ Uses coverage.py's arc tracking (from_line, to_line) to compute
195
+ per-line branch coverage.
196
+ """
197
+ try:
198
+ if not data.has_arcs():
199
+ return {"totalBranches": 0, "coveredBranches": 0, "branches": {}}
200
+
201
+ if _cov_instance is None:
202
+ return {"totalBranches": 0, "coveredBranches": 0, "branches": {}}
203
+
204
+ analysis = _cov_instance._analyze(filename)
205
+ numbers = analysis.numbers
206
+
207
+ total_branches = numbers.n_branches
208
+ covered_branches = max(0, total_branches - numbers.n_missing_branches)
209
+
210
+ missing_arcs = analysis.missing_branch_arcs()
211
+ executed_arcs = set(data.arcs(filename) or [])
212
+ executed_by_line = _group_arcs_by_line(executed_arcs)
213
+
214
+ # A line is a branch point if:
215
+ # - it appears in missing_arcs (at least one path wasn't taken), OR
216
+ # - it has multiple executed arcs (multiple paths from same line)
217
+ branch_point_lines = set(missing_arcs.keys())
218
+ for from_line, to_lines in executed_by_line.items():
219
+ if len(to_lines) > 1:
220
+ branch_point_lines.add(from_line)
221
+
222
+ branch_lines: dict[int, dict] = {}
223
+
224
+ for from_line in branch_point_lines:
225
+ executed_count = len(executed_by_line.get(from_line, []))
226
+ missing_count = len(missing_arcs.get(from_line, []))
227
+ branch_lines[from_line] = {
228
+ "total": executed_count + missing_count,
229
+ "covered": executed_count,
230
+ }
231
+
232
+ branches = {str(line): info for line, info in branch_lines.items()}
233
+
234
+ return {
235
+ "totalBranches": total_branches,
236
+ "coveredBranches": covered_branches,
237
+ "branches": branches,
238
+ }
239
+ except Exception:
240
+ return {"totalBranches": 0, "coveredBranches": 0, "branches": {}}
241
+
242
+
243
+ def _get_per_test_branch_data(data, filename: str, cached: dict) -> dict:
244
+ """Compute per-test branch coverage using cached branch structure from baseline.
245
+
246
+ Uses the cached branch point set (from baseline) for stable totals,
247
+ but computes covered counts from the current test's executed arcs.
248
+ This avoids flaky branch totals caused by non-deterministic arc detection.
249
+ """
250
+ try:
251
+ if not data.has_arcs():
252
+ return {"totalBranches": 0, "coveredBranches": 0, "branches": {}}
253
+
254
+ executed_arcs = set(data.arcs(filename) or [])
255
+ executed_by_line = _group_arcs_by_line(executed_arcs)
256
+
257
+ # Use cached branch points — only compute covered from current arcs
258
+ cached_branches = cached.get("branches", {})
259
+ branches: dict[str, dict] = {}
260
+ total_covered = 0
261
+
262
+ for line_str, info in cached_branches.items():
263
+ total = info["total"]
264
+ covered = min(len(executed_by_line.get(int(line_str), [])), total)
265
+ branches[line_str] = {"total": total, "covered": covered}
266
+ total_covered += covered
267
+
268
+ return {
269
+ "totalBranches": cached.get("totalBranches", 0),
270
+ "coveredBranches": total_covered,
271
+ "branches": branches,
272
+ }
273
+ except Exception:
274
+ return {"totalBranches": 0, "coveredBranches": 0, "branches": {}}