lago-python-client 1.46.0__tar.gz → 1.47.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 (188) hide show
  1. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/PKG-INFO +1 -1
  2. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/client.py +4 -1
  3. lago_python_client-1.47.0/lago_python_client/observability.py +67 -0
  4. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/services/rate_limit.py +110 -1
  5. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/services/request.py +10 -1
  6. lago_python_client-1.47.0/lago_python_client/version.py +1 -0
  7. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client.egg-info/PKG-INFO +1 -1
  8. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client.egg-info/SOURCES.txt +2 -0
  9. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_rate_limit.py +283 -1
  10. lago_python_client-1.46.0/lago_python_client/version.py +0 -1
  11. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/LICENSE +0 -0
  12. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/README.md +0 -0
  13. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/__init__.py +0 -0
  14. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/activity_logs/__init__.py +0 -0
  15. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/activity_logs/clients.py +0 -0
  16. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/add_ons/__init__.py +0 -0
  17. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/add_ons/clients.py +0 -0
  18. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/api_logs/__init__.py +0 -0
  19. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/api_logs/clients.py +0 -0
  20. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/base_client.py +0 -0
  21. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/base_model.py +0 -0
  22. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/billable_metrics/__init__.py +0 -0
  23. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/billable_metrics/clients.py +0 -0
  24. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/billing_entities/__init__.py +0 -0
  25. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/billing_entities/clients.py +0 -0
  26. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/coupons/__init__.py +0 -0
  27. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/coupons/clients.py +0 -0
  28. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/credit_notes/__init__.py +0 -0
  29. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/credit_notes/clients.py +0 -0
  30. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/customers/__init__.py +0 -0
  31. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/customers/applied_coupons_client.py +0 -0
  32. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/customers/clients.py +0 -0
  33. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/customers/credit_notes_client.py +0 -0
  34. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/customers/invoices_client.py +0 -0
  35. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/customers/payment_methods_client.py +0 -0
  36. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/customers/payment_requests_client.py +0 -0
  37. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/customers/payments_client.py +0 -0
  38. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/customers/subscriptions_client.py +0 -0
  39. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/customers/wallet_client.py +0 -0
  40. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/customers/wallets/__init__.py +0 -0
  41. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/customers/wallets/alert_client.py +0 -0
  42. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/customers/wallets/metadata_client.py +0 -0
  43. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/customers/wallets_client.py +0 -0
  44. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/events/__init__.py +0 -0
  45. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/events/clients.py +0 -0
  46. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/exceptions.py +0 -0
  47. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/fees/__init__.py +0 -0
  48. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/fees/clients.py +0 -0
  49. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/functools_ext.py +0 -0
  50. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/gross_revenues/__init__.py +0 -0
  51. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/gross_revenues/clients.py +0 -0
  52. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/invoice_collections/__init__.py +0 -0
  53. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/invoice_collections/clients.py +0 -0
  54. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/invoiced_usages/__init__.py +0 -0
  55. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/invoiced_usages/clients.py +0 -0
  56. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/invoices/__init__.py +0 -0
  57. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/invoices/clients.py +0 -0
  58. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/mixins.py +0 -0
  59. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/__init__.py +0 -0
  60. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/activity_log.py +0 -0
  61. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/add_on.py +0 -0
  62. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/alert.py +0 -0
  63. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/api_log.py +0 -0
  64. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/applied_coupon.py +0 -0
  65. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/billable_metric.py +0 -0
  66. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/billing_entity.py +0 -0
  67. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/billing_period.py +0 -0
  68. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/charge.py +0 -0
  69. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/coupon.py +0 -0
  70. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/credit.py +0 -0
  71. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/credit_note.py +0 -0
  72. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/customer.py +0 -0
  73. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/customer_projected_usage.py +0 -0
  74. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/customer_usage.py +0 -0
  75. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/error_detail.py +0 -0
  76. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/event.py +0 -0
  77. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/fee.py +0 -0
  78. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/fixed_charge.py +0 -0
  79. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/gross_revenue.py +0 -0
  80. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/invoice.py +0 -0
  81. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/invoice_collection.py +0 -0
  82. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/invoice_custom_section.py +0 -0
  83. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/invoice_item.py +0 -0
  84. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/invoiced_usage.py +0 -0
  85. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/lifetime_usage.py +0 -0
  86. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/minimum_commitment.py +0 -0
  87. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/mrr.py +0 -0
  88. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/organization.py +0 -0
  89. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/overdue_balance.py +0 -0
  90. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/payment.py +0 -0
  91. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/payment_method.py +0 -0
  92. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/payment_receipt.py +0 -0
  93. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/payment_request.py +0 -0
  94. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/plan.py +0 -0
  95. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/subscription.py +0 -0
  96. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/tax.py +0 -0
  97. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/usage.py +0 -0
  98. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/usage_threshold.py +0 -0
  99. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/wallet.py +0 -0
  100. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/wallet_transaction.py +0 -0
  101. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/models/webhook_endpoint.py +0 -0
  102. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/mrrs/__init__.py +0 -0
  103. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/mrrs/clients.py +0 -0
  104. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/organizations/__init__.py +0 -0
  105. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/organizations/clients.py +0 -0
  106. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/overdue_balances/__init__.py +0 -0
  107. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/overdue_balances/clients.py +0 -0
  108. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/payment_receipts/__init__.py +0 -0
  109. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/payment_receipts/clients.py +0 -0
  110. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/payment_requests/__init__.py +0 -0
  111. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/payment_requests/clients.py +0 -0
  112. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/payments/__init__.py +0 -0
  113. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/payments/clients.py +0 -0
  114. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/plans/__init__.py +0 -0
  115. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/plans/charges_client.py +0 -0
  116. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/plans/clients.py +0 -0
  117. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/plans/fixed_charges_client.py +0 -0
  118. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/py.typed +0 -0
  119. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/services/__init__.py +0 -0
  120. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/services/json.py +0 -0
  121. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/services/response.py +0 -0
  122. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/subscriptions/__init__.py +0 -0
  123. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/subscriptions/charges_client.py +0 -0
  124. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/subscriptions/clients.py +0 -0
  125. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/subscriptions/fixed_charges_client.py +0 -0
  126. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/taxes/__init__.py +0 -0
  127. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/taxes/clients.py +0 -0
  128. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/usages/__init__.py +0 -0
  129. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/usages/clients.py +0 -0
  130. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/wallets/__init__.py +0 -0
  131. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/wallets/clients.py +0 -0
  132. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/webhook_endpoints/__init__.py +0 -0
  133. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/webhook_endpoints/clients.py +0 -0
  134. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/webhooks/__init__.py +0 -0
  135. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client/webhooks/clients.py +0 -0
  136. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client.egg-info/dependency_links.txt +0 -0
  137. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client.egg-info/requires.txt +0 -0
  138. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/lago_python_client.egg-info/top_level.txt +0 -0
  139. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/pyproject.toml +0 -0
  140. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/setup.cfg +0 -0
  141. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/__init__.py +0 -0
  142. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_activity_logs_client.py +0 -0
  143. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_add_on_client.py +0 -0
  144. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_api_logs_client.py +0 -0
  145. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_applied_coupon_client.py +0 -0
  146. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_billable_metric_client.py +0 -0
  147. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_billing_entity_client.py +0 -0
  148. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_client.py +0 -0
  149. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_coupon_client.py +0 -0
  150. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_credit_note_client.py +0 -0
  151. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_customer_applied_coupons_client.py +0 -0
  152. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_customer_client.py +0 -0
  153. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_customer_credit_notes_client.py +0 -0
  154. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_customer_invoices_client.py +0 -0
  155. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_customer_payment_methods_client.py +0 -0
  156. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_customer_payment_requests_client.py +0 -0
  157. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_customer_payments_client.py +0 -0
  158. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_customer_sbscriptions_client.py +0 -0
  159. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_customer_wallet_alert_client.py +0 -0
  160. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_customer_wallet_client.py +0 -0
  161. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_customer_wallet_metadata_client.py +0 -0
  162. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_customer_wallets_client.py +0 -0
  163. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_event_client.py +0 -0
  164. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_fee_client.py +0 -0
  165. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_functools_ext.py +0 -0
  166. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_gross_revenue_client.py +0 -0
  167. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_invoice_client.py +0 -0
  168. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_invoice_collection_client.py +0 -0
  169. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_invoiced_usage_client.py +0 -0
  170. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_json_services.py +0 -0
  171. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_mrr_client.py +0 -0
  172. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_organization_client.py +0 -0
  173. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_overdue_balance_client.py +0 -0
  174. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_payment_client.py +0 -0
  175. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_payment_receipt_client.py +0 -0
  176. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_payment_request_client.py +0 -0
  177. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_plan_client.py +0 -0
  178. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_request_services.py +0 -0
  179. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_response_services.py +0 -0
  180. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_subscription_client.py +0 -0
  181. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_tax_client.py +0 -0
  182. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_usage_client.py +0 -0
  183. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_wallet_client.py +0 -0
  184. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_wallet_transaction_client.py +0 -0
  185. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_webhook_client.py +0 -0
  186. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/test_webhook_endpoint_client.py +0 -0
  187. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/utils/__init__.py +0 -0
  188. {lago_python_client-1.46.0 → lago_python_client-1.47.0}/tests/utils/mixin.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lago-python-client
