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,279 @@
1
+ """
2
+ Configuration settings and constants for Intercom API integration.
3
+ """
4
+
5
+ from datetime import datetime
6
+ from typing import Dict, List, Tuple
7
+
8
+ # API Version - REQUIRED for all requests
9
+ API_VERSION = "2.14"
10
+
11
+ # Default start date for incremental loading
12
+ DEFAULT_START_DATE = datetime(2020, 1, 1)
13
+
14
+ # Pagination settings
15
+ DEFAULT_PAGE_SIZE = 150
16
+ MAX_PAGE_SIZE = 150 # Intercom's maximum
17
+ SCROLL_EXPIRY_SECONDS = 60 # Scroll sessions expire after 1 minute
18
+
19
+ # Rate limiting settings
20
+ RATE_LIMIT_PER_10_SECONDS = 166
21
+ RATE_LIMIT_RETRY_AFTER_DEFAULT = 10
22
+
23
+ # Regional API endpoints
24
+ REGIONAL_ENDPOINTS = {
25
+ "us": "https://api.intercom.io",
26
+ "eu": "https://api.eu.intercom.io",
27
+ "au": "https://api.au.intercom.io",
28
+ }
29
+
30
+ # Resource configuration for automatic generation
31
+ # Format: resource_name -> config dict
32
+ RESOURCE_CONFIGS = {
33
+ # Search-based incremental resources
34
+ "contacts": {
35
+ "type": "search",
36
+ "incremental": True,
37
+ "transform_func": "transform_contact",
38
+ "columns": {
39
+ "custom_attributes": {"data_type": "json"},
40
+ "tags": {"data_type": "json"},
41
+ },
42
+ },
43
+ "conversations": {
44
+ "type": "search",
45
+ "incremental": True,
46
+ "transform_func": "transform_conversation",
47
+ "columns": {
48
+ "custom_attributes": {"data_type": "json"},
49
+ "tags": {"data_type": "json"},
50
+ },
51
+ },
52
+ # Pagination-based incremental resources
53
+ "companies": {
54
+ "type": "pagination",
55
+ "endpoint": "/companies",
56
+ "data_key": "data",
57
+ "pagination_type": "cursor",
58
+ "incremental": True,
59
+ "transform_func": "transform_company",
60
+ "params": {"per_page": 50},
61
+ "columns": {
62
+ "custom_attributes": {"data_type": "json"},
63
+ "tags": {"data_type": "json"},
64
+ },
65
+ },
66
+ "articles": {
67
+ "type": "pagination",
68
+ "endpoint": "/articles",
69
+ "data_key": "data",
70
+ "pagination_type": "cursor",
71
+ "incremental": True,
72
+ "transform_func": None,
73
+ "params": None,
74
+ "columns": {},
75
+ },
76
+ # Special case - tickets
77
+ "tickets": {
78
+ "type": "tickets",
79
+ "incremental": True,
80
+ "transform_func": None,
81
+ "columns": {
82
+ "ticket_attributes": {"data_type": "json"},
83
+ },
84
+ },
85
+ # Simple replace resources (non-incremental)
86
+ "tags": {
87
+ "type": "simple",
88
+ "endpoint": "/tags",
89
+ "data_key": "data",
90
+ "pagination_type": "simple",
91
+ "incremental": False,
92
+ "transform_func": None,
93
+ "columns": {},
94
+ },
95
+ "segments": {
96
+ "type": "simple",
97
+ "endpoint": "/segments",
98
+ "data_key": "segments",
99
+ "pagination_type": "cursor",
100
+ "incremental": False,
101
+ "transform_func": None,
102
+ "columns": {},
103
+ },
104
+ "teams": {
105
+ "type": "simple",
106
+ "endpoint": "/teams",
107
+ "data_key": "teams",
108
+ "pagination_type": "simple",
109
+ "incremental": False,
110
+ "transform_func": None,
111
+ "columns": {},
112
+ },
113
+ "admins": {
114
+ "type": "simple",
115
+ "endpoint": "/admins",
116
+ "data_key": "admins",
117
+ "pagination_type": "simple",
118
+ "incremental": False,
119
+ "transform_func": None,
120
+ "columns": {},
121
+ },
122
+ "data_attributes": {
123
+ "type": "simple",
124
+ "endpoint": "/data_attributes",
125
+ "data_key": "data",
126
+ "pagination_type": "cursor",
127
+ "incremental": False,
128
+ "transform_func": None,
129
+ "columns": {
130
+ "id": {"data_type": "bigint", "nullable": True},
131
+ },
132
+ },
133
+ }
134
+
135
+ # Core endpoints with their configuration (kept for backwards compatibility)
136
+ # Format: (endpoint_path, data_key, supports_incremental, pagination_type)
137
+ CORE_ENDPOINTS: Dict[str, Tuple[str, str, bool, str]] = {
138
+ "contacts": ("/contacts", "data", True, "cursor"),
139
+ "companies": ("/companies", "data", True, "cursor"),
140
+ "conversations": ("/conversations", "conversations", True, "cursor"),
141
+ "tickets": ("/tickets", "tickets", True, "cursor"),
142
+ "admins": ("/admins", "admins", False, "simple"),
143
+ "teams": ("/teams", "teams", False, "simple"),
144
+ "tags": ("/tags", "data", False, "simple"),
145
+ "segments": ("/segments", "segments", False, "cursor"),
146
+ "articles": ("/articles", "data", True, "cursor"),
147
+ "collections": ("/help_center/collections", "data", False, "cursor"),
148
+ "data_attributes": ("/data_attributes", "data", False, "cursor"),
149
+ }
150
+
151
+ # Incremental endpoints using search API
152
+ SEARCH_ENDPOINTS: Dict[str, str] = {
153
+ "contacts_search": "/contacts/search",
154
+ "companies_search": "/companies/search",
155
+ "conversations_search": "/conversations/search",
156
+ }
157
+
158
+ # Special endpoints requiring different handling
159
+ SCROLL_ENDPOINTS: List[str] = [
160
+ "companies", # Can use scroll for large exports
161
+ ]
162
+
163
+ # Event tracking endpoint
164
+ EVENTS_ENDPOINT = "/events"
165
+
166
+ # Ticket fields endpoint for custom field mapping
167
+ TICKET_FIELDS_ENDPOINT = "/ticket_types/{ticket_type_id}/attributes"
168
+
169
+ # Default fields to retrieve for each resource type
170
+ DEFAULT_CONTACT_FIELDS = [
171
+ "id",
172
+ "type",
173
+ "external_id",
174
+ "email",
175
+ "phone",
176
+ "name",
177
+ "created_at",
178
+ "updated_at",
179
+ "signed_up_at",
180
+ "last_seen_at",
181
+ "last_contacted_at",
182
+ "last_email_opened_at",
183
+ "last_email_clicked_at",
184
+ "browser",
185
+ "browser_language",
186
+ "browser_version",
187
+ "location",
188
+ "os",
189
+ "role",
190
+ "custom_attributes",
191
+ "tags",
192
+ "companies",
193
+ ]
194
+
195
+ DEFAULT_COMPANY_FIELDS = [
196
+ "id",
197
+ "type",
198
+ "company_id",
199
+ "name",
200
+ "plan",
201
+ "size",
202
+ "website",
203
+ "industry",
204
+ "created_at",
205
+ "updated_at",
206
+ "monthly_spend",
207
+ "session_count",
208
+ "user_count",
209
+ "custom_attributes",
210
+ "tags",
211
+ ]
212
+
213
+ DEFAULT_CONVERSATION_FIELDS = [
214
+ "id",
215
+ "type",
216
+ "created_at",
217
+ "updated_at",
218
+ "waiting_since",
219
+ "snoozed_until",
220
+ "state",
221
+ "open",
222
+ "read",
223
+ "priority",
224
+ "admin_assignee_id",
225
+ "team_assignee_id",
226
+ "tags",
227
+ "conversation_rating",
228
+ "source",
229
+ "contacts",
230
+ "teammates",
231
+ "custom_attributes",
232
+ "first_contact_reply",
233
+ "sla_applied",
234
+ "statistics",
235
+ "conversation_parts",
236
+ ]
237
+
238
+ DEFAULT_TICKET_FIELDS = [
239
+ "id",
240
+ "type",
241
+ "ticket_id",
242
+ "category",
243
+ "ticket_attributes",
244
+ "ticket_state",
245
+ "ticket_type",
246
+ "created_at",
247
+ "updated_at",
248
+ "ticket_parts",
249
+ "contacts",
250
+ "admin_assignee_id",
251
+ "team_assignee_id",
252
+ "open",
253
+ "snoozed_until",
254
+ ]
255
+
256
+ # Resources that support custom attributes
257
+ SUPPORTS_CUSTOM_ATTRIBUTES = [
258
+ "contacts",
259
+ "companies",
260
+ "conversations",
261
+ ]
262
+
263
+ # Maximum limits
264
+ MAX_CUSTOM_ATTRIBUTES_PER_RESOURCE = 100
265
+ MAX_EVENT_TYPES_PER_WORKSPACE = 120
266
+ MAX_CONVERSATION_PARTS = 500
267
+ MAX_SEARCH_RESULTS = 10000
268
+
269
+ # Field type mapping for custom attributes
270
+ INTERCOM_TO_DLT_TYPE_MAPPING = {
271
+ "string": "text",
272
+ "integer": "bigint",
273
+ "float": "double",
274
+ "boolean": "bool",
275
+ "date": "timestamp",
276
+ "datetime": "timestamp",
277
+ "object": "json",
278
+ "list": "json",
279
+ }
@@ -0,0 +1,340 @@
1
+ """
2
+ This source provides data extraction from Jira Cloud via the REST API v3.
3
+
4
+ It defines several functions to fetch data from different parts of Jira including
5
+ projects, issues, users, boards, sprints, and various configuration objects like
6
+ issue types, statuses, and priorities.
7
+ """
8
+
9
+ from typing import Any, Iterable, Optional
10
+
11
+ import dlt
12
+ from dlt.common.typing import TDataItem
13
+
14
+ from .helpers import get_client
15
+ from .settings import (
16
+ DEFAULT_PAGE_SIZE,
17
+ DEFAULT_START_DATE,
18
+ ISSUE_FIELDS,
19
+ )
20
+
21
+
22
+ @dlt.source
23
+ def jira_source() -> Any:
24
+ """
25
+ The main function that runs all the other functions to fetch data from Jira.
26
+
27
+ Returns:
28
+ Sequence[DltResource]: A sequence of DltResource objects containing the fetched data.
29
+ """
30
+ return [
31
+ projects,
32
+ issues,
33
+ users,
34
+ issue_types,
35
+ statuses,
36
+ priorities,
37
+ resolutions,
38
+ project_versions,
39
+ project_components,
40
+ events,
41
+ ]
42
+
43
+
44
+ @dlt.resource(write_disposition="replace")
45
+ def projects(
46
+ base_url: str = dlt.secrets.value,
47
+ email: str = dlt.secrets.value,
48
+ api_token: str = dlt.secrets.value,
49
+ expand: Optional[str] = None,
50
+ recent: Optional[int] = None,
51
+ ) -> Iterable[TDataItem]:
52
+ """
53
+ Fetches and returns a list of projects from Jira.
54
+
55
+ Args:
56
+ base_url (str): Jira instance URL (e.g., https://your-domain.atlassian.net)
57
+ email (str): User email for authentication
58
+ api_token (str): API token for authentication
59
+ expand (str): Comma-separated list of fields to expand
60
+ recent (int): Number of recent projects to return
61
+
62
+ Yields:
63
+ dict: The project data.
64
+ """
65
+ client = get_client(base_url, email, api_token)
66
+ yield from client.get_projects(expand=expand, recent=recent)
67
+
68
+
69
+ @dlt.resource(
70
+ write_disposition="merge",
71
+ primary_key="id",
72
+ max_table_nesting=2,
73
+ )
74
+ def issues(
75
+ base_url: str = dlt.secrets.value,
76
+ email: str = dlt.secrets.value,
77
+ api_token: str = dlt.secrets.value,
78
+ jql: str = "order by updated DESC",
79
+ fields: Optional[str] = None,
80
+ expand: Optional[str] = None,
81
+ max_results: Optional[int] = None,
82
+ updated: dlt.sources.incremental[str] = dlt.sources.incremental(
83
+ "fields.updated",
84
+ initial_value=DEFAULT_START_DATE,
85
+ range_end="closed",
86
+ range_start="closed",
87
+ ),
88
+ ) -> Iterable[TDataItem]:
89
+ """
90
+ Fetches issues from Jira using JQL search.
91
+
92
+ Args:
93
+ base_url (str): Jira instance URL
94
+ email (str): User email for authentication
95
+ api_token (str): API token for authentication
96
+ jql (str): JQL query string
97
+ fields (str): Comma-separated list of fields to return
98
+ expand (str): Comma-separated list of fields to expand
99
+ max_results (int): Maximum number of results to return
100
+ updated (str): The date from which to fetch updated issues
101
+
102
+ Yields:
103
+ dict: The issue data.
104
+ """
105
+ client = get_client(base_url, email, api_token)
106
+
107
+ # Build JQL with incremental filter
108
+ incremental_jql = jql
109
+ if updated.start_value:
110
+ date_filter = f"updated >= '{updated.start_value}'"
111
+
112
+ # Check if JQL has ORDER BY clause and handle it properly
113
+ jql_upper = jql.upper()
114
+ if "ORDER BY" in jql_upper:
115
+ # Split at ORDER BY and add filter before it
116
+ order_by_index = jql_upper.find("ORDER BY")
117
+ main_query = jql[:order_by_index].strip()
118
+ order_clause = jql[order_by_index:].strip()
119
+
120
+ if main_query and (
121
+ "WHERE" in main_query.upper()
122
+ or "AND" in main_query.upper()
123
+ or "OR" in main_query.upper()
124
+ ):
125
+ incremental_jql = f"({main_query}) AND {date_filter} {order_clause}"
126
+ else:
127
+ if main_query:
128
+ incremental_jql = f"{main_query} AND {date_filter} {order_clause}"
129
+ else:
130
+ incremental_jql = f"{date_filter} {order_clause}"
131
+ else:
132
+ # No ORDER BY clause, use original logic
133
+ if "WHERE" in jql_upper or "AND" in jql_upper or "OR" in jql_upper:
134
+ incremental_jql = f"({jql}) AND {date_filter}"
135
+ else:
136
+ incremental_jql = f"{jql} AND {date_filter}"
137
+
138
+ # Use default fields if not specified
139
+ if fields is None:
140
+ fields = ",".join(ISSUE_FIELDS)
141
+
142
+ yield from client.search_issues(
143
+ jql=incremental_jql, fields=fields, expand=expand, max_results=max_results
144
+ )
145
+
146
+
147
+ @dlt.resource(write_disposition="replace")
148
+ def users(
149
+ base_url: str = dlt.secrets.value,
150
+ email: str = dlt.secrets.value,
151
+ api_token: str = dlt.secrets.value,
152
+ username: Optional[str] = None,
153
+ account_id: Optional[str] = None,
154
+ max_results: int = DEFAULT_PAGE_SIZE,
155
+ ) -> Iterable[TDataItem]:
156
+ """
157
+ Fetches users from Jira.
158
+
159
+ Args:
160
+ base_url (str): Jira instance URL
161
+ email (str): User email for authentication
162
+ api_token (str): API token for authentication
163
+ username (str): Username to search for
164
+ account_id (str): Account ID to search for
165
+ max_results (int): Maximum results per page
166
+
167
+ Yields:
168
+ dict: The user data.
169
+ """
170
+ client = get_client(base_url, email, api_token)
171
+ yield from client.get_users(
172
+ username=username, account_id=account_id, max_results=max_results
173
+ )
174
+
175
+
176
+ @dlt.resource(write_disposition="replace")
177
+ def issue_types(
178
+ base_url: str = dlt.secrets.value,
179
+ email: str = dlt.secrets.value,
180
+ api_token: str = dlt.secrets.value,
181
+ ) -> Iterable[TDataItem]:
182
+ """
183
+ Fetches all issue types from Jira.
184
+
185
+ Args:
186
+ base_url (str): Jira instance URL
187
+ email (str): User email for authentication
188
+ api_token (str): API token for authentication
189
+
190
+ Yields:
191
+ dict: The issue type data.
192
+ """
193
+ client = get_client(base_url, email, api_token)
194
+ yield from client.get_issue_types()
195
+
196
+
197
+ @dlt.resource(write_disposition="replace")
198
+ def statuses(
199
+ base_url: str = dlt.secrets.value,
200
+ email: str = dlt.secrets.value,
201
+ api_token: str = dlt.secrets.value,
202
+ ) -> Iterable[TDataItem]:
203
+ """
204
+ Fetches all statuses from Jira.
205
+
206
+ Args:
207
+ base_url (str): Jira instance URL
208
+ email (str): User email for authentication
209
+ api_token (str): API token for authentication
210
+
211
+ Yields:
212
+ dict: The status data.
213
+ """
214
+ client = get_client(base_url, email, api_token)
215
+ yield from client.get_statuses()
216
+
217
+
218
+ @dlt.resource(write_disposition="replace")
219
+ def priorities(
220
+ base_url: str = dlt.secrets.value,
221
+ email: str = dlt.secrets.value,
222
+ api_token: str = dlt.secrets.value,
223
+ ) -> Iterable[TDataItem]:
224
+ """
225
+ Fetches all priorities from Jira.
226
+
227
+ Args:
228
+ base_url (str): Jira instance URL
229
+ email (str): User email for authentication
230
+ api_token (str): API token for authentication
231
+
232
+ Yields:
233
+ dict: The priority data.
234
+ """
235
+ client = get_client(base_url, email, api_token)
236
+ yield from client.get_priorities()
237
+
238
+
239
+ @dlt.resource(write_disposition="replace")
240
+ def resolutions(
241
+ base_url: str = dlt.secrets.value,
242
+ email: str = dlt.secrets.value,
243
+ api_token: str = dlt.secrets.value,
244
+ ) -> Iterable[TDataItem]:
245
+ """
246
+ Fetches all resolutions from Jira.
247
+
248
+ Args:
249
+ base_url (str): Jira instance URL
250
+ email (str): User email for authentication
251
+ api_token (str): API token for authentication
252
+
253
+ Yields:
254
+ dict: The resolution data.
255
+ """
256
+ client = get_client(base_url, email, api_token)
257
+ yield from client.get_resolutions()
258
+
259
+
260
+ @dlt.transformer(
261
+ data_from=projects,
262
+ write_disposition="replace",
263
+ )
264
+ @dlt.defer
265
+ def project_versions(
266
+ project: TDataItem,
267
+ base_url: str = dlt.secrets.value,
268
+ email: str = dlt.secrets.value,
269
+ api_token: str = dlt.secrets.value,
270
+ ) -> Iterable[TDataItem]:
271
+ """
272
+ Fetches versions for each project from Jira.
273
+
274
+ Args:
275
+ project (dict): The project data.
276
+ base_url (str): Jira instance URL
277
+ email (str): User email for authentication
278
+ api_token (str): API token for authentication
279
+
280
+ Returns:
281
+ list[dict]: The version data for the given project.
282
+ """
283
+ client = get_client(base_url, email, api_token)
284
+ project_key = project.get("key")
285
+ if not project_key:
286
+ return []
287
+
288
+ return list(client.get_project_versions(project_key))
289
+
290
+
291
+ @dlt.transformer(
292
+ data_from=projects,
293
+ write_disposition="replace",
294
+ )
295
+ @dlt.defer
296
+ def project_components(
297
+ project: TDataItem,
298
+ base_url: str = dlt.secrets.value,
299
+ email: str = dlt.secrets.value,
300
+ api_token: str = dlt.secrets.value,
301
+ ) -> Iterable[TDataItem]:
302
+ """
303
+ Fetches components for each project from Jira.
304
+
305
+ Args:
306
+ project (dict): The project data.
307
+ base_url (str): Jira instance URL
308
+ email (str): User email for authentication
309
+ api_token (str): API token for authentication
310
+
311
+ Returns:
312
+ list[dict]: The component data for the given project.
313
+ """
314
+ client = get_client(base_url, email, api_token)
315
+ project_key = project.get("key")
316
+ if not project_key:
317
+ return []
318
+
319
+ return list(client.get_project_components(project_key))
320
+
321
+
322
+ @dlt.resource(write_disposition="replace")
323
+ def events(
324
+ base_url: str = dlt.secrets.value,
325
+ email: str = dlt.secrets.value,
326
+ api_token: str = dlt.secrets.value,
327
+ ) -> Iterable[TDataItem]:
328
+ """
329
+ Fetches all event types from Jira (e.g., Issue Created, Issue Updated, etc.).
330
+
331
+ Args:
332
+ base_url (str): Jira instance URL
333
+ email (str): User email for authentication
334
+ api_token (str): API token for authentication
335
+
336
+ Yields:
337
+ dict: The event data.
338
+ """
339
+ client = get_client(base_url, email, api_token)
340
+ yield from client.get_events()