ingestr 0.13.75__py3-none-any.whl → 0.14.98__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.

Potentially problematic release.


This version of ingestr might be problematic. Click here for more details.

Files changed (79) hide show
  1. ingestr/main.py +22 -3
  2. ingestr/src/adjust/__init__.py +4 -4
  3. ingestr/src/allium/__init__.py +128 -0
  4. ingestr/src/anthropic/__init__.py +277 -0
  5. ingestr/src/anthropic/helpers.py +525 -0
  6. ingestr/src/appstore/__init__.py +1 -0
  7. ingestr/src/asana_source/__init__.py +1 -1
  8. ingestr/src/buildinfo.py +1 -1
  9. ingestr/src/chess/__init__.py +1 -1
  10. ingestr/src/couchbase_source/__init__.py +118 -0
  11. ingestr/src/couchbase_source/helpers.py +135 -0
  12. ingestr/src/cursor/__init__.py +83 -0
  13. ingestr/src/cursor/helpers.py +188 -0
  14. ingestr/src/destinations.py +169 -1
  15. ingestr/src/docebo/__init__.py +589 -0
  16. ingestr/src/docebo/client.py +435 -0
  17. ingestr/src/docebo/helpers.py +97 -0
  18. ingestr/src/elasticsearch/helpers.py +138 -0
  19. ingestr/src/errors.py +8 -0
  20. ingestr/src/facebook_ads/__init__.py +26 -23
  21. ingestr/src/facebook_ads/helpers.py +47 -1
  22. ingestr/src/factory.py +48 -0
  23. ingestr/src/filesystem/__init__.py +8 -3
  24. ingestr/src/filters.py +9 -0
  25. ingestr/src/fluxx/__init__.py +9906 -0
  26. ingestr/src/fluxx/helpers.py +209 -0
  27. ingestr/src/frankfurter/__init__.py +157 -163
  28. ingestr/src/frankfurter/helpers.py +3 -3
  29. ingestr/src/freshdesk/__init__.py +25 -8
  30. ingestr/src/freshdesk/freshdesk_client.py +40 -5
  31. ingestr/src/fundraiseup/__init__.py +49 -0
  32. ingestr/src/fundraiseup/client.py +81 -0
  33. ingestr/src/github/__init__.py +6 -4
  34. ingestr/src/google_analytics/__init__.py +1 -1
  35. ingestr/src/hostaway/__init__.py +302 -0
  36. ingestr/src/hostaway/client.py +288 -0
  37. ingestr/src/http/__init__.py +35 -0
  38. ingestr/src/http/readers.py +114 -0
  39. ingestr/src/hubspot/__init__.py +6 -12
  40. ingestr/src/influxdb/__init__.py +1 -0
  41. ingestr/src/intercom/__init__.py +142 -0
  42. ingestr/src/intercom/helpers.py +674 -0
  43. ingestr/src/intercom/settings.py +279 -0
  44. ingestr/src/jira_source/__init__.py +340 -0
  45. ingestr/src/jira_source/helpers.py +439 -0
  46. ingestr/src/jira_source/settings.py +170 -0
  47. ingestr/src/klaviyo/__init__.py +5 -5
  48. ingestr/src/linear/__init__.py +553 -116
  49. ingestr/src/linear/helpers.py +77 -38
  50. ingestr/src/mailchimp/__init__.py +126 -0
  51. ingestr/src/mailchimp/helpers.py +226 -0
  52. ingestr/src/mailchimp/settings.py +164 -0
  53. ingestr/src/masking.py +344 -0
  54. ingestr/src/monday/__init__.py +246 -0
  55. ingestr/src/monday/helpers.py +392 -0
  56. ingestr/src/monday/settings.py +328 -0
  57. ingestr/src/mongodb/__init__.py +5 -2
  58. ingestr/src/mongodb/helpers.py +384 -10
  59. ingestr/src/plusvibeai/__init__.py +335 -0
  60. ingestr/src/plusvibeai/helpers.py +544 -0
  61. ingestr/src/plusvibeai/settings.py +252 -0
  62. ingestr/src/revenuecat/__init__.py +83 -0
  63. ingestr/src/revenuecat/helpers.py +237 -0
  64. ingestr/src/salesforce/__init__.py +15 -8
  65. ingestr/src/shopify/__init__.py +1 -1
  66. ingestr/src/smartsheets/__init__.py +33 -5
  67. ingestr/src/socrata_source/__init__.py +83 -0
  68. ingestr/src/socrata_source/helpers.py +85 -0
  69. ingestr/src/socrata_source/settings.py +8 -0
  70. ingestr/src/sources.py +1418 -54
  71. ingestr/src/stripe_analytics/__init__.py +2 -19
  72. ingestr/src/wise/__init__.py +68 -0
  73. ingestr/src/wise/client.py +63 -0
  74. ingestr/tests/unit/test_smartsheets.py +6 -9
  75. {ingestr-0.13.75.dist-info → ingestr-0.14.98.dist-info}/METADATA +24 -12
  76. {ingestr-0.13.75.dist-info → ingestr-0.14.98.dist-info}/RECORD +79 -37
  77. {ingestr-0.13.75.dist-info → ingestr-0.14.98.dist-info}/WHEEL +0 -0
  78. {ingestr-0.13.75.dist-info → ingestr-0.14.98.dist-info}/entry_points.txt +0 -0
  79. {ingestr-0.13.75.dist-info → ingestr-0.14.98.dist-info}/licenses/LICENSE.md +0 -0