3
- Version: 1.46.0
3
+ Version: 1.47.0
4
4
  Summary: Lago Python API Client
5
5
  Home-page: https://github.com/getlago/lago-python-client
6
6
  Author: Lovro Colic
@@ -1,3 +1,4 @@
1
+ from typing import Optional
1
2
  from urllib.parse import urljoin
2
3
 
3
4
  from .activity_logs.clients import ActivityLogClient
@@ -32,7 +33,7 @@ from .payments.clients import PaymentClient
32
33
  from .plans.charges_client import PlanChargesClient
33
34
  from .plans.clients import PlanClient
34
35
  from .plans.fixed_charges_client import PlanFixedChargesClient
35
- from .services.rate_limit import RateLimitRetryConfig
36
+ from .services.rate_limit import RateLimitCallback, RateLimitRetryConfig
36
37
  from .subscriptions.charges_client import SubscriptionChargesClient
37
38
  from .subscriptions.clients import SubscriptionClient
38
39
  from .subscriptions.fixed_charges_client import SubscriptionFixedChargesClient
@@ -61,6 +62,7 @@ class Client:
61
62
  ingest_api_url: str = "",
62
63
  max_retries: int = 3,
63
64
  retry_on_rate_limit: bool = True,
65
+ on_rate_limit_info: Optional[RateLimitCallback] = None,
64
66
  ) -> None:
