singer-python 6.2.0__tar.gz → 6.2.2__tar.gz
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.
- {singer_python-6.2.0/singer_python.egg-info → singer_python-6.2.2}/PKG-INFO +1 -1
- {singer_python-6.2.0 → singer_python-6.2.2}/setup.py +1 -1
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/schema_generation.py +31 -18
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/transform.py +4 -2
- {singer_python-6.2.0 → singer_python-6.2.2/singer_python.egg-info}/PKG-INFO +1 -1
- {singer_python-6.2.0 → singer_python-6.2.2}/tests/test_schema_generation.py +13 -17
- {singer_python-6.2.0 → singer_python-6.2.2}/tests/test_transform.py +2 -2
- {singer_python-6.2.0 → singer_python-6.2.2}/LICENSE +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/README.md +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/setup.cfg +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/__init__.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/bookmarks.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/catalog.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/exceptions.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/logger.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/logging.conf +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/messages.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/metadata.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/metrics.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/requests.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/schema.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/statediff.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer/utils.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer_python.egg-info/SOURCES.txt +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer_python.egg-info/dependency_links.txt +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer_python.egg-info/requires.txt +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/singer_python.egg-info/top_level.txt +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/tests/__init__.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/tests/test_bookmarks.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/tests/test_catalog.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/tests/test_exceptions.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/tests/test_metadata.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/tests/test_metrics.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/tests/test_schema.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/tests/test_singer.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/tests/test_statediff.py +0 -0
- {singer_python-6.2.0 → singer_python-6.2.2}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import dateutil.parser
|
|
2
|
-
|
|
3
|
-
|
|
4
1
|
def add_observation(acc, path):
|
|
5
2
|
|
|
6
3
|
node = acc
|
|
@@ -18,19 +15,26 @@ def add_observations(acc, path, data):
|
|
|
18
15
|
for key in data:
|
|
19
16
|
add_observations(acc, path + ["object", key], data[key])
|
|
20
17
|
elif isinstance(data, list):
|
|
18
|
+
if len(data) == 0:
|
|
19
|
+
add_observations(acc, path + ["array"], None)
|
|
21
20
|
for item in data:
|
|
22
21
|
add_observations(acc, path + ["array"], item)
|
|
23
22
|
elif isinstance(data, str):
|
|
24
|
-
# If the string parses as a date, add an observation that its a date
|
|
25
23
|
try:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
# If the string parses as a int, add an observation that it's a integer
|
|
25
|
+
int(data)
|
|
26
|
+
add_observation(acc, path + ["integer"])
|
|
27
|
+
return acc
|
|
28
|
+
except (ValueError, TypeError):
|
|
29
|
+
pass
|
|
30
|
+
try:
|
|
31
|
+
# If the string parses as a float, add an observation that it's a number
|
|
32
|
+
float(data)
|
|
33
|
+
add_observation(acc, path + ["number"])
|
|
34
|
+
return acc
|
|
35
|
+
except (ValueError, TypeError):
|
|
36
|
+
pass
|
|
37
|
+
add_observation(acc, path + ["string"])
|
|
34
38
|
elif isinstance(data, bool):
|
|
35
39
|
add_observation(acc, path + ["boolean"])
|
|
36
40
|
elif isinstance(data, int):
|
|
@@ -45,9 +49,13 @@ def add_observations(acc, path, data):
|
|
|
45
49
|
return acc
|
|
46
50
|
|
|
47
51
|
def to_json_schema(obs):
|
|
48
|
-
|
|
52
|
+
types = []
|
|
53
|
+
# add schema types in a specific order to anyOf list
|
|
54
|
+
for key in ['array', 'object', 'number', 'integer', 'boolean', 'string', 'null']:
|
|
55
|
+
if key not in obs:
|
|
56
|
+
continue
|
|
49
57
|
|
|
50
|
-
|
|
58
|
+
result = {'type': ['null']}
|
|
51
59
|
|
|
52
60
|
if key == 'object':
|
|
53
61
|
result['type'] += ['object']
|
|
@@ -60,9 +68,6 @@ def to_json_schema(obs):
|
|
|
60
68
|
result['type'] += ['array']
|
|
61
69
|
result['items'] = to_json_schema(obs['array'])
|
|
62
70
|
|
|
63
|
-
elif key == 'date':
|
|
64
|
-
result['type'] += ['string']
|
|
65
|
-
result['format'] = 'date-time'
|
|
66
71
|
elif key == 'string':
|
|
67
72
|
result['type'] += ['string']
|
|
68
73
|
|
|
@@ -83,7 +88,15 @@ def to_json_schema(obs):
|
|
|
83
88
|
else:
|
|
84
89
|
raise Exception("Unexpected data type " + key)
|
|
85
90
|
|
|
86
|
-
|
|
91
|
+
types.append(result)
|
|
92
|
+
|
|
93
|
+
if len(types) == 0:
|
|
94
|
+
return {'type': ['null', 'string']}
|
|
95
|
+
|
|
96
|
+
if len(types) == 1:
|
|
97
|
+
return types[0]
|
|
98
|
+
|
|
99
|
+
return {'anyOf': types}
|
|
87
100
|
|
|
88
101
|
def generate_schema(records):
|
|
89
102
|
obs = {}
|
|
@@ -185,6 +185,8 @@ class Transformer:
|
|
|
185
185
|
success, transformed_data = self.transform_recur(data, subschema, path)
|
|
186
186
|
if success:
|
|
187
187
|
return success, transformed_data
|
|
188
|
+
else:
|
|
189
|
+
self.errors.pop()
|
|
188
190
|
else: # pylint: disable=useless-else-on-loop
|
|
189
191
|
# exhaused all schemas and didn't return, so we failed :-(
|
|
190
192
|
self.errors.append(Error(path, data, schema, logging_level=LOGGER.level))
|
|
@@ -266,13 +268,13 @@ class Transformer:
|
|
|
266
268
|
else:
|
|
267
269
|
return False, None
|
|
268
270
|
|
|
269
|
-
elif schema.get("format") == "date-time":
|
|
271
|
+
elif typ == "string" and schema.get("format") == "date-time":
|
|
270
272
|
data = self._transform_datetime(data)
|
|
271
273
|
if data is None:
|
|
272
274
|
return False, None
|
|
273
275
|
|
|
274
276
|
return True, data
|
|
275
|
-
elif schema.get("format") == "singer.decimal":
|
|
277
|
+
elif typ == "string" and schema.get("format") == "singer.decimal":
|
|
276
278
|
if data is None:
|
|
277
279
|
return False, None
|
|
278
280
|
|
|
@@ -10,7 +10,7 @@ class TestSchemaGeneration(unittest.TestCase):
|
|
|
10
10
|
'a': {'type': ['null', 'integer']},
|
|
11
11
|
'b': {'type': ['null', 'string']},
|
|
12
12
|
'c': {'type': ['null', 'boolean']},
|
|
13
|
-
'dt': {'type': ['null', 'string']
|
|
13
|
+
'dt': {'type': ['null', 'string']}
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
self.assertEqual(expected_schema, generate_schema(records))
|
|
@@ -23,24 +23,20 @@ class TestSchemaGeneration(unittest.TestCase):
|
|
|
23
23
|
]
|
|
24
24
|
expected_schema = {
|
|
25
25
|
'type': ['null', 'object'],
|
|
26
|
-
'properties': {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
'properties': {'a': {'anyOf': [{'type': ['null', 'integer']},
|
|
27
|
+
{'type': ['null', 'boolean']},
|
|
28
|
+
{'type': ['null', 'string']}]},
|
|
29
|
+
'b': {'type': ['null', 'string']},
|
|
30
|
+
'c': {'anyOf': [{'type': ['null', 'string'], 'format': 'singer.decimal'},
|
|
31
|
+
{'type': ['null', 'integer']}]},
|
|
32
|
+
'd': {'anyOf': [{'type': ['null', 'array'],
|
|
33
|
+
'items': {'anyOf': [{'type': ['null', 'integer']},
|
|
34
|
+
{'type': ['null', 'string']}]}},
|
|
35
|
+
{'type': ['null', 'object'],
|
|
36
|
+
'properties': {'one': {'type': ['null', 'integer']},
|
|
37
|
+
'two': {'type': ['null', 'string']}}}]}}
|
|
38
38
|
}
|
|
39
39
|
actual_schema = generate_schema(records)
|
|
40
|
-
actual_schema['properties']['a']['type'] = set(actual_schema['properties']['a']['type'])
|
|
41
|
-
actual_schema['properties']['c']['type'] = set(actual_schema['properties']['c']['type'])
|
|
42
|
-
actual_schema['properties']['d']['type'] = set(actual_schema['properties']['d']['type'])
|
|
43
|
-
actual_schema['properties']['d']['items']['type'] = set(actual_schema['properties']['d']['items']['type'])
|
|
44
40
|
self.assertEqual(expected_schema, actual_schema)
|
|
45
41
|
|
|
46
42
|
def test_nested_structue_schema(self):
|
|
@@ -25,7 +25,7 @@ class TestTransform(unittest.TestCase):
|
|
|
25
25
|
|
|
26
26
|
def test_multi_type_object_transform(self):
|
|
27
27
|
schema = {"type": ["null", "object", "string"],
|
|
28
|
-
"properties": {"whatever": {"type": "
|
|
28
|
+
"properties": {"whatever": {"type": "string",
|
|
29
29
|
"format": "date-time"}}}
|
|
30
30
|
data = {"whatever": "2017-01-01"}
|
|
31
31
|
expected = {"whatever": "2017-01-01T00:00:00.000000Z"}
|
|
@@ -36,7 +36,7 @@ class TestTransform(unittest.TestCase):
|
|
|
36
36
|
|
|
37
37
|
def test_multi_type_array_transform(self):
|
|
38
38
|
schema = {"type": ["null", "array", "integer"],
|
|
39
|
-
"items": {"type": "
|
|
39
|
+
"items": {"type": "string", "format": "date-time"}}
|
|
40
40
|
data = ["2017-01-01"]
|
|
41
41
|
expected = ["2017-01-01T00:00:00.000000Z"]
|
|
42
42
|
self.assertEqual(expected, transform(data, schema))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|