hotglue-singer-sdk 1.0.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.
- hotglue_singer_sdk/__init__.py +34 -0
- hotglue_singer_sdk/authenticators.py +554 -0
- hotglue_singer_sdk/cli/__init__.py +1 -0
- hotglue_singer_sdk/cli/common_options.py +37 -0
- hotglue_singer_sdk/configuration/__init__.py +1 -0
- hotglue_singer_sdk/configuration/_dict_config.py +101 -0
- hotglue_singer_sdk/exceptions.py +52 -0
- hotglue_singer_sdk/helpers/__init__.py +1 -0
- hotglue_singer_sdk/helpers/_catalog.py +122 -0
- hotglue_singer_sdk/helpers/_classproperty.py +18 -0
- hotglue_singer_sdk/helpers/_compat.py +15 -0
- hotglue_singer_sdk/helpers/_flattening.py +374 -0
- hotglue_singer_sdk/helpers/_schema.py +100 -0
- hotglue_singer_sdk/helpers/_secrets.py +41 -0
- hotglue_singer_sdk/helpers/_simpleeval.py +678 -0
- hotglue_singer_sdk/helpers/_singer.py +280 -0
- hotglue_singer_sdk/helpers/_state.py +282 -0
- hotglue_singer_sdk/helpers/_typing.py +231 -0
- hotglue_singer_sdk/helpers/_util.py +27 -0
- hotglue_singer_sdk/helpers/capabilities.py +240 -0
- hotglue_singer_sdk/helpers/jsonpath.py +39 -0
- hotglue_singer_sdk/io_base.py +134 -0
- hotglue_singer_sdk/mapper.py +691 -0
- hotglue_singer_sdk/mapper_base.py +156 -0
- hotglue_singer_sdk/plugin_base.py +415 -0
- hotglue_singer_sdk/py.typed +0 -0
- hotglue_singer_sdk/sinks/__init__.py +14 -0
- hotglue_singer_sdk/sinks/batch.py +90 -0
- hotglue_singer_sdk/sinks/core.py +412 -0
- hotglue_singer_sdk/sinks/record.py +66 -0
- hotglue_singer_sdk/sinks/sql.py +299 -0
- hotglue_singer_sdk/streams/__init__.py +14 -0
- hotglue_singer_sdk/streams/core.py +1294 -0
- hotglue_singer_sdk/streams/graphql.py +74 -0
- hotglue_singer_sdk/streams/rest.py +611 -0
- hotglue_singer_sdk/streams/sql.py +1023 -0
- hotglue_singer_sdk/tap_base.py +580 -0
- hotglue_singer_sdk/target_base.py +554 -0
- hotglue_singer_sdk/target_sdk/__init__.py +0 -0
- hotglue_singer_sdk/target_sdk/auth.py +124 -0
- hotglue_singer_sdk/target_sdk/client.py +286 -0
- hotglue_singer_sdk/target_sdk/common.py +13 -0
- hotglue_singer_sdk/target_sdk/lambda.py +121 -0
- hotglue_singer_sdk/target_sdk/rest.py +108 -0
- hotglue_singer_sdk/target_sdk/sinks.py +16 -0
- hotglue_singer_sdk/target_sdk/target.py +570 -0
- hotglue_singer_sdk/target_sdk/target_base.py +627 -0
- hotglue_singer_sdk/testing.py +198 -0
- hotglue_singer_sdk/typing.py +603 -0
- hotglue_singer_sdk-1.0.2.dist-info/METADATA +53 -0
- hotglue_singer_sdk-1.0.2.dist-info/RECORD +53 -0
- hotglue_singer_sdk-1.0.2.dist-info/WHEEL +4 -0
- hotglue_singer_sdk-1.0.2.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
"""Classes and functions to streamline JSONSchema typing.
|
|
2
|
+
|
|
3
|
+
Usage example:
|
|
4
|
+
--------------
|
|
5
|
+
.. code-block:: python
|
|
6
|
+
|
|
7
|
+
jsonschema = PropertiesList(
|
|
8
|
+
Property("id", IntegerType, required=True),
|
|
9
|
+
Property("name", StringType),
|
|
10
|
+
Property("tags", ArrayType(StringType)),
|
|
11
|
+
Property("ratio", NumberType),
|
|
12
|
+
Property("days_active", IntegerType),
|
|
13
|
+
Property("updated_on", DateTimeType),
|
|
14
|
+
Property("is_deleted", BooleanType),
|
|
15
|
+
Property(
|
|
16
|
+
"author",
|
|
17
|
+
ObjectType(
|
|
18
|
+
Property("id", StringType),
|
|
19
|
+
Property("name", StringType),
|
|
20
|
+
)
|
|
21
|
+
),
|
|
22
|
+
Property(
|
|
23
|
+
"groups",
|
|
24
|
+
ArrayType(
|
|
25
|
+
ObjectType(
|
|
26
|
+
Property("id", StringType),
|
|
27
|
+
Property("name", StringType),
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
),
|
|
31
|
+
).to_dict()
|
|
32
|
+
|
|
33
|
+
Note:
|
|
34
|
+
-----
|
|
35
|
+
- These helpers are designed to output json in the traditional Singer dialect.
|
|
36
|
+
- Due to the expansive set of capabilities within the JSONSchema spec, there may be
|
|
37
|
+
other valid implementations which are not syntactically identical to those generated
|
|
38
|
+
here.
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
import sys
|
|
45
|
+
from typing import Generic, Mapping, TypeVar, Union, cast
|
|
46
|
+
|
|
47
|
+
import sqlalchemy
|
|
48
|
+
from jsonschema import validators
|
|
49
|
+
|
|
50
|
+
from hotglue_singer_sdk.helpers._classproperty import classproperty
|
|
51
|
+
from hotglue_singer_sdk.helpers._typing import append_type, get_datelike_property_type
|
|
52
|
+
|
|
53
|
+
if sys.version_info >= (3, 10):
|
|
54
|
+
from typing import TypeAlias
|
|
55
|
+
else:
|
|
56
|
+
from typing_extensions import TypeAlias
|
|
57
|
+
|
|
58
|
+
__all__ = [
|
|
59
|
+
"extend_validator_with_defaults",
|
|
60
|
+
"to_jsonschema_type",
|
|
61
|
+
"to_sql_type",
|
|
62
|
+
"JSONTypeHelper",
|
|
63
|
+
"StringType",
|
|
64
|
+
"DateTimeType",
|
|
65
|
+
"TimeType",
|
|
66
|
+
"DateType",
|
|
67
|
+
"DurationType",
|
|
68
|
+
"EmailType",
|
|
69
|
+
"HostnameType",
|
|
70
|
+
"IPv4Type",
|
|
71
|
+
"IPv6Type",
|
|
72
|
+
"UUIDType",
|
|
73
|
+
"URIType",
|
|
74
|
+
"URIReferenceType",
|
|
75
|
+
"URITemplateType",
|
|
76
|
+
"JSONPointerType",
|
|
77
|
+
"RelativeJSONPointerType",
|
|
78
|
+
"RegexType",
|
|
79
|
+
"BooleanType",
|
|
80
|
+
"IntegerType",
|
|
81
|
+
"NumberType",
|
|
82
|
+
"ArrayType",
|
|
83
|
+
"Property",
|
|
84
|
+
"ObjectType",
|
|
85
|
+
"CustomType",
|
|
86
|
+
"PropertiesList",
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
_JsonValue: TypeAlias = Union[
|
|
90
|
+
str,
|
|
91
|
+
int,
|
|
92
|
+
float,
|
|
93
|
+
bool,
|
|
94
|
+
list,
|
|
95
|
+
dict,
|
|
96
|
+
None,
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def extend_validator_with_defaults(validator_class): # noqa
|
|
101
|
+
"""Fill in defaults, before validating with the provided JSON Schema Validator.
|
|
102
|
+
|
|
103
|
+
See https://python-jsonschema.readthedocs.io/en/latest/faq/#why-doesn-t-my-schema-s-default-property-set-the-default-on-my-instance # noqa
|
|
104
|
+
for details.
|
|
105
|
+
"""
|
|
106
|
+
validate_properties = validator_class.VALIDATORS["properties"]
|
|
107
|
+
|
|
108
|
+
def set_defaults(validator, properties, instance, schema): # noqa
|
|
109
|
+
for property, subschema in properties.items():
|
|
110
|
+
if "default" in subschema:
|
|
111
|
+
instance.setdefault(property, subschema["default"])
|
|
112
|
+
|
|
113
|
+
yield from validate_properties(
|
|
114
|
+
validator,
|
|
115
|
+
properties,
|
|
116
|
+
instance,
|
|
117
|
+
schema,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return validators.extend(
|
|
121
|
+
validator_class,
|
|
122
|
+
{"properties": set_defaults},
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class JSONTypeHelper:
|
|
127
|
+
"""Type helper base class for JSONSchema types."""
|
|
128
|
+
|
|
129
|
+
@classproperty
|
|
130
|
+
def type_dict(cls) -> dict:
|
|
131
|
+
"""Return dict describing the type.
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
NotImplementedError: If the derived class does not override this method.
|
|
135
|
+
"""
|
|
136
|
+
raise NotImplementedError()
|
|
137
|
+
|
|
138
|
+
def to_dict(self) -> dict:
|
|
139
|
+
"""Convert to dictionary.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
A JSON Schema dictionary describing the object.
|
|
143
|
+
"""
|
|
144
|
+
return cast(dict, self.type_dict)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class StringType(JSONTypeHelper):
|
|
148
|
+
"""String type."""
|
|
149
|
+
|
|
150
|
+
string_format: str | None = None
|
|
151
|
+
"""String format.
|
|
152
|
+
|
|
153
|
+
See the [formats built into the JSON Schema\
|
|
154
|
+
specification](https://json-schema.org/understanding-json-schema/reference/string.html#built-in-formats).
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
A string describing the format.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
@classproperty
|
|
161
|
+
def _format(cls) -> dict:
|
|
162
|
+
return {"format": cls.string_format} if cls.string_format else {}
|
|
163
|
+
|
|
164
|
+
@classproperty
|
|
165
|
+
def type_dict(cls) -> dict:
|
|
166
|
+
"""Get type dictionary.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
A dictionary describing the type.
|
|
170
|
+
"""
|
|
171
|
+
return {
|
|
172
|
+
"type": ["string"],
|
|
173
|
+
**cls._format,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class DateTimeType(StringType):
|
|
178
|
+
"""DateTime type.
|
|
179
|
+
|
|
180
|
+
Example: `2018-11-13T20:20:39+00:00`
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
string_format = "date-time"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class TimeType(StringType):
|
|
187
|
+
"""Time type.
|
|
188
|
+
|
|
189
|
+
Example: `20:20:39+00:00`
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
string_format = "time"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class DateType(StringType):
|
|
196
|
+
"""Date type.
|
|
197
|
+
|
|
198
|
+
Example: `2018-11-13`
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
string_format = "date"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class DurationType(StringType):
|
|
205
|
+
"""Duration type.
|
|
206
|
+
|
|
207
|
+
Example: `P3D`
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
string_format = "duration"
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class EmailType(StringType):
|
|
214
|
+
"""Email type."""
|
|
215
|
+
|
|
216
|
+
string_format = "email"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class HostnameType(StringType):
|
|
220
|
+
"""Hostname type."""
|
|
221
|
+
|
|
222
|
+
string_format = "hostname"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class IPv4Type(StringType):
|
|
226
|
+
"""IPv4 address type."""
|
|
227
|
+
|
|
228
|
+
string_format = "ipv4"
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class IPv6Type(StringType):
|
|
232
|
+
"""IPv6 type."""
|
|
233
|
+
|
|
234
|
+
string_format = "ipv6"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class UUIDType(StringType):
|
|
238
|
+
"""UUID type.
|
|
239
|
+
|
|
240
|
+
Example: `3e4666bf-d5e5-4aa7-b8ce-cefe41c7568a`
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
string_format = "uuid"
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class URIType(StringType):
|
|
247
|
+
"""URI type."""
|
|
248
|
+
|
|
249
|
+
string_format = "uri"
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class URIReferenceType(StringType):
|
|
253
|
+
"""URIReference type."""
|
|
254
|
+
|
|
255
|
+
string_format = "uri-reference"
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class URITemplateType(StringType):
|
|
259
|
+
"""URITemplate type."""
|
|
260
|
+
|
|
261
|
+
string_format = "uri-template"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class JSONPointerType(StringType):
|
|
265
|
+
"""JSONPointer type."""
|
|
266
|
+
|
|
267
|
+
string_format = "json-pointer"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class RelativeJSONPointerType(StringType):
|
|
271
|
+
"""RelativeJSONPointer type."""
|
|
272
|
+
|
|
273
|
+
string_format = "relative-json-pointer"
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class RegexType(StringType):
|
|
277
|
+
"""Regex type."""
|
|
278
|
+
|
|
279
|
+
string_format = "regex"
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class BooleanType(JSONTypeHelper):
|
|
283
|
+
"""Boolean type."""
|
|
284
|
+
|
|
285
|
+
@classproperty
|
|
286
|
+
def type_dict(cls) -> dict:
|
|
287
|
+
"""Get type dictionary.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
A dictionary describing the type.
|
|
291
|
+
"""
|
|
292
|
+
return {"type": ["boolean"]}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class IntegerType(JSONTypeHelper):
|
|
296
|
+
"""Integer type."""
|
|
297
|
+
|
|
298
|
+
@classproperty
|
|
299
|
+
def type_dict(cls) -> dict:
|
|
300
|
+
"""Get type dictionary.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
A dictionary describing the type.
|
|
304
|
+
"""
|
|
305
|
+
return {"type": ["integer"]}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class NumberType(JSONTypeHelper):
|
|
309
|
+
"""Number type."""
|
|
310
|
+
|
|
311
|
+
@classproperty
|
|
312
|
+
def type_dict(cls) -> dict:
|
|
313
|
+
"""Get type dictionary.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
A dictionary describing the type.
|
|
317
|
+
"""
|
|
318
|
+
return {"type": ["number"]}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
W = TypeVar("W", bound=JSONTypeHelper)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class ArrayType(JSONTypeHelper, Generic[W]):
|
|
325
|
+
"""Array type."""
|
|
326
|
+
|
|
327
|
+
def __init__(self, wrapped_type: W | type[W]) -> None:
|
|
328
|
+
"""Initialize Array type with wrapped inner type.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
wrapped_type: JSON Schema item type inside the array.
|
|
332
|
+
"""
|
|
333
|
+
self.wrapped_type = wrapped_type
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def type_dict(self) -> dict: # type: ignore # OK: @classproperty vs @property
|
|
337
|
+
"""Get type dictionary.
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
A dictionary describing the type.
|
|
341
|
+
"""
|
|
342
|
+
return {"type": "array", "items": self.wrapped_type.type_dict}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class Property(JSONTypeHelper, Generic[W]):
|
|
346
|
+
"""Generic Property. Should be nested within a `PropertiesList`."""
|
|
347
|
+
|
|
348
|
+
def __init__(
|
|
349
|
+
self,
|
|
350
|
+
name: str,
|
|
351
|
+
wrapped: W | type[W],
|
|
352
|
+
required: bool = False,
|
|
353
|
+
default: _JsonValue = None,
|
|
354
|
+
description: str = None,
|
|
355
|
+
) -> None:
|
|
356
|
+
"""Initialize Property object.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
name: Property name.
|
|
360
|
+
wrapped: JSON Schema type of the property.
|
|
361
|
+
required: Whether this is a required property.
|
|
362
|
+
default: Default value in the JSON Schema.
|
|
363
|
+
description: Long-text property description.
|
|
364
|
+
"""
|
|
365
|
+
self.name = name
|
|
366
|
+
self.wrapped = wrapped
|
|
367
|
+
self.optional = not required
|
|
368
|
+
self.default = default
|
|
369
|
+
self.description = description
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
def type_dict(self) -> dict: # type: ignore # OK: @classproperty vs @property
|
|
373
|
+
"""Get type dictionary.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
A dictionary describing the type.
|
|
377
|
+
|
|
378
|
+
Raises:
|
|
379
|
+
ValueError: If the type dict is not valid.
|
|
380
|
+
"""
|
|
381
|
+
wrapped = self.wrapped
|
|
382
|
+
|
|
383
|
+
if isinstance(wrapped, type) and not isinstance(wrapped.type_dict, Mapping):
|
|
384
|
+
raise ValueError(
|
|
385
|
+
f"Type dict for {wrapped} is not defined. "
|
|
386
|
+
+ "Try instantiating it with a nested type such as "
|
|
387
|
+
+ f"{wrapped.__name__}(StringType)."
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
return cast(dict, wrapped.type_dict)
|
|
391
|
+
|
|
392
|
+
def to_dict(self) -> dict:
|
|
393
|
+
"""Return a dict mapping the property name to its definition.
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
A JSON Schema dictionary describing the object.
|
|
397
|
+
"""
|
|
398
|
+
type_dict = self.type_dict
|
|
399
|
+
if self.optional:
|
|
400
|
+
type_dict = append_type(type_dict, "null")
|
|
401
|
+
if self.default is not None:
|
|
402
|
+
type_dict.update({"default": self.default})
|
|
403
|
+
if self.description:
|
|
404
|
+
type_dict.update({"description": self.description})
|
|
405
|
+
return {self.name: type_dict}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class ObjectType(JSONTypeHelper):
|
|
409
|
+
"""Object type, which wraps one or more named properties."""
|
|
410
|
+
|
|
411
|
+
def __init__(
|
|
412
|
+
self,
|
|
413
|
+
*properties: Property,
|
|
414
|
+
additional_properties: W | type[W] | None = None,
|
|
415
|
+
) -> None:
|
|
416
|
+
"""Initialize ObjectType from its list of properties.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
properties: Zero or more attributes for this JSON object.
|
|
420
|
+
additional_properties: A schema to match against unnamed properties in
|
|
421
|
+
this object.
|
|
422
|
+
"""
|
|
423
|
+
self.wrapped: list[Property] = list(properties)
|
|
424
|
+
self.additional_properties = additional_properties
|
|
425
|
+
|
|
426
|
+
@property
|
|
427
|
+
def type_dict(self) -> dict: # type: ignore # OK: @classproperty vs @property
|
|
428
|
+
"""Get type dictionary.
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
A dictionary describing the type.
|
|
432
|
+
"""
|
|
433
|
+
merged_props = {}
|
|
434
|
+
required = []
|
|
435
|
+
for w in self.wrapped:
|
|
436
|
+
merged_props.update(w.to_dict())
|
|
437
|
+
if not w.optional:
|
|
438
|
+
required.append(w.name)
|
|
439
|
+
result = {"type": "object", "properties": merged_props}
|
|
440
|
+
|
|
441
|
+
if required:
|
|
442
|
+
result["required"] = required
|
|
443
|
+
|
|
444
|
+
if self.additional_properties:
|
|
445
|
+
result["additionalProperties"] = self.additional_properties.type_dict
|
|
446
|
+
|
|
447
|
+
return result
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
class CustomType(JSONTypeHelper):
|
|
451
|
+
"""Accepts an arbitrary JSON Schema dictionary."""
|
|
452
|
+
|
|
453
|
+
def __init__(self, jsonschema_type_dict: dict) -> None:
|
|
454
|
+
"""Initialize JSONTypeHelper by importing an existing JSON Schema type.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
jsonschema_type_dict: TODO
|
|
458
|
+
"""
|
|
459
|
+
self._jsonschema_type_dict = jsonschema_type_dict
|
|
460
|
+
|
|
461
|
+
@property
|
|
462
|
+
def type_dict(self) -> dict: # type: ignore # OK: @classproperty vs @property
|
|
463
|
+
"""Get type dictionary.
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
A dictionary describing the type.
|
|
467
|
+
"""
|
|
468
|
+
return self._jsonschema_type_dict
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
class PropertiesList(ObjectType):
|
|
472
|
+
"""Properties list. A convenience wrapper around the ObjectType class."""
|
|
473
|
+
|
|
474
|
+
def items(self) -> list[tuple[str, Property]]:
|
|
475
|
+
"""Get wrapped properties.
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
List of (name, property) tuples.
|
|
479
|
+
"""
|
|
480
|
+
return [(p.name, p) for p in self.wrapped]
|
|
481
|
+
|
|
482
|
+
def append(self, property: Property) -> None:
|
|
483
|
+
"""Append a property to the property list.
|
|
484
|
+
|
|
485
|
+
Args:
|
|
486
|
+
property: Property to add
|
|
487
|
+
"""
|
|
488
|
+
self.wrapped.append(property)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def to_jsonschema_type(
|
|
492
|
+
from_type: str | sqlalchemy.types.TypeEngine | type[sqlalchemy.types.TypeEngine],
|
|
493
|
+
) -> dict:
|
|
494
|
+
"""Return the JSON Schema dict that describes the sql type.
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
from_type: The SQL type as a string or as a TypeEngine. If a TypeEngine is
|
|
498
|
+
provided, it may be provided as a class or a specific object instance.
|
|
499
|
+
|
|
500
|
+
Raises:
|
|
501
|
+
ValueError: If the `from_type` value is not of type `str` or `TypeEngine`.
|
|
502
|
+
|
|
503
|
+
Returns:
|
|
504
|
+
A compatible JSON Schema type definition.
|
|
505
|
+
"""
|
|
506
|
+
sqltype_lookup: dict[str, dict] = {
|
|
507
|
+
# NOTE: This is an ordered mapping, with earlier mappings taking precedence.
|
|
508
|
+
# If the SQL-provided type contains the type name on the left, the mapping
|
|
509
|
+
# will return the respective singer type.
|
|
510
|
+
"timestamp": DateTimeType.type_dict,
|
|
511
|
+
"datetime": DateTimeType.type_dict,
|
|
512
|
+
"date": DateType.type_dict,
|
|
513
|
+
"int": IntegerType.type_dict,
|
|
514
|
+
"number": NumberType.type_dict,
|
|
515
|
+
"decimal": NumberType.type_dict,
|
|
516
|
+
"double": NumberType.type_dict,
|
|
517
|
+
"float": NumberType.type_dict,
|
|
518
|
+
"string": StringType.type_dict,
|
|
519
|
+
"text": StringType.type_dict,
|
|
520
|
+
"char": StringType.type_dict,
|
|
521
|
+
"bool": BooleanType.type_dict,
|
|
522
|
+
"variant": StringType.type_dict,
|
|
523
|
+
}
|
|
524
|
+
if isinstance(from_type, str):
|
|
525
|
+
type_name = from_type
|
|
526
|
+
elif isinstance(from_type, sqlalchemy.types.TypeEngine):
|
|
527
|
+
type_name = type(from_type).__name__
|
|
528
|
+
elif isinstance(from_type, type) and issubclass(
|
|
529
|
+
from_type, sqlalchemy.types.TypeEngine
|
|
530
|
+
):
|
|
531
|
+
type_name = from_type.__name__
|
|
532
|
+
else:
|
|
533
|
+
raise ValueError("Expected `str` or a SQLAlchemy `TypeEngine` object or type.")
|
|
534
|
+
|
|
535
|
+
# Look for the type name within the known SQL type names:
|
|
536
|
+
for sqltype, jsonschema_type in sqltype_lookup.items():
|
|
537
|
+
if sqltype.lower() in type_name.lower():
|
|
538
|
+
return jsonschema_type
|
|
539
|
+
|
|
540
|
+
return sqltype_lookup["string"] # safe failover to str
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def _jsonschema_type_check(jsonschema_type: dict, type_check: tuple[str]) -> bool:
|
|
544
|
+
"""Return True if the jsonschema_type supports the provided type.
|
|
545
|
+
|
|
546
|
+
Args:
|
|
547
|
+
jsonschema_type: The type dict.
|
|
548
|
+
type_check: A tuple of type strings to look for.
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
True if the schema suports the type.
|
|
552
|
+
"""
|
|
553
|
+
if "type" in jsonschema_type:
|
|
554
|
+
if isinstance(jsonschema_type["type"], (list, tuple)):
|
|
555
|
+
for t in jsonschema_type["type"]:
|
|
556
|
+
if t in type_check:
|
|
557
|
+
return True
|
|
558
|
+
else:
|
|
559
|
+
if jsonschema_type.get("type") in type_check:
|
|
560
|
+
return True
|
|
561
|
+
|
|
562
|
+
if any(t in type_check for t in jsonschema_type.get("anyOf", ())):
|
|
563
|
+
return True
|
|
564
|
+
|
|
565
|
+
return False
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def to_sql_type(jsonschema_type: dict) -> sqlalchemy.types.TypeEngine:
|
|
569
|
+
"""Convert JSON Schema type to a SQL type.
|
|
570
|
+
|
|
571
|
+
Args:
|
|
572
|
+
jsonschema_type: The JSON Schema object.
|
|
573
|
+
|
|
574
|
+
Returns:
|
|
575
|
+
The SQL type.
|
|
576
|
+
"""
|
|
577
|
+
if _jsonschema_type_check(jsonschema_type, ("string",)):
|
|
578
|
+
datelike_type = get_datelike_property_type(jsonschema_type)
|
|
579
|
+
if datelike_type:
|
|
580
|
+
if datelike_type == "date-time":
|
|
581
|
+
return cast(sqlalchemy.types.TypeEngine, sqlalchemy.types.DATETIME())
|
|
582
|
+
if datelike_type in "time":
|
|
583
|
+
return cast(sqlalchemy.types.TypeEngine, sqlalchemy.types.TIME())
|
|
584
|
+
if datelike_type == "date":
|
|
585
|
+
return cast(sqlalchemy.types.TypeEngine, sqlalchemy.types.DATE())
|
|
586
|
+
|
|
587
|
+
maxlength = jsonschema_type.get("maxLength")
|
|
588
|
+
return cast(sqlalchemy.types.TypeEngine, sqlalchemy.types.VARCHAR(maxlength))
|
|
589
|
+
|
|
590
|
+
if _jsonschema_type_check(jsonschema_type, ("integer",)):
|
|
591
|
+
return cast(sqlalchemy.types.TypeEngine, sqlalchemy.types.INTEGER())
|
|
592
|
+
if _jsonschema_type_check(jsonschema_type, ("number",)):
|
|
593
|
+
return cast(sqlalchemy.types.TypeEngine, sqlalchemy.types.DECIMAL())
|
|
594
|
+
if _jsonschema_type_check(jsonschema_type, ("boolean",)):
|
|
595
|
+
return cast(sqlalchemy.types.TypeEngine, sqlalchemy.types.BOOLEAN())
|
|
596
|
+
|
|
597
|
+
if _jsonschema_type_check(jsonschema_type, ("object",)):
|
|
598
|
+
return cast(sqlalchemy.types.TypeEngine, sqlalchemy.types.VARCHAR())
|
|
599
|
+
|
|
600
|
+
if _jsonschema_type_check(jsonschema_type, ("array",)):
|
|
601
|
+
return cast(sqlalchemy.types.TypeEngine, sqlalchemy.types.VARCHAR())
|
|
602
|
+
|
|
603
|
+
return cast(sqlalchemy.types.TypeEngine, sqlalchemy.types.VARCHAR())
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hotglue-singer-sdk
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: A framework for building Singer taps and targets
|
|
5
|
+
License: Apache 2.0
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: Hotglue,Hotglue SDK,ELT
|
|
8
|
+
Author: Hotglue Team and Contributors. Original work by Meltano Team and Contributors.
|
|
9
|
+
Maintainer: Hotglue Team and Contributors
|
|
10
|
+
Requires-Python: >=3.7.1,<3.11
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: License :: Other/Proprietary License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
20
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Dist: PyJWT (>=2.4,<3.0)
|
|
24
|
+
Requires-Dist: backoff (>=1.8.0,<2.0)
|
|
25
|
+
Requires-Dist: click (>=8.0,<9.0)
|
|
26
|
+
Requires-Dist: cryptography (>=3.4.6,<38.0.0)
|
|
27
|
+
Requires-Dist: hotglue-etl-exceptions (==0.1.0)
|
|
28
|
+
Requires-Dist: importlib-metadata ; python_version < "3.8"
|
|
29
|
+
Requires-Dist: inflection (>=0.5.1,<0.6.0)
|
|
30
|
+
Requires-Dist: joblib (>=1.0.1,<2.0.0)
|
|
31
|
+
Requires-Dist: jsonpath-ng (>=1.5.3,<2.0.0)
|
|
32
|
+
Requires-Dist: memoization (>=0.3.2,<0.5.0)
|
|
33
|
+
Requires-Dist: numpy (>=1.21.6,<2.0.0)
|
|
34
|
+
Requires-Dist: pandas (>=1.3.5,<2.0.0)
|
|
35
|
+
Requires-Dist: pendulum (>=2.1.0,<3.0.0)
|
|
36
|
+
Requires-Dist: pipelinewise-singer-python (==1.2.0)
|
|
37
|
+
Requires-Dist: pydantic (==2.5.3)
|
|
38
|
+
Requires-Dist: python-dotenv (>=0.20.0,<0.21.0)
|
|
39
|
+
Requires-Dist: requests (>=2.25.1,<3.0.0)
|
|
40
|
+
Requires-Dist: sqlalchemy (>=1.4,<2.0)
|
|
41
|
+
Requires-Dist: typing-extensions (>=4.2.0,<5.0.0)
|
|
42
|
+
Project-URL: Documentation, https://hotglue.com
|
|
43
|
+
Project-URL: Homepage, https://hotglue.com
|
|
44
|
+
Project-URL: Repository, https://github.com/hotglue/hotgluesingersdk
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
|
|
47
|
+
# Hotglue SDK for Taps
|
|
48
|
+
|
|
49
|
+
This is a fork of Melanto's SingerSDK for special use in [hotglue](https://hotglue.com), an embedded integration platform for running Singer Taps and Targets.
|
|
50
|
+
|
|
51
|
+
Taps and targets built on the SDK are automatically compliant with the
|
|
52
|
+
[Singer Spec](https://hub.meltano.com/singer/spec), the
|
|
53
|
+
de-facto open source standard for extract and load pipelines.
|