65
67
  self.api_key: str = api_key
66
68
  self.api_url: str = api_url
@@ -69,6 +71,7 @@ class Client:
69
71
  self.rate_limit_retry_config: RateLimitRetryConfig = RateLimitRetryConfig(
70
72
  max_retries=max_retries,
71
73
  retry_on_rate_limit=retry_on_rate_limit,
74
+ on_rate_limit_info=on_rate_limit_info,
72
75
  )
73
76
 
74
77
  @property
@@ -0,0 +1,67 @@
1
+ """
2
+ Optional observability helpers for Lago rate limits.
3
+
4
+ Provides ready-to-use ``on_rate_limit_info`` callbacks so callers can opt into
5
+ rate limit observability without writing one themselves.
6
+
7
+ Example:
8
+ from lago_python_client import Client
9
+ from lago_python_client.observability import LoggingRateLimitObserver
10
+
11
+ client = Client(
12
+ api_key="...",
13
+ on_rate_limit_info=LoggingRateLimitObserver(),
14
+ )
15
+ """
16
+
17
+ import logging
18
+ from typing import Optional, Sequence
19
+
20
+ from .services.rate_limit import RateLimitInfo
21
+
22
+ DEFAULT_THRESHOLDS: Sequence[float] = (0.80, 0.90, 0.95)
23
+
24
+
25
+ class LoggingRateLimitObserver:
26
+ """
27
+ Emits a log line each time rate limit usage crosses a configured threshold.
28
+
29
+ Stateless across calls: every response that lands above any threshold logs.
30
+ If you want one-shot crossings, wrap it with your own state.
31
+
32
+ Args:
33
+ thresholds: Usage fractions (0.0 - 1.0) that should produce a log line.
34
+ Defaults to 80%, 90%, 95%.
35
+ logger: Logger to emit on. Defaults to ``lago_python_client.rate_limit``.
36
+ level: Log level used when a threshold is crossed. Defaults to WARNING.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ thresholds: Sequence[float] = DEFAULT_THRESHOLDS,
42
+ logger: Optional[logging.Logger] = None,
43
+ level: int = logging.WARNING,
44
+ ) -> None:
45
+ # Sort descending so we report the highest matching threshold first.
46
+ self.thresholds = tuple(sorted(thresholds, reverse=True))
47
+ self.logger = logger or logging.getLogger("lago_python_client.rate_limit")
48
+ self.level = level
49
+
50
+ def __call__(self, info: RateLimitInfo) -> None:
51
+ pct = info.usage_pct
52
+ if pct is None:
53
+ return
54
+
55
+ for threshold in self.thresholds:
56
+ if pct >= threshold:
57
+ self.logger.log(
58
+ self.level,
59
+ "Lago rate limit at %.0f%% (limit=%s, remaining=%s, reset=%ss, %s %s)",
60
+ pct * 100,
61
+ info.limit,
62
+ info.remaining,
63
+ info.reset,
64
+ info.method,
65
+ info.url,
66
+ )
67
+ return
@@ -1,11 +1,87 @@
1
+ import logging
1
2
  import time
2
- from typing import TYPE_CHECKING, Optional
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Callable, Optional
3
5
 
4
6
  if TYPE_CHECKING:
5
7
  from httpx import Response
6
8
 
7
9
  from ..exceptions import LagoRateLimitError
8
10
 
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class RateLimitInfo:
16
+ """
17
+ Parsed rate limit headers from a Lago API response.
18
+
19
+ Provided to ``on_rate_limit_info`` callbacks after every successful request
20
+ so callers can build their own observability (metrics, logs, alerts).
21
+ """
22
+
23
+ limit: Optional[int]
24
+ remaining: Optional[int]
25
+ reset: Optional[int]
26
+ url: str
27
+ method: str
28
+
29
+ @property
30
+ def usage_pct(self) -> Optional[float]:
31
+ """
32
+ Fraction of the rate limit currently used, between 0.0 and 1.0.
33
+
34
+ Returns None when the headers aren't usable (missing, zero limit, etc.).
35
+ """
36
+ if self.limit is None or self.remaining is None or self.limit <= 0:
37
+ return None
38
+ return 1.0 - (self.remaining / self.limit)
39
+
40
+
41
+ RateLimitCallback = Callable[[RateLimitInfo], None]
42
+
43
+
44
+ def parse_rate_limit_info(
45
+ response: "Response",
46
+ method: str,
47
+ url: str,
48
+ ) -> Optional[RateLimitInfo]:
49
+ """
50
+ Parse ``x-ratelimit-*`` headers from a response into a RateLimitInfo.
51
+
52
+ Returns ``None`` when no rate limit headers are present (for example, on a
53
+ self-hosted Lago instance that doesn't enforce limits) so callers can skip
54
+ emission entirely.
55
+ """
56
+ headers = response.headers
57
+ limit: Optional[int] = None
58
+ remaining: Optional[int] = None
59
+ reset: Optional[int] = None
60
+
61
+ try:
62
+ limit = int(headers["x-ratelimit-limit"])
63
+ except (KeyError, ValueError, TypeError):
64
+ pass
65
+ try:
66
+ remaining = int(headers["x-ratelimit-remaining"])
67
+ except (KeyError, ValueError, TypeError):
68
+ pass
69
+ try:
70
+ reset = int(headers["x-ratelimit-reset"])
71
+ except (KeyError, ValueError, TypeError):
72
+ pass
73
+
74
+ if limit is None and remaining is None and reset is None:
75
+ return None
76
+
77
+ return RateLimitInfo(
78
+ limit=limit,
79
+ remaining=remaining,
80
+ reset=reset,
81
+ url=url,
82
+ method=method,
83
+ )
84
+
9
85
 
10
86
  class RateLimitRetryConfig:
11
87
  """Configuration for rate limit retry behavior."""
@@ -17,6 +93,7 @@ class RateLimitRetryConfig:
17
93
  base_backoff_seconds: float = 1.0,
18
94
  backoff_multiplier: float = 2.0,
19
95
  max_retry_delay: float = 20.0,
96
+ on_rate_limit_info: Optional[RateLimitCallback] = None,
20
97
  ):
21
98
  """
22
99
  Initialize rate limit retry configuration.
@@ -27,12 +104,18 @@ class RateLimitRetryConfig:
27
104
  base_backoff_seconds: Initial backoff duration in seconds for exponential backoff.
28
105
  backoff_multiplier: Multiplier for exponential backoff between retries.
29
106
  max_retry_delay: Maximum delay in seconds before a retry (default: 20).
107
+ on_rate_limit_info: Optional callback invoked after every successful
108
+ response with parsed rate limit headers. Use this to build
109
+ observability around the rate limit (warn at 80%, emit metrics,
110
+ etc.). Exceptions raised from the callback are logged and
111
+ swallowed so they never break the request flow.
30
112
  """
31
113
  self.max_retries = max_retries
32
114
  self.retry_on_rate_limit = retry_on_rate_limit
33
115
  self.base_backoff_seconds = base_backoff_seconds
34
116
  self.backoff_multiplier = backoff_multiplier
35
117
  self.max_retry_delay = max_retry_delay
118
+ self.on_rate_limit_info = on_rate_limit_info
36
119
 
37
120
  def calculate_backoff(self, retry_attempt: int) -> float:
38
121
  """
@@ -47,6 +130,32 @@ class RateLimitRetryConfig:
47
130
  return self.base_backoff_seconds * (self.backoff_multiplier**retry_attempt)
48
131
 
49
132
 
133
+ def emit_rate_limit_info(
134
+ response: "Response",
135
+ config: "RateLimitRetryConfig",
136
+ method: str,
137
+ url: str,
138
+ ) -> None:
139
+ """
140
+ Invoke the ``on_rate_limit_info`` callback if configured.
141
+
142
+ Parses rate limit headers off the response and hands them to the user's
143
+ callback. Any exception raised from the callback is logged and swallowed so
144
+ a buggy observer never breaks the underlying request.
145
+ """
146
+ if config.on_rate_limit_info is None:
147
+ return
148
+
149
+ info = parse_rate_limit_info(response, method=method, url=url)
150
+ if info is None:
151
+ return
152
+
153
+ try:
154
+ config.on_rate_limit_info(info)
155
+ except Exception:
156
+ logger.exception("on_rate_limit_info callback raised; suppressing")
157
+
158
+
50
159
  def handle_rate_limit_response(
51
160
  response: "Response",
52
161
  config: RateLimitRetryConfig,
@@ -12,6 +12,7 @@ import httpx
12
12
  from ..version import LAGO_VERSION
13
13
  from .rate_limit import (
14
14
  RateLimitRetryConfig,
15
+ emit_rate_limit_info,
15
16
  handle_rate_limit_response,
16
17
  is_rate_limit_response,
17
18
  wait_for_retry,
@@ -92,12 +93,20 @@ def _create_retry_wrapper(
92
93
  if rate_limit_retry_config is None:
93
94
  rate_limit_retry_config = RateLimitRetryConfig()
94
95
 
96
+ method_name = http_method.__name__.upper()
97
+
95
98
  retry_attempt = 0
96
99
  while True:
97
100
  response = http_method(url, **kwargs)
98
101
 
99
- # If not rate limited, return the response
102
+ # If not rate limited, emit observability info and return the response
100
103
  if not is_rate_limit_response(response):
104
+ emit_rate_limit_info(
105
+ response,
106
+ rate_limit_retry_config,
107
+ method=method_name,
108
+ url=url,
109
+ )
101
110
  return response
102
111
 
103
112
  # Handle rate limit - may raise LagoRateLimitError or return wait duration
@@ -0,0 +1 @@
1
+ LAGO_VERSION = "1.47.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lago-python-client
3
- Version: 1.46.0
3
+ Version: 1.47.0
4
4
  Summary: Lago Python API Client
5
5
  Home-page: https://github.com/getlago/lago-python-client
6
6
  Author: Lovro Colic
@@ -9,6 +9,7 @@ setup.cfg
9
9
  ./lago_python_client/exceptions.py
10
10
  ./lago_python_client/functools_ext.py
11
11
  ./lago_python_client/mixins.py
12
+ ./lago_python_client/observability.py
12
13
  ./lago_python_client/py.typed
13
14
  ./lago_python_client/version.py
14
15
  ./lago_python_client/activity_logs/__init__.py
@@ -185,6 +186,7 @@ lago_python_client/client.py
185
186
  lago_python_client/exceptions.py
186
187
  lago_python_client/functools_ext.py
187
188
  lago_python_client/mixins.py
189
+ lago_python_client/observability.py
188
190
  lago_python_client/py.typed
189
191
  lago_python_client/version.py
190
192
  lago_python_client.egg-info/PKG-INFO
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import os
2
3
 
3
4
  import pytest
@@ -5,7 +6,12 @@ from pytest_httpx import HTTPXMock
5
6
 
6
7
  from lago_python_client.client import Client
7
8
  from lago_python_client.exceptions import LagoApiError, LagoRateLimitError
8
- from lago_python_client.services.rate_limit import RateLimitRetryConfig
9
+ from lago_python_client.observability import LoggingRateLimitObserver
10
+ from lago_python_client.services.rate_limit import (
11
+ RateLimitInfo,
12
+ RateLimitRetryConfig,
13
+ parse_rate_limit_info,
14
+ )
9
15
 
10
16
  ENDPOINT = "https://api.getlago.com/api/v1/api_logs"
11
17
 
@@ -318,3 +324,279 @@ class TestRateLimitResponses:
318
324
  error = exc_info.value
319
325
  assert isinstance(error, LagoApiError)
320
326
  assert not isinstance(error, LagoRateLimitError)
327
+
328
+
329
+ class TestRateLimitInfo:
330
+ def test_usage_pct_basic(self):
331
+ info = RateLimitInfo(
332
+ limit=100,
333
+ remaining=20,
334
+ reset=30,
335
+ url="u",
336
+ method="GET",
337
+ )
338
+ assert info.usage_pct == pytest.approx(0.80)
339
+
340
+ def test_usage_pct_full(self):
341
+ info = RateLimitInfo(
342
+ limit=100,
343
+ remaining=0,
344
+ reset=30,
345
+ url="u",
346
+ method="GET",
347
+ )
348
+ assert info.usage_pct == pytest.approx(1.0)
349
+
350
+ def test_usage_pct_none_when_limit_missing(self):
351
+ info = RateLimitInfo(
352
+ limit=None,
353
+ remaining=20,
354
+ reset=30,
355
+ url="u",
356
+ method="GET",
357
+ )
358
+ assert info.usage_pct is None
359
+
360
+ def test_usage_pct_none_when_remaining_missing(self):
361
+ info = RateLimitInfo(
362
+ limit=100,
363
+ remaining=None,
364
+ reset=30,
365
+ url="u",
366
+ method="GET",
367
+ )
368
+ assert info.usage_pct is None
369
+
370
+ def test_usage_pct_none_when_limit_zero(self):
371
+ info = RateLimitInfo(
372
+ limit=0,
373
+ remaining=0,
374
+ reset=30,
375
+ url="u",
376
+ method="GET",
377
+ )
378
+ assert info.usage_pct is None
379
+
380
+
381
+ class TestParseRateLimitInfo:
382
+ def _make_response(self, headers):
383
+ class _R:
384
+ def __init__(self, h):
385
+ self.headers = h
386
+
387
+ return _R(headers)
388
+
389
+ def test_parse_full_headers(self):
390
+ resp = self._make_response(
391
+ {
392
+ "x-ratelimit-limit": "100",
393
+ "x-ratelimit-remaining": "42",
394
+ "x-ratelimit-reset": "5",
395
+ }
396
+ )
397
+
398
+ info = parse_rate_limit_info(resp, method="GET", url="https://x")
399
+
400
+ assert info is not None
401
+ assert info.limit == 100
402
+ assert info.remaining == 42
403
+ assert info.reset == 5
404
+ assert info.method == "GET"
405
+ assert info.url == "https://x"
406
+
407
+ def test_parse_returns_none_when_headers_absent(self):
408
+ resp = self._make_response({})
409
+ assert parse_rate_limit_info(resp, method="GET", url="https://x") is None
410
+
411
+ def test_parse_partial_headers(self):
412
+ resp = self._make_response({"x-ratelimit-limit": "100"})
413
+
414
+ info = parse_rate_limit_info(resp, method="GET", url="https://x")
415
+
416
+ assert info is not None
417
+ assert info.limit == 100
418
+ assert info.remaining is None
419
+ assert info.reset is None
420
+
421
+ def test_parse_invalid_headers(self):
422
+ resp = self._make_response(
423
+ {
424
+ "x-ratelimit-limit": "not-a-number",
425
+ "x-ratelimit-remaining": "nope",
426
+ "x-ratelimit-reset": "bad",
427
+ }
428
+ )
429
+
430
+ # All fields fail to parse -> all None -> treated as no rate limit info
431
+ assert parse_rate_limit_info(resp, method="GET", url="https://x") is None
432
+
433
+
434
+ class TestRateLimitInfoCallback:
435
+ def test_callback_fires_on_success(self, httpx_mock: HTTPXMock):
436
+ """Callback receives rate limit info after a 2xx response."""
437
+ captured = []
438
+
439
+ client = Client(api_key="test-key", on_rate_limit_info=captured.append)
440
+ request_id = "test-id"
441
+
442
+ httpx_mock.add_response(
443
+ method="GET",
444
+ url=ENDPOINT + f"/{request_id}",
445
+ status_code=200,
446
+ headers={
447
+ "x-ratelimit-limit": "100",
448
+ "x-ratelimit-remaining": "20",
449
+ "x-ratelimit-reset": "5",
450
+ },
451
+ content=mock_response("fixtures/api_log.json"),
452
+ )
453
+
454
+ client.api_logs.find(request_id)
455
+
456
+ assert len(captured) == 1
457
+ info = captured[0]
458
+ assert info.limit == 100
459
+ assert info.remaining == 20
460
+ assert info.reset == 5
461
+ assert info.usage_pct == pytest.approx(0.80)
462
+ assert info.method == "GET"
463
+
464
+ def test_callback_not_called_when_headers_absent(self, httpx_mock: HTTPXMock):
465
+ """Self-hosted-style response with no rate limit headers must not call back."""
466
+ captured = []
467
+
468
+ client = Client(api_key="test-key", on_rate_limit_info=captured.append)
469
+ request_id = "test-id"
470
+
471
+ httpx_mock.add_response(
472
+ method="GET",
473
+ url=ENDPOINT + f"/{request_id}",
474
+ status_code=200,
475
+ content=mock_response("fixtures/api_log.json"),
476
+ )
477
+
478
+ client.api_logs.find(request_id)
479
+
480
+ assert captured == []
481
+
482
+ def test_callback_exception_is_swallowed(self, httpx_mock: HTTPXMock, caplog):
483
+ """A buggy callback must not break the request."""
484
+
485
+ def boom(info):
486
+ raise RuntimeError("intentional")
487
+
488
+ client = Client(api_key="test-key", on_rate_limit_info=boom)
489
+ request_id = "test-id"
490
+
491
+ httpx_mock.add_response(
492
+ method="GET",
493
+ url=ENDPOINT + f"/{request_id}",
494
+ status_code=200,
495
+ headers={
496
+ "x-ratelimit-limit": "100",
497
+ "x-ratelimit-remaining": "1",
498
+ "x-ratelimit-reset": "5",
499
+ },
500
+ content=mock_response("fixtures/api_log.json"),
501
+ )
502
+
503
+ with caplog.at_level(logging.ERROR, logger="lago_python_client.services.rate_limit"):
504
+ result = client.api_logs.find(request_id)
505
+
506
+ assert result is not None
507
+ assert any("on_rate_limit_info callback raised" in rec.message for rec in caplog.records)
508
+
509
+ def test_callback_fires_only_once_after_429_retry_resolves(
510
+ self,
511
+ httpx_mock: HTTPXMock,
512
+ monkeypatch,
513
+ ):
514
+ """After a 429 retry sequence resolves to 2xx, callback fires once for the success."""
515
+ monkeypatch.setattr("lago_python_client.services.rate_limit.time.sleep", lambda _: None)
516
+
517
+ captured = []
518
+ client = Client(api_key="test-key", on_rate_limit_info=captured.append)
519
+ request_id = "test-id"
520
+
521
+ httpx_mock.add_response(
522
+ method="GET",
523
+ url=ENDPOINT + f"/{request_id}",
524
+ status_code=429,
525
+ headers={"x-ratelimit-reset": "1"},
526
+ )
527
+ httpx_mock.add_response(
528
+ method="GET",
529
+ url=ENDPOINT + f"/{request_id}",
530
+ status_code=200,
531
+ headers={
532
+ "x-ratelimit-limit": "100",
533
+ "x-ratelimit-remaining": "50",
534
+ "x-ratelimit-reset": "5",
535
+ },
536
+ content=mock_response("fixtures/api_log.json"),
537
+ )
538
+
539
+ client.api_logs.find(request_id)
540
+
541
+ assert len(captured) == 1
542
+ assert captured[0].remaining == 50
543
+
544
+ def test_callback_not_called_when_429_exhausts(
545
+ self,
546
+ httpx_mock: HTTPXMock,
547
+ monkeypatch,
548
+ ):
549
+ """Exhausted 429 retries raise without ever invoking the success callback."""
550
+ monkeypatch.setattr("lago_python_client.services.rate_limit.time.sleep", lambda _: None)
551
+
552
+ captured = []
553
+ client = Client(api_key="test-key", on_rate_limit_info=captured.append)
554
+ request_id = "test-id"
555
+
556
+ for _ in range(4):
557
+ httpx_mock.add_response(
558
+ method="GET",
559
+ url=ENDPOINT + f"/{request_id}",
560
+ status_code=429,
561
+ headers={"x-ratelimit-reset": "1"},
562
+ )
563
+
564
+ with pytest.raises(LagoRateLimitError):
565
+ client.api_logs.find(request_id)
566
+
567
+ assert captured == []
568
+
569
+
570
+ class TestLoggingRateLimitObserver:
571
+ def _info(self, *, limit, remaining):
572
+ return RateLimitInfo(
573
+ limit=limit,
574
+ remaining=remaining,
575
+ reset=10,
576
+ url="https://x",
577
+ method="GET",
578
+ )
579
+
580
+ def test_logs_above_threshold(self, caplog):
581
+ observer = LoggingRateLimitObserver(thresholds=(0.80, 0.90, 0.95))
582
+
583
+ with caplog.at_level(logging.WARNING, logger="lago_python_client.rate_limit"):
584
+ observer(self._info(limit=100, remaining=4)) # 96% used
585
+
586
+ assert any("96%" in rec.getMessage() for rec in caplog.records)
587
+
588
+ def test_silent_below_threshold(self, caplog):
589
+ observer = LoggingRateLimitObserver(thresholds=(0.80,))
590
+
591
+ with caplog.at_level(logging.WARNING, logger="lago_python_client.rate_limit"):
592
+ observer(self._info(limit=100, remaining=50)) # 50% used
593
+
594
+ assert caplog.records == []
595
+
596
+ def test_silent_when_usage_pct_unavailable(self, caplog):
597
+ observer = LoggingRateLimitObserver()
598
+
599
+ with caplog.at_level(logging.WARNING, logger="lago_python_client.rate_limit"):
600
+ observer(self._info(limit=None, remaining=None))
601
+
602
+ assert caplog.records == []
@@ -1 +0,0 @@
1
- LAGO_VERSION = "1.46.0"