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.
Files changed (53) hide show
  1. hotglue_singer_sdk/__init__.py +34 -0
  2. hotglue_singer_sdk/authenticators.py +554 -0
  3. hotglue_singer_sdk/cli/__init__.py +1 -0
  4. hotglue_singer_sdk/cli/common_options.py +37 -0
  5. hotglue_singer_sdk/configuration/__init__.py +1 -0
  6. hotglue_singer_sdk/configuration/_dict_config.py +101 -0
  7. hotglue_singer_sdk/exceptions.py +52 -0
  8. hotglue_singer_sdk/helpers/__init__.py +1 -0
  9. hotglue_singer_sdk/helpers/_catalog.py +122 -0
  10. hotglue_singer_sdk/helpers/_classproperty.py +18 -0
  11. hotglue_singer_sdk/helpers/_compat.py +15 -0
  12. hotglue_singer_sdk/helpers/_flattening.py +374 -0
  13. hotglue_singer_sdk/helpers/_schema.py +100 -0
  14. hotglue_singer_sdk/helpers/_secrets.py +41 -0
  15. hotglue_singer_sdk/helpers/_simpleeval.py +678 -0
  16. hotglue_singer_sdk/helpers/_singer.py +280 -0
  17. hotglue_singer_sdk/helpers/_state.py +282 -0
  18. hotglue_singer_sdk/helpers/_typing.py +231 -0
  19. hotglue_singer_sdk/helpers/_util.py +27 -0
  20. hotglue_singer_sdk/helpers/capabilities.py +240 -0
  21. hotglue_singer_sdk/helpers/jsonpath.py +39 -0
  22. hotglue_singer_sdk/io_base.py +134 -0
  23. hotglue_singer_sdk/mapper.py +691 -0
  24. hotglue_singer_sdk/mapper_base.py +156 -0
  25. hotglue_singer_sdk/plugin_base.py +415 -0
  26. hotglue_singer_sdk/py.typed +0 -0
  27. hotglue_singer_sdk/sinks/__init__.py +14 -0
  28. hotglue_singer_sdk/sinks/batch.py +90 -0
  29. hotglue_singer_sdk/sinks/core.py +412 -0
  30. hotglue_singer_sdk/sinks/record.py +66 -0
  31. hotglue_singer_sdk/sinks/sql.py +299 -0
  32. hotglue_singer_sdk/streams/__init__.py +14 -0
  33. hotglue_singer_sdk/streams/core.py +1294 -0
  34. hotglue_singer_sdk/streams/graphql.py +74 -0
  35. hotglue_singer_sdk/streams/rest.py +611 -0
  36. hotglue_singer_sdk/streams/sql.py +1023 -0
  37. hotglue_singer_sdk/tap_base.py +580 -0
  38. hotglue_singer_sdk/target_base.py +554 -0
  39. hotglue_singer_sdk/target_sdk/__init__.py +0 -0
  40. hotglue_singer_sdk/target_sdk/auth.py +124 -0
  41. hotglue_singer_sdk/target_sdk/client.py +286 -0
  42. hotglue_singer_sdk/target_sdk/common.py +13 -0
  43. hotglue_singer_sdk/target_sdk/lambda.py +121 -0
  44. hotglue_singer_sdk/target_sdk/rest.py +108 -0
  45. hotglue_singer_sdk/target_sdk/sinks.py +16 -0
  46. hotglue_singer_sdk/target_sdk/target.py +570 -0
  47. hotglue_singer_sdk/target_sdk/target_base.py +627 -0
  48. hotglue_singer_sdk/testing.py +198 -0
  49. hotglue_singer_sdk/typing.py +603 -0
  50. hotglue_singer_sdk-1.0.2.dist-info/METADATA +53 -0
  51. hotglue_singer_sdk-1.0.2.dist-info/RECORD +53 -0
  52. hotglue_singer_sdk-1.0.2.dist-info/WHEEL +4 -0
  53. 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.