airbyte-cdk 6.33.1.dev1__py3-none-any.whl → 6.33.2__py3-none-any.whl
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.
- airbyte_cdk/sources/declarative/auth/oauth.py +6 -1
- airbyte_cdk/sources/declarative/concurrent_declarative_source.py +15 -1
- airbyte_cdk/sources/declarative/declarative_component_schema.yaml +1 -208
- airbyte_cdk/sources/declarative/manifest_declarative_source.py +0 -4
- airbyte_cdk/sources/declarative/models/declarative_component_schema.py +0 -165
- airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +17 -141
- airbyte_cdk/sources/declarative/requesters/http_requester.py +0 -3
- airbyte_cdk/sources/streams/call_rate.py +40 -116
- airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +3 -0
- {airbyte_cdk-6.33.1.dev1.dist-info → airbyte_cdk-6.33.2.dist-info}/METADATA +1 -1
- {airbyte_cdk-6.33.1.dev1.dist-info → airbyte_cdk-6.33.2.dist-info}/RECORD +15 -15
- {airbyte_cdk-6.33.1.dev1.dist-info → airbyte_cdk-6.33.2.dist-info}/LICENSE.txt +0 -0
- {airbyte_cdk-6.33.1.dev1.dist-info → airbyte_cdk-6.33.2.dist-info}/LICENSE_SHORT +0 -0
- {airbyte_cdk-6.33.1.dev1.dist-info → airbyte_cdk-6.33.2.dist-info}/WHEEL +0 -0
- {airbyte_cdk-6.33.1.dev1.dist-info → airbyte_cdk-6.33.2.dist-info}/entry_points.txt +0 -0
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
|
5
5
|
from dataclasses import InitVar, dataclass, field
|
6
|
-
from datetime import timedelta
|
6
|
+
from datetime import datetime, timedelta
|
7
7
|
from typing import Any, List, Mapping, MutableMapping, Optional, Union
|
8
8
|
|
9
9
|
from airbyte_cdk.sources.declarative.auth.declarative_authenticator import DeclarativeAuthenticator
|
@@ -232,8 +232,13 @@ class DeclarativeOauth2Authenticator(AbstractOauth2Authenticator, DeclarativeAut
|
|
232
232
|
return self._refresh_request_headers.eval(self.config)
|
233
233
|
|
234
234
|
def get_token_expiry_date(self) -> AirbyteDateTime:
|
235
|
+
if not self._has_access_token_been_initialized():
|
236
|
+
return AirbyteDateTime.from_datetime(datetime.min)
|
235
237
|
return self._token_expiry_date # type: ignore # _token_expiry_date is an AirbyteDateTime. It is never None despite what mypy thinks
|
236
238
|
|
239
|
+
def _has_access_token_been_initialized(self) -> bool:
|
240
|
+
return self._access_token is not None
|
241
|
+
|
237
242
|
def set_token_expiry_date(self, value: Union[str, int]) -> None:
|
238
243
|
self._token_expiry_date = self._parse_token_expiration_date(value)
|
239
244
|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
|
5
5
|
import logging
|
6
|
-
from typing import Any, Generic, Iterator, List, Mapping, Optional, Tuple
|
6
|
+
from typing import Any, Generic, Iterator, List, Mapping, MutableMapping, Optional, Tuple
|
7
7
|
|
8
8
|
from airbyte_cdk.models import (
|
9
9
|
AirbyteCatalog,
|
@@ -224,6 +224,7 @@ class ConcurrentDeclarativeSource(ManifestDeclarativeSource, Generic[TState]):
|
|
224
224
|
stream_state = self._connector_state_manager.get_stream_state(
|
225
225
|
stream_name=declarative_stream.name, namespace=declarative_stream.namespace
|
226
226
|
)
|
227
|
+
stream_state = self._migrate_state(declarative_stream, stream_state)
|
227
228
|
|
228
229
|
retriever = self._get_retriever(declarative_stream, stream_state)
|
229
230
|
|
@@ -331,6 +332,8 @@ class ConcurrentDeclarativeSource(ManifestDeclarativeSource, Generic[TState]):
|
|
331
332
|
stream_state = self._connector_state_manager.get_stream_state(
|
332
333
|
stream_name=declarative_stream.name, namespace=declarative_stream.namespace
|
333
334
|
)
|
335
|
+
stream_state = self._migrate_state(declarative_stream, stream_state)
|
336
|
+
|
334
337
|
partition_router = declarative_stream.retriever.stream_slicer._partition_router
|
335
338
|
|
336
339
|
perpartition_cursor = (
|
@@ -521,3 +524,14 @@ class ConcurrentDeclarativeSource(ManifestDeclarativeSource, Generic[TState]):
|
|
521
524
|
if stream.stream.name not in concurrent_stream_names
|
522
525
|
]
|
523
526
|
)
|
527
|
+
|
528
|
+
@staticmethod
|
529
|
+
def _migrate_state(
|
530
|
+
declarative_stream: DeclarativeStream, stream_state: MutableMapping[str, Any]
|
531
|
+
) -> MutableMapping[str, Any]:
|
532
|
+
for state_migration in declarative_stream.state_migrations:
|
533
|
+
if state_migration.should_migrate(stream_state):
|
534
|
+
# The state variable is expected to be mutable but the migrate method returns an immutable mapping.
|
535
|
+
stream_state = dict(state_migration.migrate(stream_state))
|
536
|
+
|
537
|
+
return stream_state
|
@@ -40,12 +40,6 @@ properties:
|
|
40
40
|
"$ref": "#/definitions/Spec"
|
41
41
|
concurrency_level:
|
42
42
|
"$ref": "#/definitions/ConcurrencyLevel"
|
43
|
-
api_budget:
|
44
|
-
title: API Budget
|
45
|
-
description: Defines how many requests can be made to the API in a given time frame. This field accepts either a generic APIBudget or an HTTP-specific configuration (HTTPAPIBudget) to be applied across all streams.
|
46
|
-
anyOf:
|
47
|
-
- "$ref": "#/definitions/APIBudget"
|
48
|
-
- "$ref": "#/definitions/HTTPAPIBudget"
|
49
43
|
metadata:
|
50
44
|
type: object
|
51
45
|
description: For internal Airbyte use only - DO NOT modify manually. Used by consumers of declarative manifests for storing related metadata.
|
@@ -800,7 +794,7 @@ definitions:
|
|
800
794
|
description: This option is used to adjust the upper and lower boundaries of each datetime window to beginning and end of the provided target period (day, week, month)
|
801
795
|
type: object
|
802
796
|
required:
|
803
|
-
|
797
|
+
- target
|
804
798
|
properties:
|
805
799
|
target:
|
806
800
|
title: Target
|
@@ -1371,207 +1365,6 @@ definitions:
|
|
1371
1365
|
$parameters:
|
1372
1366
|
type: object
|
1373
1367
|
additional_properties: true
|
1374
|
-
APIBudget:
|
1375
|
-
title: API Budget
|
1376
|
-
description: >
|
1377
|
-
A generic API budget configuration that defines the policies (rate limiting rules)
|
1378
|
-
and the maximum number of attempts to acquire a call credit. This budget does not automatically
|
1379
|
-
update itself based on HTTP response headers.
|
1380
|
-
type: object
|
1381
|
-
required:
|
1382
|
-
- type
|
1383
|
-
- policies
|
1384
|
-
properties:
|
1385
|
-
type:
|
1386
|
-
type: string
|
1387
|
-
enum: [APIBudget]
|
1388
|
-
policies:
|
1389
|
-
title: Policies
|
1390
|
-
description: List of call rate policies that define how many calls are allowed.
|
1391
|
-
type: array
|
1392
|
-
items:
|
1393
|
-
anyOf:
|
1394
|
-
- "$ref": "#/definitions/FixedWindowCallRatePolicy"
|
1395
|
-
- "$ref": "#/definitions/MovingWindowCallRatePolicy"
|
1396
|
-
- "$ref": "#/definitions/UnlimitedCallRatePolicy"
|
1397
|
-
maximum_attempts_to_acquire:
|
1398
|
-
title: Maximum Attempts to Acquire
|
1399
|
-
description: The maximum number of attempts to acquire a call before giving up.
|
1400
|
-
type: integer
|
1401
|
-
default: 100000
|
1402
|
-
additionalProperties: true
|
1403
|
-
HTTPAPIBudget:
|
1404
|
-
title: HTTP API Budget
|
1405
|
-
description: >
|
1406
|
-
An HTTP-specific API budget that extends APIBudget by updating rate limiting information based
|
1407
|
-
on HTTP response headers. It extracts available calls and the next reset timestamp from the HTTP responses.
|
1408
|
-
type: object
|
1409
|
-
required:
|
1410
|
-
- type
|
1411
|
-
- policies
|
1412
|
-
properties:
|
1413
|
-
type:
|
1414
|
-
type: string
|
1415
|
-
enum: [HTTPAPIBudget]
|
1416
|
-
policies:
|
1417
|
-
title: Policies
|
1418
|
-
description: List of call rate policies that define how many calls are allowed.
|
1419
|
-
type: array
|
1420
|
-
items:
|
1421
|
-
anyOf:
|
1422
|
-
- "$ref": "#/definitions/FixedWindowCallRatePolicy"
|
1423
|
-
- "$ref": "#/definitions/MovingWindowCallRatePolicy"
|
1424
|
-
- "$ref": "#/definitions/UnlimitedCallRatePolicy"
|
1425
|
-
ratelimit_reset_header:
|
1426
|
-
title: Rate Limit Reset Header
|
1427
|
-
description: The HTTP response header name that indicates when the rate limit resets.
|
1428
|
-
type: string
|
1429
|
-
default: "ratelimit-reset"
|
1430
|
-
ratelimit_remaining_header:
|
1431
|
-
title: Rate Limit Remaining Header
|
1432
|
-
description: The HTTP response header name that indicates the number of remaining allowed calls.
|
1433
|
-
type: string
|
1434
|
-
default: "ratelimit-remaining"
|
1435
|
-
status_codes_for_ratelimit_hit:
|
1436
|
-
title: Status Codes for Rate Limit Hit
|
1437
|
-
description: List of HTTP status codes that indicate a rate limit has been hit.
|
1438
|
-
type: array
|
1439
|
-
items:
|
1440
|
-
type: integer
|
1441
|
-
default: [429]
|
1442
|
-
maximum_attempts_to_acquire:
|
1443
|
-
title: Maximum Attempts to Acquire
|
1444
|
-
description: The maximum number of attempts to acquire a call before giving up.
|
1445
|
-
type: integer
|
1446
|
-
default: 100000
|
1447
|
-
additionalProperties: true
|
1448
|
-
FixedWindowCallRatePolicy:
|
1449
|
-
title: Fixed Window Call Rate Policy
|
1450
|
-
description: A policy that allows a fixed number of calls within a specific time window.
|
1451
|
-
type: object
|
1452
|
-
required:
|
1453
|
-
- type
|
1454
|
-
- next_reset_ts
|
1455
|
-
- period
|
1456
|
-
- call_limit
|
1457
|
-
- matchers
|
1458
|
-
properties:
|
1459
|
-
type:
|
1460
|
-
type: string
|
1461
|
-
enum: [FixedWindowCallRatePolicy]
|
1462
|
-
next_reset_ts:
|
1463
|
-
title: Next Reset Timestamp
|
1464
|
-
description: The timestamp when the rate limit will reset.
|
1465
|
-
type: string
|
1466
|
-
format: date-time
|
1467
|
-
period:
|
1468
|
-
title: Period
|
1469
|
-
description: The time interval for the rate limit window.
|
1470
|
-
type: string
|
1471
|
-
format: duration
|
1472
|
-
call_limit:
|
1473
|
-
title: Call Limit
|
1474
|
-
description: The maximum number of calls allowed within the period.
|
1475
|
-
type: integer
|
1476
|
-
matchers:
|
1477
|
-
title: Matchers
|
1478
|
-
description: List of matchers that define which requests this policy applies to.
|
1479
|
-
type: array
|
1480
|
-
items:
|
1481
|
-
"$ref": "#/definitions/HttpRequestRegexMatcher"
|
1482
|
-
additionalProperties: true
|
1483
|
-
MovingWindowCallRatePolicy:
|
1484
|
-
title: Moving Window Call Rate Policy
|
1485
|
-
description: A policy that allows a fixed number of calls within a moving time window.
|
1486
|
-
type: object
|
1487
|
-
required:
|
1488
|
-
- type
|
1489
|
-
- rates
|
1490
|
-
- matchers
|
1491
|
-
properties:
|
1492
|
-
type:
|
1493
|
-
type: string
|
1494
|
-
enum: [MovingWindowCallRatePolicy]
|
1495
|
-
rates:
|
1496
|
-
title: Rates
|
1497
|
-
description: List of rates that define the call limits for different time intervals.
|
1498
|
-
type: array
|
1499
|
-
items:
|
1500
|
-
"$ref": "#/definitions/Rate"
|
1501
|
-
matchers:
|
1502
|
-
title: Matchers
|
1503
|
-
description: List of matchers that define which requests this policy applies to.
|
1504
|
-
type: array
|
1505
|
-
items:
|
1506
|
-
"$ref": "#/definitions/HttpRequestRegexMatcher"
|
1507
|
-
additionalProperties: true
|
1508
|
-
UnlimitedCallRatePolicy:
|
1509
|
-
title: Unlimited Call Rate Policy
|
1510
|
-
description: A policy that allows unlimited calls for specific requests.
|
1511
|
-
type: object
|
1512
|
-
required:
|
1513
|
-
- type
|
1514
|
-
- matchers
|
1515
|
-
properties:
|
1516
|
-
type:
|
1517
|
-
type: string
|
1518
|
-
enum: [UnlimitedCallRatePolicy]
|
1519
|
-
matchers:
|
1520
|
-
title: Matchers
|
1521
|
-
description: List of matchers that define which requests this policy applies to.
|
1522
|
-
type: array
|
1523
|
-
items:
|
1524
|
-
"$ref": "#/definitions/HttpRequestRegexMatcher"
|
1525
|
-
additionalProperties: true
|
1526
|
-
Rate:
|
1527
|
-
title: Rate
|
1528
|
-
description: Defines a rate limit with a specific number of calls allowed within a time interval.
|
1529
|
-
type: object
|
1530
|
-
required:
|
1531
|
-
- limit
|
1532
|
-
- interval
|
1533
|
-
properties:
|
1534
|
-
limit:
|
1535
|
-
title: Limit
|
1536
|
-
description: The maximum number of calls allowed within the interval.
|
1537
|
-
type: integer
|
1538
|
-
interval:
|
1539
|
-
title: Interval
|
1540
|
-
description: The time interval for the rate limit.
|
1541
|
-
type: string
|
1542
|
-
format: duration
|
1543
|
-
additionalProperties: true
|
1544
|
-
HttpRequestRegexMatcher:
|
1545
|
-
title: HTTP Request Matcher
|
1546
|
-
description: >
|
1547
|
-
Matches HTTP requests based on method, base URL, URL path pattern, query parameters, and headers.
|
1548
|
-
Use `url_base` to specify the scheme and host (without trailing slash) and
|
1549
|
-
`url_path_pattern` to apply a regex to the request path.
|
1550
|
-
type: object
|
1551
|
-
properties:
|
1552
|
-
method:
|
1553
|
-
title: Method
|
1554
|
-
description: The HTTP method to match (e.g., GET, POST).
|
1555
|
-
type: string
|
1556
|
-
url_base:
|
1557
|
-
title: URL Base
|
1558
|
-
description: The base URL (scheme and host, e.g. "https://api.example.com") to match.
|
1559
|
-
type: string
|
1560
|
-
url_path_pattern:
|
1561
|
-
title: URL Path Pattern
|
1562
|
-
description: A regular expression pattern to match the URL path.
|
1563
|
-
type: string
|
1564
|
-
params:
|
1565
|
-
title: Parameters
|
1566
|
-
description: The query parameters to match.
|
1567
|
-
type: object
|
1568
|
-
additionalProperties: true
|
1569
|
-
headers:
|
1570
|
-
title: Headers
|
1571
|
-
description: The headers to match.
|
1572
|
-
type: object
|
1573
|
-
additionalProperties: true
|
1574
|
-
additionalProperties: true
|
1575
1368
|
DefaultErrorHandler:
|
1576
1369
|
title: Default Error Handler
|
1577
1370
|
description: Component defining how to handle errors. Default behavior includes only retrying server errors (HTTP 5XX) and too many requests (HTTP 429) with an exponential backoff.
|
@@ -137,10 +137,6 @@ class ManifestDeclarativeSource(DeclarativeSource):
|
|
137
137
|
self._source_config, config
|
138
138
|
)
|
139
139
|
|
140
|
-
api_budget_model = self._source_config.get("api_budget")
|
141
|
-
if api_budget_model:
|
142
|
-
self._constructor.set_api_budget(api_budget_model, config)
|
143
|
-
|
144
140
|
source_streams = [
|
145
141
|
self._constructor.create_component(
|
146
142
|
DeclarativeStreamModel,
|
@@ -3,7 +3,6 @@
|
|
3
3
|
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
|
-
from datetime import datetime, timedelta
|
7
6
|
from enum import Enum
|
8
7
|
from typing import Any, Dict, List, Literal, Optional, Union
|
9
8
|
|
@@ -643,45 +642,6 @@ class OAuthAuthenticator(BaseModel):
|
|
643
642
|
parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
|
644
643
|
|
645
644
|
|
646
|
-
class Rate(BaseModel):
|
647
|
-
class Config:
|
648
|
-
extra = Extra.allow
|
649
|
-
|
650
|
-
limit: int = Field(
|
651
|
-
...,
|
652
|
-
description="The maximum number of calls allowed within the interval.",
|
653
|
-
title="Limit",
|
654
|
-
)
|
655
|
-
interval: timedelta = Field(
|
656
|
-
..., description="The time interval for the rate limit.", title="Interval"
|
657
|
-
)
|
658
|
-
|
659
|
-
|
660
|
-
class HttpRequestRegexMatcher(BaseModel):
|
661
|
-
class Config:
|
662
|
-
extra = Extra.allow
|
663
|
-
|
664
|
-
method: Optional[str] = Field(
|
665
|
-
None, description="The HTTP method to match (e.g., GET, POST).", title="Method"
|
666
|
-
)
|
667
|
-
url_base: Optional[str] = Field(
|
668
|
-
None,
|
669
|
-
description='The base URL (scheme and host, e.g. "https://api.example.com") to match.',
|
670
|
-
title="URL Base",
|
671
|
-
)
|
672
|
-
url_path_pattern: Optional[str] = Field(
|
673
|
-
None,
|
674
|
-
description="A regular expression pattern to match the URL path.",
|
675
|
-
title="URL Path Pattern",
|
676
|
-
)
|
677
|
-
params: Optional[Dict[str, Any]] = Field(
|
678
|
-
None, description="The query parameters to match.", title="Parameters"
|
679
|
-
)
|
680
|
-
headers: Optional[Dict[str, Any]] = Field(
|
681
|
-
None, description="The headers to match.", title="Headers"
|
682
|
-
)
|
683
|
-
|
684
|
-
|
685
645
|
class DpathExtractor(BaseModel):
|
686
646
|
type: Literal["DpathExtractor"]
|
687
647
|
field_path: List[str] = Field(
|
@@ -1624,60 +1584,6 @@ class DatetimeBasedCursor(BaseModel):
|
|
1624
1584
|
parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
|
1625
1585
|
|
1626
1586
|
|
1627
|
-
class FixedWindowCallRatePolicy(BaseModel):
|
1628
|
-
class Config:
|
1629
|
-
extra = Extra.allow
|
1630
|
-
|
1631
|
-
type: Literal["FixedWindowCallRatePolicy"]
|
1632
|
-
next_reset_ts: datetime = Field(
|
1633
|
-
...,
|
1634
|
-
description="The timestamp when the rate limit will reset.",
|
1635
|
-
title="Next Reset Timestamp",
|
1636
|
-
)
|
1637
|
-
period: timedelta = Field(
|
1638
|
-
..., description="The time interval for the rate limit window.", title="Period"
|
1639
|
-
)
|
1640
|
-
call_limit: int = Field(
|
1641
|
-
...,
|
1642
|
-
description="The maximum number of calls allowed within the period.",
|
1643
|
-
title="Call Limit",
|
1644
|
-
)
|
1645
|
-
matchers: List[HttpRequestRegexMatcher] = Field(
|
1646
|
-
...,
|
1647
|
-
description="List of matchers that define which requests this policy applies to.",
|
1648
|
-
title="Matchers",
|
1649
|
-
)
|
1650
|
-
|
1651
|
-
|
1652
|
-
class MovingWindowCallRatePolicy(BaseModel):
|
1653
|
-
class Config:
|
1654
|
-
extra = Extra.allow
|
1655
|
-
|
1656
|
-
type: Literal["MovingWindowCallRatePolicy"]
|
1657
|
-
rates: List[Rate] = Field(
|
1658
|
-
...,
|
1659
|
-
description="List of rates that define the call limits for different time intervals.",
|
1660
|
-
title="Rates",
|
1661
|
-
)
|
1662
|
-
matchers: List[HttpRequestRegexMatcher] = Field(
|
1663
|
-
...,
|
1664
|
-
description="List of matchers that define which requests this policy applies to.",
|
1665
|
-
title="Matchers",
|
1666
|
-
)
|
1667
|
-
|
1668
|
-
|
1669
|
-
class UnlimitedCallRatePolicy(BaseModel):
|
1670
|
-
class Config:
|
1671
|
-
extra = Extra.allow
|
1672
|
-
|
1673
|
-
type: Literal["UnlimitedCallRatePolicy"]
|
1674
|
-
matchers: List[HttpRequestRegexMatcher] = Field(
|
1675
|
-
...,
|
1676
|
-
description="List of matchers that define which requests this policy applies to.",
|
1677
|
-
title="Matchers",
|
1678
|
-
)
|
1679
|
-
|
1680
|
-
|
1681
1587
|
class DefaultErrorHandler(BaseModel):
|
1682
1588
|
type: Literal["DefaultErrorHandler"]
|
1683
1589
|
backoff_strategies: Optional[
|
@@ -1809,67 +1715,6 @@ class CompositeErrorHandler(BaseModel):
|
|
1809
1715
|
parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
|
1810
1716
|
|
1811
1717
|
|
1812
|
-
class APIBudget(BaseModel):
|
1813
|
-
class Config:
|
1814
|
-
extra = Extra.allow
|
1815
|
-
|
1816
|
-
type: Literal["APIBudget"]
|
1817
|
-
policies: List[
|
1818
|
-
Union[
|
1819
|
-
FixedWindowCallRatePolicy,
|
1820
|
-
MovingWindowCallRatePolicy,
|
1821
|
-
UnlimitedCallRatePolicy,
|
1822
|
-
]
|
1823
|
-
] = Field(
|
1824
|
-
...,
|
1825
|
-
description="List of call rate policies that define how many calls are allowed.",
|
1826
|
-
title="Policies",
|
1827
|
-
)
|
1828
|
-
maximum_attempts_to_acquire: Optional[int] = Field(
|
1829
|
-
100000,
|
1830
|
-
description="The maximum number of attempts to acquire a call before giving up.",
|
1831
|
-
title="Maximum Attempts to Acquire",
|
1832
|
-
)
|
1833
|
-
|
1834
|
-
|
1835
|
-
class HTTPAPIBudget(BaseModel):
|
1836
|
-
class Config:
|
1837
|
-
extra = Extra.allow
|
1838
|
-
|
1839
|
-
type: Literal["HTTPAPIBudget"]
|
1840
|
-
policies: List[
|
1841
|
-
Union[
|
1842
|
-
FixedWindowCallRatePolicy,
|
1843
|
-
MovingWindowCallRatePolicy,
|
1844
|
-
UnlimitedCallRatePolicy,
|
1845
|
-
]
|
1846
|
-
] = Field(
|
1847
|
-
...,
|
1848
|
-
description="List of call rate policies that define how many calls are allowed.",
|
1849
|
-
title="Policies",
|
1850
|
-
)
|
1851
|
-
ratelimit_reset_header: Optional[str] = Field(
|
1852
|
-
"ratelimit-reset",
|
1853
|
-
description="The HTTP response header name that indicates when the rate limit resets.",
|
1854
|
-
title="Rate Limit Reset Header",
|
1855
|
-
)
|
1856
|
-
ratelimit_remaining_header: Optional[str] = Field(
|
1857
|
-
"ratelimit-remaining",
|
1858
|
-
description="The HTTP response header name that indicates the number of remaining allowed calls.",
|
1859
|
-
title="Rate Limit Remaining Header",
|
1860
|
-
)
|
1861
|
-
status_codes_for_ratelimit_hit: Optional[List[int]] = Field(
|
1862
|
-
[429],
|
1863
|
-
description="List of HTTP status codes that indicate a rate limit has been hit.",
|
1864
|
-
title="Status Codes for Rate Limit Hit",
|
1865
|
-
)
|
1866
|
-
maximum_attempts_to_acquire: Optional[int] = Field(
|
1867
|
-
100000,
|
1868
|
-
description="The maximum number of attempts to acquire a call before giving up.",
|
1869
|
-
title="Maximum Attempts to Acquire",
|
1870
|
-
)
|
1871
|
-
|
1872
|
-
|
1873
1718
|
class ZipfileDecoder(BaseModel):
|
1874
1719
|
class Config:
|
1875
1720
|
extra = Extra.allow
|
@@ -1903,11 +1748,6 @@ class DeclarativeSource1(BaseModel):
|
|
1903
1748
|
definitions: Optional[Dict[str, Any]] = None
|
1904
1749
|
spec: Optional[Spec] = None
|
1905
1750
|
concurrency_level: Optional[ConcurrencyLevel] = None
|
1906
|
-
api_budget: Optional[Union[APIBudget, HTTPAPIBudget]] = Field(
|
1907
|
-
None,
|
1908
|
-
description="Defines how many requests can be made to the API in a given time frame. This field accepts either a generic APIBudget or an HTTP-specific configuration (HTTPAPIBudget) to be applied across all streams.",
|
1909
|
-
title="API Budget",
|
1910
|
-
)
|
1911
1751
|
metadata: Optional[Dict[str, Any]] = Field(
|
1912
1752
|
None,
|
1913
1753
|
description="For internal Airbyte use only - DO NOT modify manually. Used by consumers of declarative manifests for storing related metadata.",
|
@@ -1934,11 +1774,6 @@ class DeclarativeSource2(BaseModel):
|
|
1934
1774
|
definitions: Optional[Dict[str, Any]] = None
|
1935
1775
|
spec: Optional[Spec] = None
|
1936
1776
|
concurrency_level: Optional[ConcurrencyLevel] = None
|
1937
|
-
api_budget: Optional[Union[APIBudget, HTTPAPIBudget]] = Field(
|
1938
|
-
None,
|
1939
|
-
description="Defines how many requests can be made to the API in a given time frame. This field accepts either a generic APIBudget or an HTTP-specific configuration (HTTPAPIBudget) to be applied across all streams.",
|
1940
|
-
title="API Budget",
|
1941
|
-
)
|
1942
1777
|
metadata: Optional[Dict[str, Any]] = Field(
|
1943
1778
|
None,
|
1944
1779
|
description="For internal Airbyte use only - DO NOT modify manually. Used by consumers of declarative manifests for storing related metadata.",
|
@@ -112,9 +112,6 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
|
|
112
112
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
113
113
|
AddFields as AddFieldsModel,
|
114
114
|
)
|
115
|
-
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
116
|
-
APIBudget as APIBudgetModel,
|
117
|
-
)
|
118
115
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
119
116
|
ApiKeyAuthenticator as ApiKeyAuthenticatorModel,
|
120
117
|
)
|
@@ -229,9 +226,6 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
|
|
229
226
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
230
227
|
ExponentialBackoffStrategy as ExponentialBackoffStrategyModel,
|
231
228
|
)
|
232
|
-
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
233
|
-
FixedWindowCallRatePolicy as FixedWindowCallRatePolicyModel,
|
234
|
-
)
|
235
229
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
236
230
|
FlattenFields as FlattenFieldsModel,
|
237
231
|
)
|
@@ -241,18 +235,12 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
|
|
241
235
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
242
236
|
GzipParser as GzipParserModel,
|
243
237
|
)
|
244
|
-
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
245
|
-
HTTPAPIBudget as HTTPAPIBudgetModel,
|
246
|
-
)
|
247
238
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
248
239
|
HttpComponentsResolver as HttpComponentsResolverModel,
|
249
240
|
)
|
250
241
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
251
242
|
HttpRequester as HttpRequesterModel,
|
252
243
|
)
|
253
|
-
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
254
|
-
HttpRequestRegexMatcher as HttpRequestRegexMatcherModel,
|
255
|
-
)
|
256
244
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
257
245
|
HttpResponseFilter as HttpResponseFilterModel,
|
258
246
|
)
|
@@ -307,9 +295,6 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
|
|
307
295
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
308
296
|
MinMaxDatetime as MinMaxDatetimeModel,
|
309
297
|
)
|
310
|
-
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
311
|
-
MovingWindowCallRatePolicy as MovingWindowCallRatePolicyModel,
|
312
|
-
)
|
313
298
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
314
299
|
NoAuth as NoAuthModel,
|
315
300
|
)
|
@@ -328,9 +313,6 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
|
|
328
313
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
329
314
|
ParentStreamConfig as ParentStreamConfigModel,
|
330
315
|
)
|
331
|
-
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
332
|
-
Rate as RateModel,
|
333
|
-
)
|
334
316
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
335
317
|
RecordFilter as RecordFilterModel,
|
336
318
|
)
|
@@ -374,9 +356,6 @@ from airbyte_cdk.sources.declarative.models.declarative_component_schema import
|
|
374
356
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
375
357
|
TypesMap as TypesMapModel,
|
376
358
|
)
|
377
|
-
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
378
|
-
UnlimitedCallRatePolicy as UnlimitedCallRatePolicyModel,
|
379
|
-
)
|
380
359
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import ValueType
|
381
360
|
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
|
382
361
|
WaitTimeFromHeader as WaitTimeFromHeaderModel,
|
@@ -490,15 +469,6 @@ from airbyte_cdk.sources.message import (
|
|
490
469
|
MessageRepository,
|
491
470
|
NoopMessageRepository,
|
492
471
|
)
|
493
|
-
from airbyte_cdk.sources.streams.call_rate import (
|
494
|
-
APIBudget,
|
495
|
-
FixedWindowCallRatePolicy,
|
496
|
-
HttpAPIBudget,
|
497
|
-
HttpRequestRegexMatcher,
|
498
|
-
MovingWindowCallRatePolicy,
|
499
|
-
Rate,
|
500
|
-
UnlimitedCallRatePolicy,
|
501
|
-
)
|
502
472
|
from airbyte_cdk.sources.streams.concurrent.clamping import (
|
503
473
|
ClampingEndProvider,
|
504
474
|
ClampingStrategy,
|
@@ -550,7 +520,6 @@ class ModelToComponentFactory:
|
|
550
520
|
self._evaluate_log_level(emit_connector_builder_messages)
|
551
521
|
)
|
552
522
|
self._connector_state_manager = connector_state_manager or ConnectorStateManager()
|
553
|
-
self._api_budget: Optional[Union[APIBudget, HttpAPIBudget]] = None
|
554
523
|
|
555
524
|
def _init_mappings(self) -> None:
|
556
525
|
self.PYDANTIC_MODEL_TO_CONSTRUCTOR: Mapping[Type[BaseModel], Callable[..., Any]] = {
|
@@ -638,13 +607,6 @@ class ModelToComponentFactory:
|
|
638
607
|
StreamConfigModel: self.create_stream_config,
|
639
608
|
ComponentMappingDefinitionModel: self.create_components_mapping_definition,
|
640
609
|
ZipfileDecoderModel: self.create_zipfile_decoder,
|
641
|
-
APIBudgetModel: self.create_api_budget,
|
642
|
-
HTTPAPIBudgetModel: self.create_http_api_budget,
|
643
|
-
FixedWindowCallRatePolicyModel: self.create_fixed_window_call_rate_policy,
|
644
|
-
MovingWindowCallRatePolicyModel: self.create_moving_window_call_rate_policy,
|
645
|
-
UnlimitedCallRatePolicyModel: self.create_unlimited_call_rate_policy,
|
646
|
-
RateModel: self.create_rate,
|
647
|
-
HttpRequestRegexMatcherModel: self.create_http_request_matcher,
|
648
610
|
}
|
649
611
|
|
650
612
|
# Needed for the case where we need to perform a second parse on the fields of a custom component
|
@@ -972,6 +934,17 @@ class ModelToComponentFactory:
|
|
972
934
|
parameters={},
|
973
935
|
)
|
974
936
|
|
937
|
+
@staticmethod
|
938
|
+
def apply_stream_state_migrations(
|
939
|
+
stream_state_migrations: List[Any] | None, stream_state: MutableMapping[str, Any]
|
940
|
+
) -> MutableMapping[str, Any]:
|
941
|
+
if stream_state_migrations:
|
942
|
+
for state_migration in stream_state_migrations:
|
943
|
+
if state_migration.should_migrate(stream_state):
|
944
|
+
# The state variable is expected to be mutable but the migrate method returns an immutable mapping.
|
945
|
+
stream_state = dict(state_migration.migrate(stream_state))
|
946
|
+
return stream_state
|
947
|
+
|
975
948
|
def create_concurrent_cursor_from_datetime_based_cursor(
|
976
949
|
self,
|
977
950
|
model_type: Type[BaseModel],
|
@@ -981,6 +954,7 @@ class ModelToComponentFactory:
|
|
981
954
|
config: Config,
|
982
955
|
message_repository: Optional[MessageRepository] = None,
|
983
956
|
runtime_lookback_window: Optional[datetime.timedelta] = None,
|
957
|
+
stream_state_migrations: Optional[List[Any]] = None,
|
984
958
|
**kwargs: Any,
|
985
959
|
) -> ConcurrentCursor:
|
986
960
|
# Per-partition incremental streams can dynamically create child cursors which will pass their current
|
@@ -991,6 +965,7 @@ class ModelToComponentFactory:
|
|
991
965
|
if "stream_state" not in kwargs
|
992
966
|
else kwargs["stream_state"]
|
993
967
|
)
|
968
|
+
stream_state = self.apply_stream_state_migrations(stream_state_migrations, stream_state)
|
994
969
|
|
995
970
|
component_type = component_definition.get("type")
|
996
971
|
if component_definition.get("type") != model_type.__name__:
|
@@ -1226,6 +1201,7 @@ class ModelToComponentFactory:
|
|
1226
1201
|
config: Config,
|
1227
1202
|
stream_state: MutableMapping[str, Any],
|
1228
1203
|
partition_router: PartitionRouter,
|
1204
|
+
stream_state_migrations: Optional[List[Any]] = None,
|
1229
1205
|
**kwargs: Any,
|
1230
1206
|
) -> ConcurrentPerPartitionCursor:
|
1231
1207
|
component_type = component_definition.get("type")
|
@@ -1274,8 +1250,10 @@ class ModelToComponentFactory:
|
|
1274
1250
|
stream_namespace=stream_namespace,
|
1275
1251
|
config=config,
|
1276
1252
|
message_repository=NoopMessageRepository(),
|
1253
|
+
stream_state_migrations=stream_state_migrations,
|
1277
1254
|
)
|
1278
1255
|
)
|
1256
|
+
stream_state = self.apply_stream_state_migrations(stream_state_migrations, stream_state)
|
1279
1257
|
|
1280
1258
|
# Return the concurrent cursor and state converter
|
1281
1259
|
return ConcurrentPerPartitionCursor(
|
@@ -1784,6 +1762,7 @@ class ModelToComponentFactory:
|
|
1784
1762
|
stream_name=model.name or "",
|
1785
1763
|
stream_namespace=None,
|
1786
1764
|
config=config or {},
|
1765
|
+
stream_state_migrations=model.state_migrations,
|
1787
1766
|
)
|
1788
1767
|
return (
|
1789
1768
|
self._create_component_from_model(model=model.incremental_sync, config=config)
|
@@ -1940,8 +1919,6 @@ class ModelToComponentFactory:
|
|
1940
1919
|
)
|
1941
1920
|
)
|
1942
1921
|
|
1943
|
-
api_budget = self._api_budget
|
1944
|
-
|
1945
1922
|
request_options_provider = InterpolatedRequestOptionsProvider(
|
1946
1923
|
request_body_data=model.request_body_data,
|
1947
1924
|
request_body_json=model.request_body_json,
|
@@ -1962,7 +1939,6 @@ class ModelToComponentFactory:
|
|
1962
1939
|
path=model.path,
|
1963
1940
|
authenticator=authenticator,
|
1964
1941
|
error_handler=error_handler,
|
1965
|
-
api_budget=api_budget,
|
1966
1942
|
http_method=HttpMethod[model.http_method.value],
|
1967
1943
|
request_options_provider=request_options_provider,
|
1968
1944
|
config=config,
|
@@ -2964,103 +2940,3 @@ class ModelToComponentFactory:
|
|
2964
2940
|
return isinstance(parser.inner_parser, JsonParser)
|
2965
2941
|
else:
|
2966
2942
|
return False
|
2967
|
-
|
2968
|
-
def create_api_budget(self, model: APIBudgetModel, config: Config, **kwargs: Any) -> APIBudget:
|
2969
|
-
policies = [
|
2970
|
-
self._create_component_from_model(model=policy, config=config)
|
2971
|
-
for policy in model.policies
|
2972
|
-
]
|
2973
|
-
|
2974
|
-
return APIBudget(
|
2975
|
-
policies=policies,
|
2976
|
-
maximum_attempts_to_acquire=model.maximum_attempts_to_acquire or 100000,
|
2977
|
-
)
|
2978
|
-
|
2979
|
-
def create_http_api_budget(
|
2980
|
-
self, model: HTTPAPIBudgetModel, config: Config, **kwargs: Any
|
2981
|
-
) -> HttpAPIBudget:
|
2982
|
-
policies = [
|
2983
|
-
self._create_component_from_model(model=policy, config=config)
|
2984
|
-
for policy in model.policies
|
2985
|
-
]
|
2986
|
-
|
2987
|
-
return HttpAPIBudget(
|
2988
|
-
policies=policies,
|
2989
|
-
maximum_attempts_to_acquire=model.maximum_attempts_to_acquire or 100000,
|
2990
|
-
ratelimit_reset_header=model.ratelimit_reset_header or "ratelimit-reset",
|
2991
|
-
ratelimit_remaining_header=model.ratelimit_remaining_header or "ratelimit-remaining",
|
2992
|
-
status_codes_for_ratelimit_hit=model.status_codes_for_ratelimit_hit or (429,),
|
2993
|
-
)
|
2994
|
-
|
2995
|
-
def create_fixed_window_call_rate_policy(
|
2996
|
-
self, model: FixedWindowCallRatePolicyModel, config: Config, **kwargs: Any
|
2997
|
-
) -> FixedWindowCallRatePolicy:
|
2998
|
-
matchers = [
|
2999
|
-
self._create_component_from_model(model=matcher, config=config)
|
3000
|
-
for matcher in model.matchers
|
3001
|
-
]
|
3002
|
-
return FixedWindowCallRatePolicy(
|
3003
|
-
next_reset_ts=model.next_reset_ts,
|
3004
|
-
period=model.period,
|
3005
|
-
call_limit=model.call_limit,
|
3006
|
-
matchers=matchers,
|
3007
|
-
)
|
3008
|
-
|
3009
|
-
def create_moving_window_call_rate_policy(
|
3010
|
-
self, model: MovingWindowCallRatePolicyModel, config: Config, **kwargs: Any
|
3011
|
-
) -> MovingWindowCallRatePolicy:
|
3012
|
-
rates = [
|
3013
|
-
self._create_component_from_model(model=rate, config=config) for rate in model.rates
|
3014
|
-
]
|
3015
|
-
matchers = [
|
3016
|
-
self._create_component_from_model(model=matcher, config=config)
|
3017
|
-
for matcher in model.matchers
|
3018
|
-
]
|
3019
|
-
return MovingWindowCallRatePolicy(
|
3020
|
-
rates=rates,
|
3021
|
-
matchers=matchers,
|
3022
|
-
)
|
3023
|
-
|
3024
|
-
def create_unlimited_call_rate_policy(
|
3025
|
-
self, model: UnlimitedCallRatePolicyModel, config: Config, **kwargs: Any
|
3026
|
-
) -> UnlimitedCallRatePolicy:
|
3027
|
-
matchers = [
|
3028
|
-
self._create_component_from_model(model=matcher, config=config)
|
3029
|
-
for matcher in model.matchers
|
3030
|
-
]
|
3031
|
-
|
3032
|
-
return UnlimitedCallRatePolicy(
|
3033
|
-
matchers=matchers,
|
3034
|
-
)
|
3035
|
-
|
3036
|
-
def create_rate(self, model: RateModel, config: Config, **kwargs: Any) -> Rate:
|
3037
|
-
return Rate(
|
3038
|
-
limit=model.limit,
|
3039
|
-
interval=model.interval,
|
3040
|
-
)
|
3041
|
-
|
3042
|
-
def create_http_request_matcher(
|
3043
|
-
self, model: HttpRequestRegexMatcherModel, config: Config, **kwargs: Any
|
3044
|
-
) -> HttpRequestRegexMatcher:
|
3045
|
-
return HttpRequestRegexMatcher(
|
3046
|
-
method=model.method,
|
3047
|
-
url_base=model.url_base,
|
3048
|
-
url_path_pattern=model.url_path_pattern,
|
3049
|
-
params=model.params,
|
3050
|
-
headers=model.headers,
|
3051
|
-
)
|
3052
|
-
|
3053
|
-
def set_api_budget(self, component_definition: ComponentDefinition, config: Config) -> None:
|
3054
|
-
model_str = component_definition.get("type")
|
3055
|
-
if model_str == "APIBudget":
|
3056
|
-
# Annotate model_type as a type that is a subclass of BaseModel
|
3057
|
-
model_type: Union[Type[APIBudgetModel], Type[HTTPAPIBudgetModel]] = APIBudgetModel
|
3058
|
-
elif model_str == "HTTPAPIBudget":
|
3059
|
-
model_type = HTTPAPIBudgetModel
|
3060
|
-
else:
|
3061
|
-
raise ValueError(f"Unknown API Budget type: {model_str}")
|
3062
|
-
|
3063
|
-
# create_component expects a type[BaseModel] and returns an instance of that model.
|
3064
|
-
self._api_budget = self.create_component(
|
3065
|
-
model_type=model_type, component_definition=component_definition, config=config
|
3066
|
-
)
|
@@ -22,7 +22,6 @@ from airbyte_cdk.sources.declarative.requesters.request_options.interpolated_req
|
|
22
22
|
)
|
23
23
|
from airbyte_cdk.sources.declarative.requesters.requester import HttpMethod, Requester
|
24
24
|
from airbyte_cdk.sources.message import MessageRepository, NoopMessageRepository
|
25
|
-
from airbyte_cdk.sources.streams.call_rate import APIBudget
|
26
25
|
from airbyte_cdk.sources.streams.http import HttpClient
|
27
26
|
from airbyte_cdk.sources.streams.http.error_handlers import ErrorHandler
|
28
27
|
from airbyte_cdk.sources.types import Config, StreamSlice, StreamState
|
@@ -56,7 +55,6 @@ class HttpRequester(Requester):
|
|
56
55
|
http_method: Union[str, HttpMethod] = HttpMethod.GET
|
57
56
|
request_options_provider: Optional[InterpolatedRequestOptionsProvider] = None
|
58
57
|
error_handler: Optional[ErrorHandler] = None
|
59
|
-
api_budget: Optional[APIBudget] = None
|
60
58
|
disable_retries: bool = False
|
61
59
|
message_repository: MessageRepository = NoopMessageRepository()
|
62
60
|
use_cache: bool = False
|
@@ -93,7 +91,6 @@ class HttpRequester(Requester):
|
|
93
91
|
name=self.name,
|
94
92
|
logger=self.logger,
|
95
93
|
error_handler=self.error_handler,
|
96
|
-
api_budget=self.api_budget,
|
97
94
|
authenticator=self._authenticator,
|
98
95
|
use_cache=self.use_cache,
|
99
96
|
backoff_strategy=backoff_strategies,
|
@@ -6,12 +6,10 @@ import abc
|
|
6
6
|
import dataclasses
|
7
7
|
import datetime
|
8
8
|
import logging
|
9
|
-
import re
|
10
9
|
import time
|
11
|
-
from dataclasses import InitVar, dataclass, field
|
12
10
|
from datetime import timedelta
|
13
11
|
from threading import RLock
|
14
|
-
from typing import TYPE_CHECKING, Any, Mapping, Optional
|
12
|
+
from typing import TYPE_CHECKING, Any, Mapping, Optional
|
15
13
|
from urllib import parse
|
16
14
|
|
17
15
|
import requests
|
@@ -162,100 +160,6 @@ class HttpRequestMatcher(RequestMatcher):
|
|
162
160
|
return True
|
163
161
|
|
164
162
|
|
165
|
-
class HttpRequestRegexMatcher(RequestMatcher):
|
166
|
-
"""
|
167
|
-
Extended RequestMatcher for HTTP requests that supports matching on:
|
168
|
-
- HTTP method (case-insensitive)
|
169
|
-
- URL base (scheme + netloc) optionally
|
170
|
-
- URL path pattern (a regex applied to the path portion of the URL)
|
171
|
-
- Query parameters (must be present)
|
172
|
-
- Headers (header names compared case-insensitively)
|
173
|
-
"""
|
174
|
-
|
175
|
-
def __init__(
|
176
|
-
self,
|
177
|
-
method: Optional[str] = None,
|
178
|
-
url_base: Optional[str] = None,
|
179
|
-
url_path_pattern: Optional[str] = None,
|
180
|
-
params: Optional[Mapping[str, Any]] = None,
|
181
|
-
headers: Optional[Mapping[str, Any]] = None,
|
182
|
-
):
|
183
|
-
"""
|
184
|
-
:param method: HTTP method (e.g. "GET", "POST"); compared case-insensitively.
|
185
|
-
:param url_base: Base URL (scheme://host) that must match.
|
186
|
-
:param url_path_pattern: A regex pattern that will be applied to the path portion of the URL.
|
187
|
-
:param params: Dictionary of query parameters that must be present in the request.
|
188
|
-
:param headers: Dictionary of headers that must be present (header keys are compared case-insensitively).
|
189
|
-
"""
|
190
|
-
self._method = method.upper() if method else None
|
191
|
-
|
192
|
-
# Normalize the url_base if provided: remove trailing slash.
|
193
|
-
self._url_base = url_base.rstrip("/") if url_base else None
|
194
|
-
|
195
|
-
# Compile the URL path pattern if provided.
|
196
|
-
self._url_path_pattern = re.compile(url_path_pattern) if url_path_pattern else None
|
197
|
-
|
198
|
-
# Normalize query parameters to strings.
|
199
|
-
self._params = {str(k): str(v) for k, v in (params or {}).items()}
|
200
|
-
|
201
|
-
# Normalize header keys to lowercase.
|
202
|
-
self._headers = {str(k).lower(): str(v) for k, v in (headers or {}).items()}
|
203
|
-
|
204
|
-
@staticmethod
|
205
|
-
def _match_dict(obj: Mapping[str, Any], pattern: Mapping[str, Any]) -> bool:
|
206
|
-
"""Check that every key/value in the pattern exists in the object."""
|
207
|
-
return pattern.items() <= obj.items()
|
208
|
-
|
209
|
-
def __call__(self, request: Any) -> bool:
|
210
|
-
"""
|
211
|
-
:param request: A requests.Request or requests.PreparedRequest instance.
|
212
|
-
:return: True if the request matches all provided criteria; False otherwise.
|
213
|
-
"""
|
214
|
-
# Prepare the request (if needed) and extract the URL details.
|
215
|
-
if isinstance(request, requests.Request):
|
216
|
-
prepared_request = request.prepare()
|
217
|
-
elif isinstance(request, requests.PreparedRequest):
|
218
|
-
prepared_request = request
|
219
|
-
else:
|
220
|
-
return False
|
221
|
-
|
222
|
-
# Check HTTP method.
|
223
|
-
if self._method is not None and prepared_request.method is not None:
|
224
|
-
if prepared_request.method.upper() != self._method:
|
225
|
-
return False
|
226
|
-
|
227
|
-
# Parse the URL.
|
228
|
-
parsed_url = parse.urlsplit(prepared_request.url)
|
229
|
-
# Reconstruct the base: scheme://netloc
|
230
|
-
request_url_base = f"{str(parsed_url.scheme)}://{str(parsed_url.netloc)}"
|
231
|
-
# The path (without query parameters)
|
232
|
-
request_path = str(parsed_url.path).rstrip("/")
|
233
|
-
|
234
|
-
# If a base URL is provided, check that it matches.
|
235
|
-
if self._url_base is not None:
|
236
|
-
if request_url_base != self._url_base:
|
237
|
-
return False
|
238
|
-
|
239
|
-
# If a URL path pattern is provided, ensure the path matches the regex.
|
240
|
-
if self._url_path_pattern is not None:
|
241
|
-
if not self._url_path_pattern.search(request_path):
|
242
|
-
return False
|
243
|
-
|
244
|
-
# Check query parameters.
|
245
|
-
if self._params:
|
246
|
-
query_params = dict(parse.parse_qsl(str(parsed_url.query)))
|
247
|
-
if not self._match_dict(query_params, self._params):
|
248
|
-
return False
|
249
|
-
|
250
|
-
# Check headers (normalize keys to lower-case).
|
251
|
-
if self._headers:
|
252
|
-
req_headers = {k.lower(): v for k, v in prepared_request.headers.items()}
|
253
|
-
if not self._match_dict(req_headers, self._headers):
|
254
|
-
return False
|
255
|
-
|
256
|
-
return True
|
257
|
-
|
258
|
-
|
259
163
|
class BaseCallRatePolicy(AbstractCallRatePolicy, abc.ABC):
|
260
164
|
def __init__(self, matchers: list[RequestMatcher]):
|
261
165
|
self._matchers = matchers
|
@@ -495,17 +399,24 @@ class AbstractAPIBudget(abc.ABC):
|
|
495
399
|
"""
|
496
400
|
|
497
401
|
|
498
|
-
@dataclass
|
499
402
|
class APIBudget(AbstractAPIBudget):
|
500
|
-
"""
|
501
|
-
|
502
|
-
|
403
|
+
"""Default APIBudget implementation"""
|
404
|
+
|
405
|
+
def __init__(
|
406
|
+
self, policies: list[AbstractCallRatePolicy], maximum_attempts_to_acquire: int = 100000
|
407
|
+
) -> None:
|
408
|
+
"""Constructor
|
409
|
+
|
410
|
+
:param policies: list of policies in this budget
|
411
|
+
:param maximum_attempts_to_acquire: number of attempts before throwing hit ratelimit exception, we put some big number here
|
412
|
+
to avoid situations when many threads compete with each other for a few lots over a significant amount of time
|
413
|
+
"""
|
503
414
|
|
504
|
-
|
505
|
-
|
415
|
+
self._policies = policies
|
416
|
+
self._maximum_attempts_to_acquire = maximum_attempts_to_acquire
|
506
417
|
|
507
418
|
def get_matching_policy(self, request: Any) -> Optional[AbstractCallRatePolicy]:
|
508
|
-
for policy in self.
|
419
|
+
for policy in self._policies:
|
509
420
|
if policy.matches(request):
|
510
421
|
return policy
|
511
422
|
return None
|
@@ -526,7 +437,7 @@ class APIBudget(AbstractAPIBudget):
|
|
526
437
|
policy = self.get_matching_policy(request)
|
527
438
|
if policy:
|
528
439
|
self._do_acquire(request=request, policy=policy, block=block, timeout=timeout)
|
529
|
-
elif self.
|
440
|
+
elif self._policies:
|
530
441
|
logger.info("no policies matched with requests, allow call by default")
|
531
442
|
|
532
443
|
def update_from_response(self, request: Any, response: Any) -> None:
|
@@ -549,7 +460,7 @@ class APIBudget(AbstractAPIBudget):
|
|
549
460
|
"""
|
550
461
|
last_exception = None
|
551
462
|
# sometimes we spend all budget before a second attempt, so we have few more here
|
552
|
-
for attempt in range(1, self.
|
463
|
+
for attempt in range(1, self._maximum_attempts_to_acquire):
|
553
464
|
try:
|
554
465
|
policy.try_acquire(request, weight=1)
|
555
466
|
return
|
@@ -573,18 +484,31 @@ class APIBudget(AbstractAPIBudget):
|
|
573
484
|
|
574
485
|
if last_exception:
|
575
486
|
logger.info(
|
576
|
-
"we used all %s attempts to acquire and failed", self.
|
487
|
+
"we used all %s attempts to acquire and failed", self._maximum_attempts_to_acquire
|
577
488
|
)
|
578
489
|
raise last_exception
|
579
490
|
|
580
491
|
|
581
|
-
@dataclass
|
582
492
|
class HttpAPIBudget(APIBudget):
|
583
493
|
"""Implementation of AbstractAPIBudget for HTTP"""
|
584
494
|
|
585
|
-
|
586
|
-
|
587
|
-
|
495
|
+
def __init__(
|
496
|
+
self,
|
497
|
+
ratelimit_reset_header: str = "ratelimit-reset",
|
498
|
+
ratelimit_remaining_header: str = "ratelimit-remaining",
|
499
|
+
status_codes_for_ratelimit_hit: tuple[int] = (429,),
|
500
|
+
**kwargs: Any,
|
501
|
+
):
|
502
|
+
"""Constructor
|
503
|
+
|
504
|
+
:param ratelimit_reset_header: name of the header that has a timestamp of the next reset of call budget
|
505
|
+
:param ratelimit_remaining_header: name of the header that has the number of calls left
|
506
|
+
:param status_codes_for_ratelimit_hit: list of HTTP status codes that signal about rate limit being hit
|
507
|
+
"""
|
508
|
+
self._ratelimit_reset_header = ratelimit_reset_header
|
509
|
+
self._ratelimit_remaining_header = ratelimit_remaining_header
|
510
|
+
self._status_codes_for_ratelimit_hit = status_codes_for_ratelimit_hit
|
511
|
+
super().__init__(**kwargs)
|
588
512
|
|
589
513
|
def update_from_response(self, request: Any, response: Any) -> None:
|
590
514
|
policy = self.get_matching_policy(request)
|
@@ -599,17 +523,17 @@ class HttpAPIBudget(APIBudget):
|
|
599
523
|
def get_reset_ts_from_response(
|
600
524
|
self, response: requests.Response
|
601
525
|
) -> Optional[datetime.datetime]:
|
602
|
-
if response.headers.get(self.
|
526
|
+
if response.headers.get(self._ratelimit_reset_header):
|
603
527
|
return datetime.datetime.fromtimestamp(
|
604
|
-
int(response.headers[self.
|
528
|
+
int(response.headers[self._ratelimit_reset_header])
|
605
529
|
)
|
606
530
|
return None
|
607
531
|
|
608
532
|
def get_calls_left_from_response(self, response: requests.Response) -> Optional[int]:
|
609
|
-
if response.headers.get(self.
|
610
|
-
return int(response.headers[self.
|
533
|
+
if response.headers.get(self._ratelimit_remaining_header):
|
534
|
+
return int(response.headers[self._ratelimit_remaining_header])
|
611
535
|
|
612
|
-
if response.status_code in self.
|
536
|
+
if response.status_code in self._status_codes_for_ratelimit_hit:
|
613
537
|
return 0
|
614
538
|
|
615
539
|
return None
|
@@ -261,6 +261,9 @@ class AbstractOauth2Authenticator(AuthBase):
|
|
261
261
|
|
262
262
|
:return: expiration datetime
|
263
263
|
"""
|
264
|
+
if not value and not self.token_has_expired():
|
265
|
+
# No expiry token was provided but the previous one is not expired so it's fine
|
266
|
+
return self.get_token_expiry_date()
|
264
267
|
|
265
268
|
if self.token_expiry_is_time_of_expiration:
|
266
269
|
if not self.token_expiry_date_format:
|
@@ -53,7 +53,7 @@ airbyte_cdk/sources/declarative/async_job/timer.py,sha256=Fb8P72CQ7jIzJyzMSSNuBf
|
|
53
53
|
airbyte_cdk/sources/declarative/auth/__init__.py,sha256=e2CRrcBWGhz3sQu3Oh34d1riEIwXipGS8hrSB1pu0Oo,284
|
54
54
|
airbyte_cdk/sources/declarative/auth/declarative_authenticator.py,sha256=nf-OmRUHYG4ORBwyb5CANzuHEssE-oNmL-Lccn41Td8,1099
|
55
55
|
airbyte_cdk/sources/declarative/auth/jwt.py,sha256=SICqNsN2Cn_EgKadIgWuZpQxuMHyzrMZD_2-Uwy10rY,8539
|
56
|
-
airbyte_cdk/sources/declarative/auth/oauth.py,sha256=
|
56
|
+
airbyte_cdk/sources/declarative/auth/oauth.py,sha256=SUfib1oSzlyRRnOSg8Bui73mfyrcyr9OssdchbKdu4s,14162
|
57
57
|
airbyte_cdk/sources/declarative/auth/selective_authenticator.py,sha256=qGwC6YsCldr1bIeKG6Qo-A9a5cTdHw-vcOn3OtQrS4c,1540
|
58
58
|
airbyte_cdk/sources/declarative/auth/token.py,sha256=2EnE78EhBOY9hbeZnQJ9AuFaM-G7dccU-oKo_LThRQk,11070
|
59
59
|
airbyte_cdk/sources/declarative/auth/token_provider.py,sha256=9CuSsmOoHkvlc4k-oZ3Jx5luAgfTMm1I_5HOZxw7wMU,3075
|
@@ -63,11 +63,11 @@ airbyte_cdk/sources/declarative/checks/check_stream.py,sha256=dAA-UhmMj0WLXCkRQr
|
|
63
63
|
airbyte_cdk/sources/declarative/checks/connection_checker.py,sha256=MBRJo6WJlZQHpIfOGaNOkkHUmgUl_4wDM6VPo41z5Ss,1383
|
64
64
|
airbyte_cdk/sources/declarative/concurrency_level/__init__.py,sha256=5XUqrmlstYlMM0j6crktlKQwALek0uiz2D3WdM46MyA,191
|
65
65
|
airbyte_cdk/sources/declarative/concurrency_level/concurrency_level.py,sha256=YIwCTCpOr_QSNW4ltQK0yUGWInI8PKNY216HOOegYLk,2101
|
66
|
-
airbyte_cdk/sources/declarative/concurrent_declarative_source.py,sha256=
|
66
|
+
airbyte_cdk/sources/declarative/concurrent_declarative_source.py,sha256=ThOqmaaqPykS2gTDKnlLSPy0p7djjV1Svazes58Rmic,28844
|
67
67
|
airbyte_cdk/sources/declarative/datetime/__init__.py,sha256=l9LG7Qm6e5r_qgqfVKnx3mXYtg1I9MmMjomVIPfU4XA,177
|
68
68
|
airbyte_cdk/sources/declarative/datetime/datetime_parser.py,sha256=SX9JjdesN1edN2WVUVMzU_ptqp2QB1OnsnjZ4mwcX7w,2579
|
69
69
|
airbyte_cdk/sources/declarative/datetime/min_max_datetime.py,sha256=0BHBtDNQZfvwM45-tY5pNlTcKAFSGGNxemoi0Jic-0E,5785
|
70
|
-
airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=
|
70
|
+
airbyte_cdk/sources/declarative/declarative_component_schema.yaml,sha256=51R-WLE1Xhrk57DoiHFxWZSP8V9HtCEi1UE8_KtEOuM,140375
|
71
71
|
airbyte_cdk/sources/declarative/declarative_source.py,sha256=nF7wBqFd3AQmEKAm4CnIo29CJoQL562cJGSCeL8U8bA,1531
|
72
72
|
airbyte_cdk/sources/declarative/declarative_stream.py,sha256=venZjfpvtqr3oFSuvMBWtn4h9ayLhD4L65ACuXCDZ64,10445
|
73
73
|
airbyte_cdk/sources/declarative/decoders/__init__.py,sha256=KSpQetKGqPCv-38QgcVJ5kzM5nzbFldTSsYDCS3Xf0Y,1035
|
@@ -104,18 +104,18 @@ airbyte_cdk/sources/declarative/interpolation/interpolated_string.py,sha256=LYEZ
|
|
104
104
|
airbyte_cdk/sources/declarative/interpolation/interpolation.py,sha256=-V5UddGm69UKEB6o_O1EIES9kfY8FV_X4Ji8w1yOuSA,981
|
105
105
|
airbyte_cdk/sources/declarative/interpolation/jinja.py,sha256=BtsY_jtT4MihFqeQgc05HXj3Ndt-e2ESQgGwbg3Sdxc,6430
|
106
106
|
airbyte_cdk/sources/declarative/interpolation/macros.py,sha256=Y5AWYxbJTUtJ_Jm7DV9qrZDiymFR9LST7fBt4piT2-U,4585
|
107
|
-
airbyte_cdk/sources/declarative/manifest_declarative_source.py,sha256=
|
107
|
+
airbyte_cdk/sources/declarative/manifest_declarative_source.py,sha256=26qMXRugdPAd3zyYRH6YpNi--TorGZVOtxzY5O6muL0,16912
|
108
108
|
airbyte_cdk/sources/declarative/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
109
109
|
airbyte_cdk/sources/declarative/migrations/legacy_to_per_partition_state_migration.py,sha256=iemy3fKLczcU0-Aor7tx5jcT6DRedKMqyK7kCOp01hg,3924
|
110
110
|
airbyte_cdk/sources/declarative/migrations/state_migration.py,sha256=KWPjealMLKSMtajXgkdGgKg7EmTLR-CqqD7UIh0-eDU,794
|
111
111
|
airbyte_cdk/sources/declarative/models/__init__.py,sha256=nUFxNCiKeYRVXuZEKA7GD-lTHxsiKcQ8FitZjKhPIvE,100
|
112
|
-
airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=
|
112
|
+
airbyte_cdk/sources/declarative/models/declarative_component_schema.py,sha256=7qzq5ZV3V90uYRMa_-CGTbJ7aVv5jyPqGGjpJb9zdHk,98706
|
113
113
|
airbyte_cdk/sources/declarative/parsers/__init__.py,sha256=ZnqYNxHsKCgO38IwB34RQyRMXTs4GTvlRi3ImKnIioo,61
|
114
114
|
airbyte_cdk/sources/declarative/parsers/custom_code_compiler.py,sha256=958MMX6_ZOJUlDDdNr9Krosgi2bCKGx2Z765M2Woz18,5505
|
115
115
|
airbyte_cdk/sources/declarative/parsers/custom_exceptions.py,sha256=Rir9_z3Kcd5Es0-LChrzk-0qubAsiK_RSEnLmK2OXm8,553
|
116
116
|
airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py,sha256=CXwTfD3wSQq3okcqwigpprbHhSURUokh4GK2OmOyKC8,9132
|
117
117
|
airbyte_cdk/sources/declarative/parsers/manifest_reference_resolver.py,sha256=IWUOdF03o-aQn0Occo1BJCxU0Pz-QILk5L67nzw2thw,6803
|
118
|
-
airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=
|
118
|
+
airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py,sha256=MuzhEy8ST0-VijWck1VEeO7NTFRW4BpJ9LafFHxOTwo,129444
|
119
119
|
airbyte_cdk/sources/declarative/partition_routers/__init__.py,sha256=HJ-Syp3p7RpyR_OK0X_a2kSyISfu3W-PKrRI16iY0a8,957
|
120
120
|
airbyte_cdk/sources/declarative/partition_routers/async_job_partition_router.py,sha256=VelO7zKqKtzMJ35jyFeg0ypJLQC0plqqIBNXoBW1G2E,3001
|
121
121
|
airbyte_cdk/sources/declarative/partition_routers/cartesian_product_stream_slicer.py,sha256=c5cuVFM6NFkuQqG8Z5IwkBuwDrvXZN1CunUOM_L0ezg,6892
|
@@ -139,7 +139,7 @@ airbyte_cdk/sources/declarative/requesters/error_handlers/default_http_response_
|
|
139
139
|
airbyte_cdk/sources/declarative/requesters/error_handlers/error_handler.py,sha256=Tan66odx8VHzfdyyXMQkXz2pJYksllGqvxmpoajgcK4,669
|
140
140
|
airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py,sha256=E-fQbt4ShfxZVoqfnmOx69C6FUPWZz8BIqI3DN9Kcjs,7935
|
141
141
|
airbyte_cdk/sources/declarative/requesters/http_job_repository.py,sha256=3GtOefPH08evlSUxaILkiKLTHbIspFY4qd5B3ZqNE60,10063
|
142
|
-
airbyte_cdk/sources/declarative/requesters/http_requester.py,sha256=
|
142
|
+
airbyte_cdk/sources/declarative/requesters/http_requester.py,sha256=C6YT7t4UZMfarFeQ9fc362R5TbQ2jNSew8ESP-9yuZQ,14851
|
143
143
|
airbyte_cdk/sources/declarative/requesters/paginators/__init__.py,sha256=uArbKs9JKNCt7t9tZoeWwjDpyI1HoPp29FNW0JzvaEM,644
|
144
144
|
airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py,sha256=dSm_pKGOZjzvg-X_Vif-MjrnlUG23fCa69bocq8dVIs,11693
|
145
145
|
airbyte_cdk/sources/declarative/requesters/paginators/no_pagination.py,sha256=j6j9QRPaTbKQ2N661RFVKthhkWiodEp6ut0tKeEd0Ng,2019
|
@@ -249,7 +249,7 @@ airbyte_cdk/sources/message/repository.py,sha256=SG7avgti_-dj8FcRHTTrhgLLGJbElv1
|
|
249
249
|
airbyte_cdk/sources/source.py,sha256=KIBBH5VLEb8BZ8B9aROlfaI6OLoJqKDPMJ10jkAR7nk,3611
|
250
250
|
airbyte_cdk/sources/streams/__init__.py,sha256=8fzTKpRTnSx5PggXgQPKJzHNZUV2BCA40N-dI6JM1xI,256
|
251
251
|
airbyte_cdk/sources/streams/availability_strategy.py,sha256=_RU4JITrxMEN36g1RDHMu0iSw0I_3yWGfo5N8_YRvOg,3247
|
252
|
-
airbyte_cdk/sources/streams/call_rate.py,sha256=
|
252
|
+
airbyte_cdk/sources/streams/call_rate.py,sha256=Um_Ny8R7WZ2B0PWoxr-wrWPsgc5we7HrHalaMcozuVs,21052
|
253
253
|
airbyte_cdk/sources/streams/checkpoint/__init__.py,sha256=3oy7Hd4ivVWTZlN6dKAf4Fv_G7U5iZrvhO9hT871UIo,712
|
254
254
|
airbyte_cdk/sources/streams/checkpoint/checkpoint_reader.py,sha256=6HMT2NI-FQuaW0nt95NcyWrt5rZN4gF-Arx0sxdgbv4,15221
|
255
255
|
airbyte_cdk/sources/streams/checkpoint/cursor.py,sha256=3e-3c-54k8U7Awno7DMmAD9ndbnl9OM48EnbEgeDUO0,3499
|
@@ -295,7 +295,7 @@ airbyte_cdk/sources/streams/http/http.py,sha256=0uariNq8OFnlX7iqOHwBhecxA-Hfd5hS
|
|
295
295
|
airbyte_cdk/sources/streams/http/http_client.py,sha256=tDE0ROtxjGMVphvsw8INvGMtZ97hIF-v47pZ3jIyiwc,23011
|
296
296
|
airbyte_cdk/sources/streams/http/rate_limiting.py,sha256=IwdjrHKUnU97XO4qONgYRv4YYW51xQ8SJm4WLafXDB8,6351
|
297
297
|
airbyte_cdk/sources/streams/http/requests_native_auth/__init__.py,sha256=RN0D3nOX1xLgwEwKWu6pkGy3XqBFzKSNZ8Lf6umU2eY,413
|
298
|
-
airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py,sha256=
|
298
|
+
airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py,sha256=cM5CM1mnbTEMiY6gKHblGXr9KTS5VEziGoc-TXC302k,18791
|
299
299
|
airbyte_cdk/sources/streams/http/requests_native_auth/abstract_token.py,sha256=Y3n7J-sk5yGjv_OxtY6Z6k0PEsFZmtIRi-x0KCbaHdA,1010
|
300
300
|
airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py,sha256=C2j2uVfi9d-3KgHO3NGxIiFdfASjHOtsd6g_LWPYOAs,20311
|
301
301
|
airbyte_cdk/sources/streams/http/requests_native_auth/token.py,sha256=h5PTzcdH-RQLeCg7xZ45w_484OPUDSwNWl_iMJQmZoI,2526
|
@@ -351,9 +351,9 @@ airbyte_cdk/utils/slice_hasher.py,sha256=EDxgROHDbfG-QKQb59m7h_7crN1tRiawdf5uU7G
|
|
351
351
|
airbyte_cdk/utils/spec_schema_transformations.py,sha256=-5HTuNsnDBAhj-oLeQXwpTGA0HdcjFOf2zTEMUTTg_Y,816
|
352
352
|
airbyte_cdk/utils/stream_status_utils.py,sha256=ZmBoiy5HVbUEHAMrUONxZvxnvfV9CesmQJLDTAIWnWw,1171
|
353
353
|
airbyte_cdk/utils/traced_exception.py,sha256=C8uIBuCL_E4WnBAOPSxBicD06JAldoN9fGsQDp463OY,6292
|
354
|
-
airbyte_cdk-6.33.
|
355
|
-
airbyte_cdk-6.33.
|
356
|
-
airbyte_cdk-6.33.
|
357
|
-
airbyte_cdk-6.33.
|
358
|
-
airbyte_cdk-6.33.
|
359
|
-
airbyte_cdk-6.33.
|
354
|
+
airbyte_cdk-6.33.2.dist-info/LICENSE.txt,sha256=Wfe61S4BaGPj404v8lrAbvhjYR68SHlkzeYrg3_bbuM,1051
|
355
|
+
airbyte_cdk-6.33.2.dist-info/LICENSE_SHORT,sha256=aqF6D1NcESmpn-cqsxBtszTEnHKnlsp8L4x9wAh3Nxg,55
|
356
|
+
airbyte_cdk-6.33.2.dist-info/METADATA,sha256=8_7_yrHWUow7tK9vqk_hqRiXhf9geY_kN_N1cU1XpLY,6010
|
357
|
+
airbyte_cdk-6.33.2.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
358
|
+
airbyte_cdk-6.33.2.dist-info/entry_points.txt,sha256=fj-e3PAQvsxsQzyyq8UkG1k8spunWnD4BAH2AwlR6NM,95
|
359
|
+
airbyte_cdk-6.33.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|