|
142 | 142 | from airbyte_cdk.sources.declarative.transformations import AddFields, RemoveFields
|
143 | 143 | from airbyte_cdk.sources.declarative.transformations.add_fields import AddedFieldDefinition
|
144 | 144 | from airbyte_cdk.sources.declarative.yaml_declarative_source import YamlDeclarativeSource
|
| 145 | +from airbyte_cdk.sources.streams.call_rate import MovingWindowCallRatePolicy |
145 | 146 | from airbyte_cdk.sources.streams.concurrent.clamping import (
|
146 | 147 | ClampingEndProvider,
|
147 | 148 | DayClampingStrategy,
|
@@ -3564,3 +3565,82 @@ def test_create_async_retriever():
|
3564 | 3565 | assert isinstance(selector, RecordSelector)
|
3565 | 3566 | assert isinstance(extractor, DpathExtractor)
|
3566 | 3567 | assert extractor.field_path == ["data"]
|
| 3568 | + |
| 3569 | + |
| 3570 | +def test_api_budget(): |
| 3571 | + manifest = { |
| 3572 | + "type": "DeclarativeSource", |
| 3573 | + "api_budget": { |
| 3574 | + "type": "HTTPAPIBudget", |
| 3575 | + "ratelimit_reset_header": "X-RateLimit-Reset", |
| 3576 | + "ratelimit_remaining_header": "X-RateLimit-Remaining", |
| 3577 | + "status_codes_for_ratelimit_hit": [429, 503], |
| 3578 | + "policies": [ |
| 3579 | + { |
| 3580 | + "type": "MovingWindowCallRatePolicy", |
| 3581 | + "rates": [ |
| 3582 | + { |
| 3583 | + "type": "Rate", |
| 3584 | + "limit": 3, |
| 3585 | + "interval": "PT0.1S", # 0.1 seconds |
| 3586 | + } |
| 3587 | + ], |
| 3588 | + "matchers": [ |
| 3589 | + { |
| 3590 | + "type": "HttpRequestRegexMatcher", |
| 3591 | + "method": "GET", |
| 3592 | + "url_base": "https://api.sendgrid.com", |
| 3593 | + "url_path_pattern": "/v3/marketing/lists", |
| 3594 | + } |
| 3595 | + ], |
| 3596 | + } |
| 3597 | + ], |
| 3598 | + }, |
| 3599 | + "my_requester": { |
| 3600 | + "type": "HttpRequester", |
| 3601 | + "path": "/v3/marketing/lists", |
| 3602 | + "url_base": "https://api.sendgrid.com", |
| 3603 | + "http_method": "GET", |
| 3604 | + "authenticator": { |
| 3605 | + "type": "BasicHttpAuthenticator", |
| 3606 | + "username": "admin", |
| 3607 | + "password": "{{ config['password'] }}", |
| 3608 | + }, |
| 3609 | + }, |
| 3610 | + } |
| 3611 | + |
| 3612 | + config = { |
| 3613 | + "password": "verysecrettoken", |
| 3614 | + } |
| 3615 | + |
| 3616 | + factory = ModelToComponentFactory() |
| 3617 | + if "api_budget" in manifest: |
| 3618 | + factory.set_api_budget(manifest["api_budget"], config) |
| 3619 | + |
| 3620 | + from airbyte_cdk.sources.declarative.models.declarative_component_schema import ( |
| 3621 | + HttpRequester as HttpRequesterModel, |
| 3622 | + ) |
| 3623 | + |
| 3624 | + requester_definition = manifest["my_requester"] |
| 3625 | + assert requester_definition["type"] == "HttpRequester" |
| 3626 | + |
| 3627 | + http_requester = factory.create_component( |
| 3628 | + model_type=HttpRequesterModel, |
| 3629 | + component_definition=requester_definition, |
| 3630 | + config=config, |
| 3631 | + name="lists_stream", |
| 3632 | + decoder=None, |
| 3633 | + ) |
| 3634 | + |
| 3635 | + assert http_requester.api_budget is not None |
| 3636 | + assert http_requester.api_budget.ratelimit_reset_header == "X-RateLimit-Reset" |
| 3637 | + assert http_requester.api_budget.status_codes_for_ratelimit_hit == [429, 503] |
| 3638 | + assert len(http_requester.api_budget.policies) == 1 |
| 3639 | + |
| 3640 | + # The single policy is a MovingWindowCallRatePolicy |
| 3641 | + policy = http_requester.api_budget.policies[0] |
| 3642 | + assert isinstance(policy, MovingWindowCallRatePolicy) |
| 3643 | + assert policy._bucket.rates[0].limit == 3 |
| 3644 | + # The 0.1s from 'PT0.1S' is stored in ms by PyRateLimiter internally |
| 3645 | + # but here just check that the limit and interval exist |
| 3646 | + assert policy._bucket.rates[0].interval == 100 # 100 ms |
0 commit comments