@@ -0,0 +1,209 @@
1
+ import json
2
+ from typing import Any, Dict, Iterator, List, Optional
3
+
4
+ import dlt
5
+ import pendulum
6
+ import requests
7
+
8
+ FLUXX_API_BASE = "https://{instance}.fluxxlabs.com"
9
+ FLUXX_OAUTH_TOKEN_PATH = "/oauth/token"
10
+ FLUXX_API_V2_PATH = "/api/rest/v2"
11
+
12
+
13
+ def get_access_token(instance: str, client_id: str, client_secret: str) -> str:
14
+ """Obtain OAuth access token using client credentials flow."""
15
+ token_url = f"{FLUXX_API_BASE.format(instance=instance)}{FLUXX_OAUTH_TOKEN_PATH}"
16
+
17
+ response = requests.post(
18
+ token_url,
19
+ data={
20
+ "grant_type": "client_credentials",
21
+ "client_id": client_id,
22
+ "client_secret": client_secret,
23
+ },
24
+ )
25
+ response.raise_for_status()
26
+
27
+ token_data = response.json()
28
+ return token_data["access_token"]
29
+
30
+
31
+ def fluxx_api_request(
32
+ instance: str,
33
+ access_token: str,
34
+ endpoint: str,
35
+ method: str = "GET",
36
+ params: Optional[Dict[str, Any]] = None,
37
+ data: Optional[Dict[str, Any]] = None,
38
+ ) -> Dict[str, Any]:
39
+ """Make an authenticated request to the Fluxx API."""
40
+ url = f"{FLUXX_API_BASE.format(instance=instance)}{FLUXX_API_V2_PATH}/{endpoint}"
41
+
42
+ headers = {
43
+ "Authorization": f"Bearer {access_token}",
44
+ "Content-Type": "application/json",
45
+ }
46
+
47
+ response = requests.request(
48
+ method=method,
49
+ url=url,
50
+ headers=headers,
51
+ params=params,
52
+ json=data,
53
+ )
54
+ response.raise_for_status()
55
+
56
+ if response.text:
57
+ return response.json()
58
+ return {}
59
+
60
+
61
+ def paginate_fluxx_resource(
62
+ instance: str,
63
+ access_token: str,
64
+ endpoint: str,
65
+ params: Optional[Dict[str, Any]] = None,
66
+ page_size: int = 100,
67
+ ) -> Iterator[List[Dict[str, Any]]]:
68
+ """Paginate through a Fluxx API resource."""
69
+ if params is None:
70
+ params = {}
71
+
72
+ page = 1
73
+ params["per_page"] = page_size
74
+
75
+ while True:
76
+ params["page"] = page
77
+
78
+ response = fluxx_api_request(
79
+ instance=instance,
80
+ access_token=access_token,
81
+ endpoint=endpoint,
82
+ params=params,
83
+ )
84
+
85
+ if not response:
86
+ break
87
+
88
+ # Get the first available key from records instead of assuming endpoint name
89
+ records = response["records"]
90
+ if records:
91
+ # Pick the first key available in records
92
+ first_key = next(iter(records))
93
+ items = records[first_key]
94
+ else:
95
+ items = []
96
+
97
+ yield items
98
+
99
+ if response["per_page"] is None or len(items) < response["per_page"]:
100
+ break
101
+
102
+ page += 1
103
+
104
+
105
+ def get_date_range(updated_at, start_date):
106
+ """Extract current start and end dates from incremental state."""
107
+ if updated_at.last_value:
108
+ current_start_date = pendulum.parse(updated_at.last_value)
109
+ else:
110
+ current_start_date = (
111
+ pendulum.parse(start_date)
112
+ if start_date
113
+ else pendulum.now().subtract(days=30)
114
+ )
115
+
116
+ if updated_at.end_value:
117
+ current_end_date = pendulum.parse(updated_at.end_value)
118
+ else:
119
+ current_end_date = pendulum.now(tz="UTC")
120
+
121
+ return current_start_date, current_end_date
122
+
123
+
124
+ def create_dynamic_resource(
125
+ resource_name: str,
126
+ endpoint: str,
127
+ instance: str,
128
+ access_token: str,
129
+ start_date: Optional[pendulum.DateTime] = None,
130
+ end_date: Optional[pendulum.DateTime] = None,
131
+ fields_to_extract: Optional[Dict[str, Any]] = None,
132
+ ):
133
+ """Factory function to create dynamic Fluxx resources."""
134
+
135
+ # Extract column definitions for DLT resource
136
+ columns = {}
137
+ if fields_to_extract:
138
+ for field_name, field_config in fields_to_extract.items():
139
+ data_type = field_config.get("data_type")
140
+ if data_type:
141
+ columns[field_name] = {"data_type": data_type}
142
+
143
+ @dlt.resource(name=resource_name, write_disposition="replace", columns=columns) # type: ignore
144
+ def fluxx_resource() -> Iterator[Dict[str, Any]]:
145
+ params = {}
146
+ if fields_to_extract:
147
+ field_names = list(fields_to_extract.keys())
148
+ params["cols"] = json.dumps(field_names)
149
+
150
+ for page in paginate_fluxx_resource(
151
+ instance=instance,
152
+ access_token=access_token,
153
+ endpoint=endpoint,
154
+ params=params,
155
+ page_size=100,
156
+ ):
157
+ yield [normalize_fluxx_item(item, fields_to_extract) for item in page] # type: ignore
158
+
159
+ return fluxx_resource
160
+
161
+
162
+ def normalize_fluxx_item(
163
+ item: Dict[str, Any], fields_to_extract: Optional[Dict[str, Any]] = None
164
+ ) -> Dict[str, Any]:
165
+ """
166
+ Normalize a Fluxx API response item.
167
+ Handles nested structures and field extraction based on field types.
168
+ Rounds all decimal/float values to 4 decimal places regardless of field type.
169
+ """
170
+ normalized: Dict[str, Any] = {}
171
+
172
+ # If no field mapping provided, just return the item as-is
173
+ if not fields_to_extract:
174
+ return item
175
+
176
+ for field_name, field_config in fields_to_extract.items():
177
+ if field_name in item:
178
+ value = item[field_name]
179
+ field_type = field_config.get("data_type")
180
+
181
+ if isinstance(value, float):
182
+ # Round any numeric value with decimal places
183
+ normalized[field_name] = round(value, 4)
184
+ elif field_type == "json":
185
+ # Handle json fields (arrays/relations)
186
+ if value is None:
187
+ normalized[field_name] = None
188
+ elif value == "":
189
+ normalized[field_name] = None
190
+ elif isinstance(value, (list, dict)):
191
+ normalized[field_name] = value
192
+ else:
193
+ # Single value - wrap in array for json fields
194
+ normalized[field_name] = [value]
195
+ elif field_type in ("date", "timestamp", "datetime", "text"):
196
+ # Handle text/date fields - convert empty strings to None
197
+ if value == "":
198
+ normalized[field_name] = None
199
+ else:
200
+ normalized[field_name] = value
201
+ else:
202
+ # All other field types - pass through as-is
203
+ normalized[field_name] = value
204
+
205
+ # Always include id if present
206
+ if "id" in item:
207
+ normalized["id"] = item["id"]
208
+
209
+ return normalized
@@ -1,163 +1,157 @@
1
- from typing import Any, Iterator, Optional
2
-
3
- import dlt
4
- from dlt.common.pendulum import pendulum
5
- from dlt.common.time import ensure_pendulum_datetime
6
- from dlt.common.typing import TAnyDateTime
7
-
8
- from ingestr.src.frankfurter.helpers import get_path_with_retry
9
-
10
-
11
- @dlt.source(
12
- name="frankfurter",
13
- max_table_nesting=0,
14
- )
15
- def frankfurter_source(
16
- start_date: TAnyDateTime,
17
- end_date: TAnyDateTime,
18
- base_currency: str,
19
- ) -> Any:
20
- """
21
- A dlt source for the frankfurter.dev API. It groups several resources (in this case frankfurter.dev API endpoints) containing
22
- various types of data: currencies, latest rates, historical rates.
23
- """
24
- date_time = dlt.sources.incremental(
25
- "date",
26
- initial_value=start_date,
27
- end_value=end_date,
28
- range_start="closed",
29
- range_end="closed",
30
- )
31
-
32
- return (
33
- currencies(),
34
- latest(base_currency=base_currency),
35
- exchange_rates(
36
- start_date=date_time, end_date=end_date, base_currency=base_currency
37
- ),
38
- )
39
-
40
-
41
- @dlt.resource(
42
- write_disposition="replace",
43
- columns={
44
- "currency_code": {"data_type": "text"},
45
- "currency_name": {"data_type": "text"},
46
- },
47
- )
48
- def currencies() -> Iterator[dict]:
49
- """
50
- Yields each currency as a separate row with two columns: currency_code and currency_name.
51
- """
52
- # Retrieve the list of currencies from the API
53
- currencies_data = get_path_with_retry("currencies")
54
-
55
- for currency_code, currency_name in currencies_data.items():
56
- yield {"currency_code": currency_code, "currency_name": currency_name}
57
-
58
-
59
- @dlt.resource(
60
- write_disposition="merge",
61
- columns={
62
- "date": {"data_type": "text"},
63
- "currency_code": {"data_type": "text"},
64
- "rate": {"data_type": "double"},
65
- "base_currency": {"data_type": "text"},
66
- },
67
- primary_key=["date", "currency_code", "base_currency"],
68
- )
69
- def latest(base_currency: Optional[str] = "") -> Iterator[dict]:
70
- """
71
- Fetches the latest exchange rates and yields them as rows.
72
- """
73
- # Base URL
74
- url = "latest?"
75
-
76
- if base_currency:
77
- url += f"base={base_currency}"
78
-
79
- # Fetch data
80
- data = get_path_with_retry(url)
81
-
82
- # Extract rates and base currency
83
- rates = data["rates"]
84
- date = pendulum.parse(data["date"])
85
-
86
- # Add the base currency with a rate of 1.0
87
- yield {
88
- "date": date,
89
- "currency_code": base_currency,
90
- "rate": 1.0,
91
- "base_currency": base_currency,
92
- }
93
-
94
- # Add all currencies and their rates
95
- for currency_code, rate in rates.items():
96
- yield {
97
- "date": date,
98
- "currency_code": currency_code,
99
- "rate": rate,
100
- "base_currency": base_currency,
101
- }
102
-
103
-
104
- @dlt.resource(
105
- write_disposition="merge",
106
- columns={
107
- "date": {"data_type": "text"},
108
- "currency_code": {"data_type": "text"},
109
- "rate": {"data_type": "double"},
110
- "base_currency": {"data_type": "text"},
111
- },
112
- primary_key=("date", "currency_code", "base_currency"),
113
- )
114
- def exchange_rates(
115
- end_date: TAnyDateTime,
116
- start_date: dlt.sources.incremental[TAnyDateTime] = dlt.sources.incremental("date"),
117
- base_currency: Optional[str] = "",
118
- ) -> Iterator[dict]:
119
- """
120
- Fetches exchange rates for a specified date range.
121
- If only start_date is provided, fetches data until now.
122
- If both start_date and end_date are provided, fetches data for each day in the range.
123
- """
124
- # Ensure start_date.last_value is a pendulum.DateTime object
125
- start_date_obj = ensure_pendulum_datetime(start_date.last_value) # type: ignore
126
- start_date_str = start_date_obj.format("YYYY-MM-DD")
127
-
128
- # Ensure end_date is a pendulum.DateTime object
129
- end_date_obj = ensure_pendulum_datetime(end_date)
130
- end_date_str = end_date_obj.format("YYYY-MM-DD")
131
-
132
- # Compose the URL
133
- url = f"{start_date_str}..{end_date_str}?"
134
-
135
- if base_currency:
136
- url += f"base={base_currency}"
137
-
138
- # Fetch data from the API
139
- data = get_path_with_retry(url)
140
-
141
- # Extract base currency and rates from the API response
142
- rates = data["rates"]
143
-
144
- # Iterate over the rates dictionary (one entry per date)
145
- for date, daily_rates in rates.items():
146
- formatted_date = pendulum.parse(date)
147
-
148
- # Add the base currency with a rate of 1.0
149
- yield {
150
- "date": formatted_date,
151
- "currency_code": base_currency,
152
- "rate": 1.0,
153
- "base_currency": base_currency,
154
- }
155
-
156
- # Add all other currencies and their rates
157
- for currency_code, rate in daily_rates.items():
158
- yield {
159
- "date": formatted_date,
160
- "currency_code": currency_code,
161
- "rate": rate,
162
- "base_currency": base_currency,
163
- }
1
+ from typing import Any, Iterator, Optional
2
+
3
+ import dlt
4
+ from dlt.common.pendulum import pendulum
5
+ from dlt.common.time import ensure_pendulum_datetime
6
+ from dlt.common.typing import TAnyDateTime
7
+
8
+ from ingestr.src.frankfurter.helpers import get_path_with_retry
9
+
10
+
11
+ @dlt.source(
12
+ name="frankfurter",
13
+ max_table_nesting=0,
14
+ )
15
+ def frankfurter_source(
16
+ start_date: TAnyDateTime,
17
+ end_date: TAnyDateTime | None,
18
+ base_currency: str,
19
+ ) -> Any:
20
+ """
21
+ A dlt source for the frankfurter.dev API. It groups several resources (in this case frankfurter.dev API endpoints) containing
22
+ various types of data: currencies, latest rates, historical rates.
23
+ """
24
+
25
+ @dlt.resource(
26
+ write_disposition="replace",
27
+ )
28
+ def currencies() -> Iterator[dict]:
29
+ """
30
+ Yields each currency as a separate row with two columns: currency_code and currency_name.
31
+ """
32
+ # Retrieve the list of currencies from the API
33
+ currencies_data = get_path_with_retry("currencies")
34
+
35
+ for currency_code, currency_name in currencies_data.items():
36
+ yield {"currency_code": currency_code, "currency_name": currency_name}
37
+
38
+ @dlt.resource(
39
+ write_disposition="merge",
40
+ columns={
41
+ "date": {"data_type": "text"},
42
+ "currency_code": {"data_type": "text"},
43
+ "rate": {"data_type": "double"},
44
+ "base_currency": {"data_type": "text"},
45
+ },
46
+ primary_key=["date", "currency_code", "base_currency"],
47
+ )
48
+ def latest(base_currency: Optional[str] = "") -> Iterator[dict]:
49
+ """
50
+ Fetches the latest exchange rates and yields them as rows.
51
+ """
52
+ # Base URL
53
+ url = "latest?"
54
+
55
+ if base_currency:
56
+ url += f"base={base_currency}"
57
+
58
+ # Fetch data
59
+ data = get_path_with_retry(url)
60
+
61
+ # Extract rates and base currency
62
+ rates = data["rates"]
63
+ date = pendulum.parse(data["date"])
64
+
65
+ # Add the base currency with a rate of 1.0
66
+ yield {
67
+ "date": date,
68
+ "currency_code": base_currency,
69
+ "rate": 1.0,
70
+ "base_currency": base_currency,
71
+ }
72
+
73
+ # Add all currencies and their rates
74
+ for currency_code, rate in rates.items():
75
+ yield {
76
+ "date": date,
77
+ "currency_code": currency_code,
78
+ "rate": rate,
79
+ "base_currency": base_currency,
80
+ }
81
+
82
+ @dlt.resource(
83
+ write_disposition="merge",
84
+ columns={
85
+ "date": {"data_type": "text"},
86
+ "currency_code": {"data_type": "text"},
87
+ "rate": {"data_type": "double"},
88
+ "base_currency": {"data_type": "text"},
89
+ },
90
+ primary_key=("date", "currency_code", "base_currency"),
91
+ )
92
+ def exchange_rates(
93
+ date_time=dlt.sources.incremental(
94
+ "date",
95
+ initial_value=start_date,
96
+ end_value=end_date,
97
+ range_start="closed",
98
+ range_end="closed",
99
+ ),
100
+ ) -> Iterator[dict]:
101
+ """
102
+ Fetches exchange rates for a specified date range.
103
+ If only start_date is provided, fetches data until now.
104
+ If both start_date and end_date are provided, fetches data for each day in the range.
105
+ """
106
+ if date_time.last_value is not None:
107
+ start_date = date_time.last_value
108
+ else:
109
+ start_date = start_date
110
+
111
+ if date_time.end_value is not None:
112
+ end_date = date_time.end_value
113
+ else:
114
+ end_date = pendulum.now()
115
+
116
+ # Ensure start_date.last_value is a pendulum.DateTime object
117
+ start_date_obj = ensure_pendulum_datetime(start_date) # type: ignore
118
+ start_date_str = start_date_obj.format("YYYY-MM-DD")
119
+
120
+ # Ensure end_date is a pendulum.DateTime object
121
+ end_date_obj = ensure_pendulum_datetime(end_date)
122
+ end_date_str = end_date_obj.format("YYYY-MM-DD")
123
+
124
+ # Compose the URL
125
+ url = f"{start_date_str}..{end_date_str}?"
126
+
127
+ if base_currency:
128
+ url += f"base={base_currency}"
129
+
130
+ # Fetch data from the API
131
+ data = get_path_with_retry(url)
132
+
133
+ # Extract base currency and rates from the API response
134
+ rates = data["rates"]
135
+
136
+ # Iterate over the rates dictionary (one entry per date)
137
+ for date, daily_rates in rates.items():
138
+ formatted_date = pendulum.parse(date)
139
+
140
+ # Add the base currency with a rate of 1.0
141
+ yield {
142
+ "date": formatted_date,
143
+ "currency_code": base_currency,
144
+ "rate": 1.0,
145
+ "base_currency": base_currency,
146
+ }
147
+
148
+ # Add all other currencies and their rates
149
+ for currency_code, rate in daily_rates.items():
150
+ yield {
151
+ "date": formatted_date,
152
+ "currency_code": currency_code,
153
+ "rate": rate,
154
+ "base_currency": base_currency,
155
+ }
156
+
157
+ return currencies, latest, exchange_rates
@@ -16,7 +16,7 @@ def get_path_with_retry(path: str) -> StrAny:
16
16
  return get_url_with_retry(f"{FRANKFURTER_API_URL}{path}")
