python-ubercode-utils 1.0.9__tar.gz → 2.0.0__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.
- {python-ubercode-utils-1.0.9/python_ubercode_utils.egg-info → python-ubercode-utils-2.0.0}/PKG-INFO +1 -1
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0/python_ubercode_utils.egg-info}/PKG-INFO +1 -1
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/python_ubercode_utils.egg-info/SOURCES.txt +3 -5
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/setup.py +1 -1
- python-ubercode-utils-1.0.9/test/test_json.py → python-ubercode-utils-2.0.0/test/test_data.py +67 -9
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/test/test_urls.py +10 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/ubercode/utils/convert.py +28 -0
- python-ubercode-utils-1.0.9/ubercode/utils/xml.py → python-ubercode-utils-2.0.0/ubercode/utils/data.py +53 -12
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/ubercode/utils/urls.py +21 -17
- python-ubercode-utils-1.0.9/test/test_xml.py +0 -61
- python-ubercode-utils-1.0.9/ubercode/utils/json.py +0 -54
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/LICENSE +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/MANIFEST.in +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/README.md +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/python_ubercode_utils.egg-info/dependency_links.txt +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/python_ubercode_utils.egg-info/not-zip-safe +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/python_ubercode_utils.egg-info/top_level.txt +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/setup.cfg +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/test/test_convert.py +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/test/test_cursor.py +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/test/test_dataframe.py +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/test/test_environment.py +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/test/test_logging.py +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/ubercode/__init__.py +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/ubercode/utils/__init__.py +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/ubercode/utils/cursor.py +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/ubercode/utils/dataframe.py +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/ubercode/utils/environment.py +0 -0
- {python-ubercode-utils-1.0.9 → python-ubercode-utils-2.0.0}/ubercode/utils/logging.py +0 -0
|
@@ -9,19 +9,17 @@ python_ubercode_utils.egg-info/not-zip-safe
|
|
|
9
9
|
python_ubercode_utils.egg-info/top_level.txt
|
|
10
10
|
test/test_convert.py
|
|
11
11
|
test/test_cursor.py
|
|
12
|
+
test/test_data.py
|
|
12
13
|
test/test_dataframe.py
|
|
13
14
|
test/test_environment.py
|
|
14
|
-
test/test_json.py
|
|
15
15
|
test/test_logging.py
|
|
16
16
|
test/test_urls.py
|
|
17
|
-
test/test_xml.py
|
|
18
17
|
ubercode/__init__.py
|
|
19
18
|
ubercode/utils/__init__.py
|
|
20
19
|
ubercode/utils/convert.py
|
|
21
20
|
ubercode/utils/cursor.py
|
|
21
|
+
ubercode/utils/data.py
|
|
22
22
|
ubercode/utils/dataframe.py
|
|
23
23
|
ubercode/utils/environment.py
|
|
24
|
-
ubercode/utils/json.py
|
|
25
24
|
ubercode/utils/logging.py
|
|
26
|
-
ubercode/utils/urls.py
|
|
27
|
-
ubercode/utils/xml.py
|
|
25
|
+
ubercode/utils/urls.py
|
|
@@ -4,7 +4,7 @@ with open("README.md", "r") as fh:
|
|
|
4
4
|
long_description = fh.read()
|
|
5
5
|
|
|
6
6
|
setuptools.setup(name='python-ubercode-utils',
|
|
7
|
-
version='
|
|
7
|
+
version='2.0.0',
|
|
8
8
|
description='Core python utilities for all apps',
|
|
9
9
|
long_description=long_description,
|
|
10
10
|
long_description_content_type="text/markdown",
|
python-ubercode-utils-1.0.9/test/test_json.py → python-ubercode-utils-2.0.0/test/test_data.py
RENAMED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
|
-
from ubercode.utils.
|
|
4
|
+
from ubercode.utils.data import JSON
|
|
5
|
+
from ubercode.utils.data import XML
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class TestJSON(unittest.TestCase):
|
|
@@ -48,12 +49,10 @@ class TestJSON(unittest.TestCase):
|
|
|
48
49
|
"""
|
|
49
50
|
# test we can construct from a json string
|
|
50
51
|
json = JSON(json_string=json_string)
|
|
51
|
-
self.assertEqual(len(json.
|
|
52
|
+
self.assertEqual(len(json.data['people']), 3)
|
|
52
53
|
# test we can construct by chaining and reading file
|
|
53
|
-
json2 = JSON().from_json_file(file_path)
|
|
54
|
-
self.assertEqual(json.
|
|
55
|
-
# test the dict matches the to_dict() result
|
|
56
|
-
self.assertEqual(json.json_dict, json.to_dict())
|
|
54
|
+
json2 = JSON().from_json_file(str(file_path))
|
|
55
|
+
self.assertEqual(json.data, json2.data)
|
|
57
56
|
# test encoding
|
|
58
57
|
json_string = """
|
|
59
58
|
{
|
|
@@ -91,12 +90,71 @@ class TestJSON(unittest.TestCase):
|
|
|
91
90
|
}
|
|
92
91
|
"""
|
|
93
92
|
json = JSON(json_string=json_string, encode_ampersands=True)
|
|
94
|
-
self.assertEqual(len(json.
|
|
95
|
-
first_name = json.
|
|
93
|
+
self.assertEqual(len(json.data['people']), 3)
|
|
94
|
+
first_name = json.data['people'][0]['firstName']
|
|
96
95
|
self.assertEqual(first_name, "Joe & Baker")
|
|
97
96
|
# make sure the second name isn't double encoded
|
|
98
|
-
second_name = json.
|
|
97
|
+
second_name = json.data['people'][1]['firstName']
|
|
99
98
|
self.assertEqual(second_name, "James &")
|
|
100
99
|
# test the str function
|
|
101
100
|
result = "{'people': [{'firstName': 'Joe & Baker', 'lastName': 'Jackson', 'gender': 'male', 'age': 28, 'number': '7349282382', 'groups': ['members', 'student']}, {'firstName': 'James &', 'lastName': 'Smith', 'gender': 'male', 'age': 32, 'number': '5678568567', 'groups': ['members', 'professional']}, {'firstName': 'Emily', 'lastName': 'Jones', 'gender': 'female', 'age': 24, 'number': '456754675'}]}"
|
|
102
101
|
self.assertEqual(str(json), result)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TestXML(unittest.TestCase):
|
|
105
|
+
|
|
106
|
+
# -------- common usages ----------
|
|
107
|
+
def test_XML(self):
|
|
108
|
+
# calculate the filename for our test.xml file from this file (much like settings does)
|
|
109
|
+
file_path = Path(__file__).resolve().parent / 'test.xml'
|
|
110
|
+
xml_string = """
|
|
111
|
+
<contacts>
|
|
112
|
+
<contact>
|
|
113
|
+
<name>Steve Stacha</name>
|
|
114
|
+
</contact>
|
|
115
|
+
<contact>
|
|
116
|
+
<name>Buggs Bunny</name>
|
|
117
|
+
</contact>
|
|
118
|
+
<contact>
|
|
119
|
+
<name>Daffy Duck</name>
|
|
120
|
+
</contact>
|
|
121
|
+
</contacts>
|
|
122
|
+
"""
|
|
123
|
+
# test we can construct from an xml string
|
|
124
|
+
xml = XML(xml_string=xml_string)
|
|
125
|
+
# NOTE: because we used a multiline string we need to strip the extra newlines before and after <contacts>
|
|
126
|
+
self.assertEqual(str(xml), xml_string.strip())
|
|
127
|
+
# normal string doesn't need stripping
|
|
128
|
+
xml_compact_string = "<contacts><contact><name>Buggs Bunny</name></contact><contact><name>Daffy Duck</name></contact></contacts>"
|
|
129
|
+
xml2 = XML(xml_compact_string)
|
|
130
|
+
self.assertEqual(str(xml2), xml_compact_string)
|
|
131
|
+
# test we can create using the from_xml_string() method chaining
|
|
132
|
+
xml3 = XML().from_xml_string(xml_compact_string)
|
|
133
|
+
self.assertEqual(str(xml2), str(xml3))
|
|
134
|
+
# test that method chaining after constructor overrides the value in place
|
|
135
|
+
self.assertNotEqual(str(xml), str(xml2))
|
|
136
|
+
xml.from_xml_string(xml_compact_string)
|
|
137
|
+
self.assertEqual(str(xml), str(xml2))
|
|
138
|
+
# test that we can read from file
|
|
139
|
+
xml.from_xml_file(str(file_path))
|
|
140
|
+
# we should be back to before
|
|
141
|
+
self.assertEqual(str(xml), xml_string.strip())
|
|
142
|
+
# test an attribute
|
|
143
|
+
xml_string = """
|
|
144
|
+
<contacts>
|
|
145
|
+
<contact attr="1">
|
|
146
|
+
<name>Steve Stacha</name>
|
|
147
|
+
</contact>
|
|
148
|
+
<contact>
|
|
149
|
+
<name>Buggs & Bunny</name>
|
|
150
|
+
</contact>
|
|
151
|
+
<contact>
|
|
152
|
+
<name>Daffy Duck</name>
|
|
153
|
+
</contact>
|
|
154
|
+
</contacts>
|
|
155
|
+
"""
|
|
156
|
+
xml = XML(xml_string=xml_string, encode_ampersands=True)
|
|
157
|
+
xml_dict = xml.to_dict()
|
|
158
|
+
self.assertEqual(xml_dict['contacts']['contact'][0]['@attr'], '1')
|
|
159
|
+
|
|
160
|
+
|
|
@@ -40,6 +40,16 @@ class TestUrls(unittest.TestCase):
|
|
|
40
40
|
self.assertEqual(test_uri, str(parsed_url))
|
|
41
41
|
parsed_url = ParsedUrl(test_uri, default_scheme='https')
|
|
42
42
|
self.assertEqual("https:" + test_uri, str(parsed_url))
|
|
43
|
+
# test that mailto and tel links don't get defaulted
|
|
44
|
+
test_uri = 'mailto:me@mail.com?subject=mysubject&body=mybody'
|
|
45
|
+
parsed_url = ParsedUrl(test_uri, default_scheme='http', default_netloc='localhost:8000', default_filepath='/booger/')
|
|
46
|
+
self.assertEqual("mailto", str(parsed_url.scheme))
|
|
47
|
+
self.assertEqual(test_uri, parsed_url.url)
|
|
48
|
+
test_uri = 'tel: 222.222.2222'
|
|
49
|
+
parsed_url = ParsedUrl(test_uri, default_scheme='http', default_netloc='localhost:8000', default_filepath='/booger/')
|
|
50
|
+
self.assertEqual("tel", str(parsed_url.scheme))
|
|
51
|
+
self.assertEqual(test_uri, parsed_url.url)
|
|
52
|
+
|
|
43
53
|
|
|
44
54
|
# --- basic retrieval
|
|
45
55
|
# -------------------
|
|
@@ -250,3 +250,31 @@ def to_mask(value: str or None) -> str or None:
|
|
|
250
250
|
_mask += value[-_iqtr:]
|
|
251
251
|
return _mask
|
|
252
252
|
|
|
253
|
+
def obj_to_str(obj, property_filter_list=None):
|
|
254
|
+
"""
|
|
255
|
+
Mostly used for debugging. Very useful to print the properties of an object on a line; condensing reasonably
|
|
256
|
+
|
|
257
|
+
:param obj: the object to inspect properties for
|
|
258
|
+
:param property_filter_list: any property names we want to omit
|
|
259
|
+
:return: a string containing the outputted properties
|
|
260
|
+
"""
|
|
261
|
+
attbuf = ""
|
|
262
|
+
for key, value in vars(obj).items():
|
|
263
|
+
if property_filter_list and key in property_filter_list:
|
|
264
|
+
continue
|
|
265
|
+
if not key.startswith('__'):
|
|
266
|
+
if len(attbuf) > 0:
|
|
267
|
+
attbuf += ", "
|
|
268
|
+
# show the first 50 chars and last 25 chars
|
|
269
|
+
this_content = str(value)
|
|
270
|
+
this_content = this_content.replace('\n', ' ').replace('\r', '').strip()
|
|
271
|
+
if this_content:
|
|
272
|
+
if len(this_content) > 150:
|
|
273
|
+
attbuf += str(key) + ": [" + this_content[0:25] + " ... " + this_content[
|
|
274
|
+
len(this_content) - 25:len(
|
|
275
|
+
this_content)] + "]"
|
|
276
|
+
else:
|
|
277
|
+
attbuf += str(key) + ": " + this_content or ""
|
|
278
|
+
else:
|
|
279
|
+
attbuf += str(key) + ": " + this_content or ""
|
|
280
|
+
return "[" + attbuf + "]"
|
|
@@ -1,18 +1,59 @@
|
|
|
1
1
|
"""
|
|
2
|
-
A collection of basic xml conversion helper utilities.
|
|
2
|
+
A collection of basic json/xml conversion helper utilities.
|
|
3
3
|
"""
|
|
4
|
-
import
|
|
4
|
+
import json
|
|
5
5
|
import re
|
|
6
6
|
# from typing import Self (Not available until 3.9; omitting for now)
|
|
7
|
+
import xml.etree.ElementTree as Etree
|
|
8
|
+
# from typing import Self (Not available until 3.9; omitting for now)
|
|
7
9
|
from collections import defaultdict
|
|
8
10
|
|
|
9
11
|
|
|
10
|
-
class
|
|
11
|
-
""" simple
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
class JSON:
|
|
13
|
+
""" simple json class to encapsulate basic json operations """
|
|
14
|
+
def __init__(self, json_string: str or None = None, encode_ampersands: bool = False):
|
|
15
|
+
# data is core python objects (list, dict, object, etc) from the core python JSON.loads
|
|
16
|
+
self.data = None
|
|
17
|
+
self.encode_ampersands = encode_ampersands
|
|
18
|
+
self.from_json_string(json_string)
|
|
19
|
+
|
|
20
|
+
def from_json_string(self, json_string: str):
|
|
21
|
+
"""
|
|
22
|
+
read in from json_string
|
|
23
|
+
:param json_string:
|
|
24
|
+
:return: self
|
|
25
|
+
"""
|
|
26
|
+
if json_string:
|
|
27
|
+
if self.encode_ampersands:
|
|
28
|
+
regex = re.compile(r"&(?!amp;|lt;|gt;)")
|
|
29
|
+
json_string = regex.sub("&", json_string)
|
|
30
|
+
self.data = json.loads(json_string)
|
|
31
|
+
return self
|
|
14
32
|
|
|
33
|
+
def from_json_file(self, json_file_path: str):
|
|
34
|
+
"""
|
|
35
|
+
read in from json_file
|
|
36
|
+
:param json_file_path: string to file
|
|
37
|
+
:return: self
|
|
38
|
+
"""
|
|
39
|
+
with open(json_file_path, encoding='utf-8-sig') as json_file:
|
|
40
|
+
json_string = json_file.read()
|
|
41
|
+
if json_string:
|
|
42
|
+
if self.encode_ampersands:
|
|
43
|
+
regex = re.compile(r"&(?!amp;|lt;|gt;)")
|
|
44
|
+
json_string = regex.sub("&", json_string)
|
|
45
|
+
self.data = json.loads(json_string)
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
def __str__(self):
|
|
49
|
+
return str(self.data)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class XML:
|
|
53
|
+
""" simple xml class to encapsulate basic xml operations using build in python ETree """
|
|
15
54
|
def __init__(self, xml_string: str or None = None, encode_ampersands: bool = False):
|
|
55
|
+
# data is core python ElementTree object
|
|
56
|
+
self.data = None
|
|
16
57
|
self.encode_ampersands = encode_ampersands
|
|
17
58
|
self.from_xml_string(xml_string)
|
|
18
59
|
|
|
@@ -26,7 +67,7 @@ class XML:
|
|
|
26
67
|
if self.encode_ampersands:
|
|
27
68
|
regex = re.compile(r"&(?!amp;|lt;|gt;)")
|
|
28
69
|
xml_string = regex.sub("&", xml_string)
|
|
29
|
-
self.
|
|
70
|
+
self.data = Etree.fromstring(xml_string)
|
|
30
71
|
return self
|
|
31
72
|
|
|
32
73
|
def from_xml_file(self, xml_file_path: str):
|
|
@@ -46,7 +87,7 @@ class XML:
|
|
|
46
87
|
else:
|
|
47
88
|
tree = Etree.parse(xml_file_path)
|
|
48
89
|
tree = tree.getroot()
|
|
49
|
-
self.
|
|
90
|
+
self.data = tree
|
|
50
91
|
return self
|
|
51
92
|
|
|
52
93
|
def to_dict(self) -> dict:
|
|
@@ -54,10 +95,10 @@ class XML:
|
|
|
54
95
|
output to dict
|
|
55
96
|
:return: dict
|
|
56
97
|
"""
|
|
57
|
-
return XML.tree_to_dict(self.
|
|
98
|
+
return XML.tree_to_dict(self.data)
|
|
58
99
|
|
|
59
100
|
@staticmethod
|
|
60
|
-
def tree_to_dict(t) -> dict:
|
|
101
|
+
def tree_to_dict(t: Etree) -> dict:
|
|
61
102
|
"""
|
|
62
103
|
Convert an etree structure to a dictionary of values
|
|
63
104
|
:param t: etree instance
|
|
@@ -83,6 +124,6 @@ class XML:
|
|
|
83
124
|
return d
|
|
84
125
|
|
|
85
126
|
def __str__(self):
|
|
86
|
-
if self.
|
|
87
|
-
return Etree.tostring(self.
|
|
127
|
+
if self.data:
|
|
128
|
+
return Etree.tostring(self.data, encoding='unicode')
|
|
88
129
|
return ""
|
|
@@ -64,9 +64,11 @@ class ParsedUrl:
|
|
|
64
64
|
raise Exception(
|
|
65
65
|
f'Attempted to parse [{str(self.url_filter(url))}]. Url parameter must exist and be a relative or absolute url after filtering!')
|
|
66
66
|
self.parsed = urlsplit(self.url_filter(url), default_scheme or "", allow_fragments=allow_fragments)
|
|
67
|
-
if
|
|
67
|
+
if default_scheme and not self.parsed.scheme and self.netloc:
|
|
68
|
+
self.scheme = default_scheme
|
|
69
|
+
if default_netloc and not self.parsed.netloc and self.scheme in ["http", "https", ""]:
|
|
68
70
|
self.netloc = default_netloc
|
|
69
|
-
if default_filepath and default_filepath not in self.filepath:
|
|
71
|
+
if default_filepath and default_filepath not in self.filepath and self.scheme in ["http", "https", ""]:
|
|
70
72
|
# we have a parent path we need to append to the existing one
|
|
71
73
|
new_path = str(PurePath(default_filepath, self.path))
|
|
72
74
|
# note: I don't want .. since this is web urls; using normpath to remove them unless symlinks is true
|
|
@@ -80,10 +82,8 @@ class ParsedUrl:
|
|
|
80
82
|
if new_path.endswith(default_filepath) or new_path.endswith(default_filepath[:-1]) and not new_path.endswith('/'):
|
|
81
83
|
new_path += '/'
|
|
82
84
|
self.path = new_path
|
|
83
|
-
if default_scheme and not self.parsed.scheme and self.netloc:
|
|
84
|
-
self.scheme = default_scheme
|
|
85
85
|
# one last correction; if we have a scheme but no netloc lets omit the scheme so it doesn't give bad results
|
|
86
|
-
if not self.parsed.netloc and self.parsed.scheme:
|
|
86
|
+
if not self.parsed.netloc and self.parsed.scheme and self.parsed.scheme in ['http', 'https']:
|
|
87
87
|
self.parsed = self.parsed._replace(scheme='')
|
|
88
88
|
|
|
89
89
|
@property
|
|
@@ -222,12 +222,12 @@ if __name__ == "__main__":
|
|
|
222
222
|
# parsed_url = ParsedUrl(test_uri)
|
|
223
223
|
# print(f"root domain [{test_uri}]: {parsed_url.get_root_domain()}")
|
|
224
224
|
# print(f"url:{parsed_url.url}")
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
225
|
+
test_uri = "/?id=1&b=&c=3"
|
|
226
|
+
parsed_url = ParsedUrl(test_uri, default_netloc='ex.org')
|
|
227
|
+
print(f"root domain [{test_uri}]: {parsed_url.root_domain}")
|
|
228
|
+
print(f"url:{parsed_url.url}")
|
|
229
|
+
print(f"base: {parsed_url.base}")
|
|
230
|
+
print(f"rel: {parsed_url.rel}")
|
|
231
231
|
# print(f"url after base: {parsed_url.url}")
|
|
232
232
|
# parsed_url.domain = "ex.org"
|
|
233
233
|
# print(f"root domain [{str(parsed_url)}]: {parsed_url.root_domain}")
|
|
@@ -241,9 +241,13 @@ if __name__ == "__main__":
|
|
|
241
241
|
# print(f"test parent fragment only: {ParsedUrl(test_uri, default_netloc='localhost:8000', default_scheme='http', default_path='/mdb/')}")
|
|
242
242
|
# test_uri = "#testproduct"
|
|
243
243
|
# print(f"test parent fragment only: {ParsedUrl(test_uri, default_netloc='localhost:8000', default_scheme='http', default_path='/mdb/')}")
|
|
244
|
-
test_uri = "/products/"
|
|
245
|
-
purl = ParsedUrl(test_uri, default_scheme='http', default_netloc='localhost:8000', default_filepath='/blog')
|
|
246
|
-
print(purl)
|
|
247
|
-
test_uri = "../products/"
|
|
248
|
-
purl = ParsedUrl(test_uri, default_scheme='http', default_netloc='localhost:8000', default_filepath='/')
|
|
249
|
-
print(purl)
|
|
244
|
+
# test_uri = "/products/"
|
|
245
|
+
# purl = ParsedUrl(test_uri, default_scheme='http', default_netloc='localhost:8000', default_filepath='/blog')
|
|
246
|
+
# print(purl)
|
|
247
|
+
# test_uri = "../products/"
|
|
248
|
+
# purl = ParsedUrl(test_uri, default_scheme='http', default_netloc='localhost:8000', default_filepath='/')
|
|
249
|
+
# print(purl)
|
|
250
|
+
# # test mailto links
|
|
251
|
+
test_uri = 'mailto:me@mail.com?subject=mysubject&body=mybody'
|
|
252
|
+
purl = ParsedUrl(test_uri, default_scheme='http', default_netloc='localhost:8000')
|
|
253
|
+
print(purl)
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from ubercode.utils.xml import XML
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class TestXML(unittest.TestCase):
|
|
7
|
-
|
|
8
|
-
# -------- common usages ----------
|
|
9
|
-
def test_XML(self):
|
|
10
|
-
# calculate the filename for our test.xml file from this file (much like settings does)
|
|
11
|
-
file_path = Path(__file__).resolve().parent / 'test.xml'
|
|
12
|
-
xml_string = """
|
|
13
|
-
<contacts>
|
|
14
|
-
<contact>
|
|
15
|
-
<name>Steve Stacha</name>
|
|
16
|
-
</contact>
|
|
17
|
-
<contact>
|
|
18
|
-
<name>Buggs Bunny</name>
|
|
19
|
-
</contact>
|
|
20
|
-
<contact>
|
|
21
|
-
<name>Daffy Duck</name>
|
|
22
|
-
</contact>
|
|
23
|
-
</contacts>
|
|
24
|
-
"""
|
|
25
|
-
# test we can construct from an xml string
|
|
26
|
-
xml = XML(xml_string=xml_string)
|
|
27
|
-
# NOTE: because we used a multiline string we need to strip the extra newlines before and after <contacts>
|
|
28
|
-
self.assertEqual(str(xml), xml_string.strip())
|
|
29
|
-
# normal string doesn't need stripping
|
|
30
|
-
xml_compact_string = "<contacts><contact><name>Buggs Bunny</name></contact><contact><name>Daffy Duck</name></contact></contacts>"
|
|
31
|
-
xml2 = XML(xml_compact_string)
|
|
32
|
-
self.assertEqual(str(xml2), xml_compact_string)
|
|
33
|
-
# test we can create using the from_xml_string() method chaining
|
|
34
|
-
xml3 = XML().from_xml_string(xml_compact_string)
|
|
35
|
-
self.assertEqual(str(xml2), str(xml3))
|
|
36
|
-
# test that method chaining after constructor overrides the value in place
|
|
37
|
-
self.assertNotEqual(str(xml), str(xml2))
|
|
38
|
-
xml.from_xml_string(xml_compact_string)
|
|
39
|
-
self.assertEqual(str(xml), str(xml2))
|
|
40
|
-
# test that we can read from file
|
|
41
|
-
xml.from_xml_file(file_path)
|
|
42
|
-
# we should be back to before
|
|
43
|
-
self.assertEqual(str(xml), xml_string.strip())
|
|
44
|
-
# test an attribute
|
|
45
|
-
xml_string = """
|
|
46
|
-
<contacts>
|
|
47
|
-
<contact attr="1">
|
|
48
|
-
<name>Steve Stacha</name>
|
|
49
|
-
</contact>
|
|
50
|
-
<contact>
|
|
51
|
-
<name>Buggs & Bunny</name>
|
|
52
|
-
</contact>
|
|
53
|
-
<contact>
|
|
54
|
-
<name>Daffy Duck</name>
|
|
55
|
-
</contact>
|
|
56
|
-
</contacts>
|
|
57
|
-
"""
|
|
58
|
-
xml = XML(xml_string=xml_string, encode_ampersands=True)
|
|
59
|
-
xml_dict = xml.to_dict()
|
|
60
|
-
self.assertEqual(xml_dict['contacts']['contact'][0]['@attr'], '1')
|
|
61
|
-
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
A collection of basic json conversion helper utilities.
|
|
3
|
-
"""
|
|
4
|
-
import json
|
|
5
|
-
import re
|
|
6
|
-
# from typing import Self (Not available until 3.9; omitting for now)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class JSON:
|
|
10
|
-
""" simple json class to encapsulate basic json operations """
|
|
11
|
-
# the base implementation will be dict
|
|
12
|
-
json_dict = {}
|
|
13
|
-
|
|
14
|
-
def __init__(self, json_string: str or None = None, encode_ampersands: bool = False):
|
|
15
|
-
self.encode_ampersands = encode_ampersands
|
|
16
|
-
self.from_json_string(json_string)
|
|
17
|
-
|
|
18
|
-
def from_json_string(self, json_string: str):
|
|
19
|
-
"""
|
|
20
|
-
read in from json_string
|
|
21
|
-
:param json_string:
|
|
22
|
-
:return: self
|
|
23
|
-
"""
|
|
24
|
-
if json_string:
|
|
25
|
-
if self.encode_ampersands:
|
|
26
|
-
regex = re.compile(r"&(?!amp;|lt;|gt;)")
|
|
27
|
-
json_string = regex.sub("&", json_string)
|
|
28
|
-
self.json_dict = json.loads(json_string)
|
|
29
|
-
return self
|
|
30
|
-
|
|
31
|
-
def from_json_file(self, json_file_path: str):
|
|
32
|
-
"""
|
|
33
|
-
read in from json_file
|
|
34
|
-
:param json_file_path: string to file
|
|
35
|
-
:return: self
|
|
36
|
-
"""
|
|
37
|
-
with open(json_file_path, encoding='utf-8-sig') as json_file:
|
|
38
|
-
json_string = json_file.read()
|
|
39
|
-
if json_string:
|
|
40
|
-
if self.encode_ampersands:
|
|
41
|
-
regex = re.compile(r"&(?!amp;|lt;|gt;)")
|
|
42
|
-
json_string = regex.sub("&", json_string)
|
|
43
|
-
self.json_dict = json.loads(json_string)
|
|
44
|
-
return self
|
|
45
|
-
|
|
46
|
-
def to_dict(self) -> dict:
|
|
47
|
-
"""
|
|
48
|
-
output to dict
|
|
49
|
-
:return: dict
|
|
50
|
-
"""
|
|
51
|
-
return self.json_dict
|
|
52
|
-
|
|
53
|
-
def __str__(self):
|
|
54
|
-
return str(self.json_dict)
|
|
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
|