17
17
 
18
18
 
19
- def validate_dates(start_date: datetime, end_date: datetime) -> None:
19
+ def validate_dates(start_date: datetime, end_date: datetime | None) -> None:
20
20
  current_date = pendulum.now()
21
21
 
22
22
  # Check if start_date is in the futurep
@@ -24,11 +24,11 @@ def validate_dates(start_date: datetime, end_date: datetime) -> None:
24
24
  raise ValueError("Interval-start cannot be in the future.")
25
25
 
26
26
  # Check if end_date is in the future
27
- if end_date > current_date:
27
+ if end_date is not None and end_date > current_date:
28
28
  raise ValueError("Interval-end cannot be in the future.")
29
29
 
30
30
  # Check if start_date is before end_date
31
- if start_date > end_date:
31
+ if end_date is not None and start_date > end_date:
32
32
  raise ValueError("Interval-end cannot be before interval-start.")
33
33
 
34
34
 
@@ -4,6 +4,8 @@ etc. to the database"""
4
4
  from typing import Any, Dict, Generator, Iterable, List, Optional
5
5
 
6
6
  import dlt
7
+ import pendulum
8
+ from dlt.common.time import ensure_pendulum_datetime
7
9
  from dlt.sources import DltResource
8
10
 
9
11
  from .freshdesk_client import FreshdeskClient
@@ -12,10 +14,13 @@ from .settings import DEFAULT_ENDPOINTS
12
14
 
13
15
  @dlt.source()
14
16
  def freshdesk_source(
15
- endpoints: Optional[List[str]] = None,
17
+ domain: str,
18
+ api_secret_key: str,
19
+ start_date: pendulum.DateTime,
20
+ end_date: Optional[pendulum.DateTime] = None,
16
21
  per_page: int = 100,
17
- domain: str = dlt.secrets.value,
18
- api_secret_key: str = dlt.secrets.value,
22
+ endpoints: Optional[List[str]] = None,
23
+ query: Optional[str] = None,
19
24
  ) -> Iterable[DltResource]:
20
25
  """
21
26
  Retrieves data from specified Freshdesk API endpoints.
@@ -39,7 +44,11 @@ def freshdesk_source(
39
44
  def incremental_resource(
40
45
  endpoint: str,
41
46
  updated_at: Optional[Any] = dlt.sources.incremental(
42
- "updated_at", initial_value="2022-01-01T00:00:00Z"
47
+ "updated_at",
48
+ initial_value=start_date.isoformat(),
49
+ end_value=end_date.isoformat() if end_date else None,
50
+ range_start="closed",
51
+ range_end="closed",
43
52
  ),
44
53
  ) -> Generator[Dict[Any, Any], Any, None]:
45
54
  """
@@ -48,15 +57,23 @@ def freshdesk_source(
48
57
  to ensure incremental loading.
49
58
  """
50
59
 
51
- # Retrieve the last updated timestamp to fetch only new or updated records.
52
- if updated_at is not None:
53
- updated_at = updated_at.last_value
60
+ if updated_at.last_value is not None:
61
+ start_date = ensure_pendulum_datetime(updated_at.last_value)
62
+ else:
63
+ start_date = start_date
64
+
65
+ if updated_at.end_value is not None:
66
+ end_date = ensure_pendulum_datetime(updated_at.end_value)
67
+ else:
68
+ end_date = pendulum.now(tz="UTC")
54
69
 
55
70
  # Use the FreshdeskClient instance to fetch paginated responses
56
71
  yield from freshdesk.paginated_response(
57
72
  endpoint=endpoint,
58
73
  per_page=per_page,
59
- updated_at=updated_at,
74
+ start_date=start_date,
75
+ end_date=end_date,
76
+ query=query,
60
77
  )
61
78
 
62
79
  # Set default endpoints if not provided