datatypes 0.31.0__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.
- datatypes/__init__.py +169 -0
- datatypes/collections/__init__.py +25 -0
- datatypes/collections/container.py +248 -0
- datatypes/collections/mapping.py +1226 -0
- datatypes/collections/sequence.py +311 -0
- datatypes/command.py +596 -0
- datatypes/compat.py +206 -0
- datatypes/config/__init__.py +13 -0
- datatypes/config/environ/__init__.py +33 -0
- datatypes/config/environ/base.py +332 -0
- datatypes/config/environ/token.py +173 -0
- datatypes/config/parser.py +256 -0
- datatypes/config/settings.py +352 -0
- datatypes/copy.py +151 -0
- datatypes/csv.py +547 -0
- datatypes/datetime.py +1243 -0
- datatypes/decorators/__init__.py +31 -0
- datatypes/decorators/base.py +449 -0
- datatypes/decorators/descriptor.py +490 -0
- datatypes/decorators/misc.py +152 -0
- datatypes/email.py +439 -0
- datatypes/enum.py +176 -0
- datatypes/event.py +299 -0
- datatypes/exception.py +13 -0
- datatypes/filetypes/__init__.py +10 -0
- datatypes/filetypes/html.py +540 -0
- datatypes/filetypes/toml.py +275 -0
- datatypes/http.py +1443 -0
- datatypes/logging.py +475 -0
- datatypes/number.py +586 -0
- datatypes/parse.py +362 -0
- datatypes/path.py +4358 -0
- datatypes/profile.py +246 -0
- datatypes/reflection/__init__.py +24 -0
- datatypes/reflection/inspect.py +3475 -0
- datatypes/reflection/other.py +843 -0
- datatypes/reflection/path.py +639 -0
- datatypes/server.py +642 -0
- datatypes/service.py +212 -0
- datatypes/ssh.py +812 -0
- datatypes/string.py +1632 -0
- datatypes/token/__init__.py +16 -0
- datatypes/token/abnf.py +1696 -0
- datatypes/token/base.py +921 -0
- datatypes/token/word.py +259 -0
- datatypes/typing.py +126 -0
- datatypes/url.py +988 -0
- datatypes/utils.py +179 -0
- datatypes-0.31.0.dist-info/METADATA +38 -0
- datatypes-0.31.0.dist-info/RECORD +53 -0
- datatypes-0.31.0.dist-info/WHEEL +5 -0
- datatypes-0.31.0.dist-info/licenses/LICENSE.txt +21 -0
- datatypes-0.31.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import string
|
|
3
|
+
|
|
4
|
+
from ...token.base import Tokenizer, Token, Scanner
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EnvironToken(Token):
|
|
8
|
+
"""A parsed environ variable name and value
|
|
9
|
+
|
|
10
|
+
Fields each instance should have:
|
|
11
|
+
|
|
12
|
+
* `.name` - the variable name
|
|
13
|
+
* `.value` - the variable value
|
|
14
|
+
* `.start` - the offset this token's name started
|
|
15
|
+
* `.stop` - the offset this token's value stopped
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self, tokenizer, name, value, start, stop):
|
|
18
|
+
super().__init__(tokenizer, start, stop)
|
|
19
|
+
|
|
20
|
+
self.name = name
|
|
21
|
+
self.value = value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class EnvironTokenizer(Tokenizer):
|
|
25
|
+
"""Parses an environment file buffer and returns the key/value pairs as
|
|
26
|
+
EnvironToken instances
|
|
27
|
+
|
|
28
|
+
An environment file is a file that contains NAME=VALUE pairs. It ignores
|
|
29
|
+
all comments starting with the number sign (#)
|
|
30
|
+
|
|
31
|
+
.. Example:
|
|
32
|
+
with open("<ENVIRON-FILE-PATH>") as fp:
|
|
33
|
+
ts = EnvironTokenizer(fp, os.environ)
|
|
34
|
+
for t in ts:
|
|
35
|
+
print(t.name, t.value)
|
|
36
|
+
"""
|
|
37
|
+
token_class = EnvironToken
|
|
38
|
+
|
|
39
|
+
def __init__(self, buffer, environ=None):
|
|
40
|
+
"""
|
|
41
|
+
:param buffer: io.IOBase|str
|
|
42
|
+
:param environ: Mapping
|
|
43
|
+
"""
|
|
44
|
+
super().__init__(Scanner(buffer))
|
|
45
|
+
self.environ = environ
|
|
46
|
+
|
|
47
|
+
def next(self):
|
|
48
|
+
"""Get the next environment variable
|
|
49
|
+
|
|
50
|
+
:returns: EnvironToken
|
|
51
|
+
"""
|
|
52
|
+
token = None
|
|
53
|
+
name = ""
|
|
54
|
+
value = ""
|
|
55
|
+
|
|
56
|
+
buffer = self.buffer
|
|
57
|
+
alphanum = string.ascii_letters + string.digits + "_"
|
|
58
|
+
|
|
59
|
+
# get the variable name
|
|
60
|
+
while True:
|
|
61
|
+
start = buffer.tell()
|
|
62
|
+
ch = buffer.read(1)
|
|
63
|
+
|
|
64
|
+
if ch == "":
|
|
65
|
+
# we've exhausted the buffer
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
elif ch == "#":
|
|
69
|
+
# read to the end of the line and discard
|
|
70
|
+
buffer.readline()
|
|
71
|
+
|
|
72
|
+
elif ch in string.whitespace:
|
|
73
|
+
buffer.read_thru(whitespace=True)
|
|
74
|
+
|
|
75
|
+
elif ch in string.ascii_letters:
|
|
76
|
+
name = ch + buffer.read_thru(chars=alphanum)
|
|
77
|
+
|
|
78
|
+
# ignore export statements
|
|
79
|
+
if name == "export":
|
|
80
|
+
buffer.read_thru(whitespace=True)
|
|
81
|
+
name = ""
|
|
82
|
+
|
|
83
|
+
else:
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
else:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f"Not sure what to do with char [{ch}]"
|
|
89
|
+
" in environ variable name"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if name:
|
|
93
|
+
equal_sign = buffer.read(1)
|
|
94
|
+
if equal_sign != "=":
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"Equal sign [=] was expected but got [{equal_sign}]"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
sentinel = ""
|
|
100
|
+
multiline = False
|
|
101
|
+
|
|
102
|
+
while True:
|
|
103
|
+
ch = buffer.read(1)
|
|
104
|
+
|
|
105
|
+
if ch == "":
|
|
106
|
+
# we've exhausted the buffer
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
elif ch == "\\":
|
|
110
|
+
multiline = True
|
|
111
|
+
ch = buffer.read(1)
|
|
112
|
+
|
|
113
|
+
if ch in string.whitespace:
|
|
114
|
+
buffer.read_thru(whitespace=True)
|
|
115
|
+
if value.endswith(" "):
|
|
116
|
+
ch = ""
|
|
117
|
+
|
|
118
|
+
else:
|
|
119
|
+
ch = " "
|
|
120
|
+
|
|
121
|
+
value += ch
|
|
122
|
+
|
|
123
|
+
elif ch == "\"" or ch == "'":
|
|
124
|
+
if sentinel == ch:
|
|
125
|
+
sentinel = ""
|
|
126
|
+
|
|
127
|
+
else:
|
|
128
|
+
sentinel = ch
|
|
129
|
+
|
|
130
|
+
elif ch == "$":
|
|
131
|
+
ch = buffer.read(1)
|
|
132
|
+
if ch == "{":
|
|
133
|
+
variable_name = buffer.read_to(delim="}")
|
|
134
|
+
buffer.read_thru(delim="}")
|
|
135
|
+
|
|
136
|
+
else:
|
|
137
|
+
variable_name = ch + buffer.read_thru(chars=alphanum)
|
|
138
|
+
|
|
139
|
+
if self.environ:
|
|
140
|
+
value += self.environ.get(variable_name, "")
|
|
141
|
+
|
|
142
|
+
elif ch == "#":
|
|
143
|
+
if sentinel:
|
|
144
|
+
value += ch
|
|
145
|
+
|
|
146
|
+
else:
|
|
147
|
+
# ignore the rest of the line since it's a comment
|
|
148
|
+
buffer.readline()
|
|
149
|
+
|
|
150
|
+
if multiline:
|
|
151
|
+
buffer.read_thru(whitespace=True)
|
|
152
|
+
multiline = False
|
|
153
|
+
|
|
154
|
+
else:
|
|
155
|
+
break
|
|
156
|
+
|
|
157
|
+
elif ch == "\n":
|
|
158
|
+
# we are done grabbing the value
|
|
159
|
+
break
|
|
160
|
+
|
|
161
|
+
else:
|
|
162
|
+
value += ch
|
|
163
|
+
|
|
164
|
+
token = self.token_class(
|
|
165
|
+
self,
|
|
166
|
+
name,
|
|
167
|
+
value,
|
|
168
|
+
start,
|
|
169
|
+
buffer.tell()
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return token
|
|
173
|
+
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from configparser import ConfigParser, ExtendedInterpolation
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from ..compat import *
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConfigInterpolation(ExtendedInterpolation):
|
|
9
|
+
"""Internal class used by Config
|
|
10
|
+
|
|
11
|
+
https://github.com/python/cpython/blob/3.11/Lib/configparser.py#L361
|
|
12
|
+
"""
|
|
13
|
+
def before_set_dict(self, parser, section, option, value):
|
|
14
|
+
if parser.path.endswith(".toml"):
|
|
15
|
+
values = []
|
|
16
|
+
for key, val in value.items():
|
|
17
|
+
val = self.before_set_value(parser, section, key, val)
|
|
18
|
+
values.append(f"{key} = {val}")
|
|
19
|
+
|
|
20
|
+
return "{" + ", ".join(values) + "}"
|
|
21
|
+
|
|
22
|
+
else:
|
|
23
|
+
return value
|
|
24
|
+
|
|
25
|
+
def before_get_dict(self, parser, section, option, value, defaults):
|
|
26
|
+
if parser.path.endswith(".toml"):
|
|
27
|
+
d = {}
|
|
28
|
+
regex = r"([a-zA-Z0-9_-]+)\s*=\s*([\"\[\{])"
|
|
29
|
+
for m in re.finditer(regex, value):
|
|
30
|
+
key = m.group(1)
|
|
31
|
+
sentinel = value[m.regs[2][0]:m.regs[2][1]]
|
|
32
|
+
start = m.regs[2][0]
|
|
33
|
+
stop = start + 1
|
|
34
|
+
while value[stop] != sentinel:
|
|
35
|
+
if value[stop] == "\\":
|
|
36
|
+
# skip escaped characters
|
|
37
|
+
stop += 1
|
|
38
|
+
stop += 1
|
|
39
|
+
stop += 1
|
|
40
|
+
|
|
41
|
+
d[key] = self.before_get(
|
|
42
|
+
parser,
|
|
43
|
+
section,
|
|
44
|
+
key,
|
|
45
|
+
value[start:stop],
|
|
46
|
+
defaults
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return d
|
|
50
|
+
|
|
51
|
+
else:
|
|
52
|
+
return value
|
|
53
|
+
|
|
54
|
+
def before_get_str(self, parser, section, option, value, defaults):
|
|
55
|
+
if parser.path.endswith(".toml"):
|
|
56
|
+
return value.strip("\"")
|
|
57
|
+
|
|
58
|
+
else:
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
def before_set_str(self, parser, section, option, value):
|
|
62
|
+
if parser.path.endswith(".toml"):
|
|
63
|
+
return f"\"{value}\""
|
|
64
|
+
|
|
65
|
+
else:
|
|
66
|
+
return value
|
|
67
|
+
|
|
68
|
+
def before_set_list(self, parser, section, option, value):
|
|
69
|
+
if parser.path.endswith(".toml"):
|
|
70
|
+
values = []
|
|
71
|
+
for val in value:
|
|
72
|
+
val = self.before_set_value(parser, section, option, val)
|
|
73
|
+
values.append(f" {val}")
|
|
74
|
+
|
|
75
|
+
return "[\n" + ",\n".join(values) + "\n]"
|
|
76
|
+
|
|
77
|
+
else:
|
|
78
|
+
return value
|
|
79
|
+
|
|
80
|
+
def before_get_list(self, parser, section, option, value, defaults):
|
|
81
|
+
if parser.path.endswith(".toml"):
|
|
82
|
+
l = []
|
|
83
|
+
cv = value.strip("[]").strip()
|
|
84
|
+
while cv:
|
|
85
|
+
sentinel = cv[0]
|
|
86
|
+
start = 0
|
|
87
|
+
stop = start + 1
|
|
88
|
+
while cv[stop] != sentinel:
|
|
89
|
+
if cv[stop] == "\\":
|
|
90
|
+
# skip escaped characters
|
|
91
|
+
stop += 1
|
|
92
|
+
stop += 1
|
|
93
|
+
stop += 1
|
|
94
|
+
|
|
95
|
+
l.append(self.before_get(
|
|
96
|
+
parser,
|
|
97
|
+
section,
|
|
98
|
+
option,
|
|
99
|
+
cv[start:stop],
|
|
100
|
+
defaults
|
|
101
|
+
))
|
|
102
|
+
|
|
103
|
+
cv = cv[stop:].strip(",").strip()
|
|
104
|
+
|
|
105
|
+
return l
|
|
106
|
+
|
|
107
|
+
else:
|
|
108
|
+
return value
|
|
109
|
+
|
|
110
|
+
def before_set_value(self, parser, section, option, value):
|
|
111
|
+
if value is not None:
|
|
112
|
+
if isinstance(value, Mapping):
|
|
113
|
+
value = self.before_set_dict(
|
|
114
|
+
parser,
|
|
115
|
+
section,
|
|
116
|
+
option,
|
|
117
|
+
value
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
elif isinstance(value, str):
|
|
121
|
+
value = self.before_set_str(
|
|
122
|
+
parser,
|
|
123
|
+
section,
|
|
124
|
+
option,
|
|
125
|
+
value
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
elif isinstance(value, Sequence):
|
|
129
|
+
value = self.before_set_list(
|
|
130
|
+
parser,
|
|
131
|
+
section,
|
|
132
|
+
option,
|
|
133
|
+
value
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
else:
|
|
137
|
+
value = str(value)
|
|
138
|
+
|
|
139
|
+
return value
|
|
140
|
+
|
|
141
|
+
def optionxform(self, parser, optionstr):
|
|
142
|
+
if parser.path.endswith(".toml"):
|
|
143
|
+
return optionstr
|
|
144
|
+
|
|
145
|
+
else:
|
|
146
|
+
return optionstr.lower()
|
|
147
|
+
|
|
148
|
+
def before_write(self, parser, section, option, value):
|
|
149
|
+
return super().before_write(parser, section, option, value)
|
|
150
|
+
|
|
151
|
+
def before_get(self, parser, section, option, value, defaults):
|
|
152
|
+
if value.startswith("{"):
|
|
153
|
+
return self.before_get_dict(
|
|
154
|
+
parser,
|
|
155
|
+
section,
|
|
156
|
+
option,
|
|
157
|
+
value,
|
|
158
|
+
defaults
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
elif value.startswith("["):
|
|
162
|
+
return self.before_get_list(
|
|
163
|
+
parser,
|
|
164
|
+
section,
|
|
165
|
+
option,
|
|
166
|
+
value,
|
|
167
|
+
defaults
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
elif value.startswith("\""):
|
|
171
|
+
return value.strip("\"")
|
|
172
|
+
|
|
173
|
+
else:
|
|
174
|
+
return super().before_get(parser, section, option, value, defaults)
|
|
175
|
+
|
|
176
|
+
def before_set(self, parser, section, option, value):
|
|
177
|
+
return super().before_set(parser, section, option, value)
|
|
178
|
+
|
|
179
|
+
def before_read(self, parser, section, option, value):
|
|
180
|
+
return super().before_read(parser, section, option, value)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class Config(ConfigParser):
|
|
184
|
+
"""Parse a configuration file
|
|
185
|
+
|
|
186
|
+
:Example:
|
|
187
|
+
c = Config("<PATH-TO-CONFIG-FILE>")
|
|
188
|
+
c["<SECTION-NAME>"]["<VALUE>"]
|
|
189
|
+
|
|
190
|
+
https://docs.python.org/3/library/configparser.html
|
|
191
|
+
"""
|
|
192
|
+
def __init__(self, path, **kwargs):
|
|
193
|
+
from ..path import Filepath # avoid circular dependency
|
|
194
|
+
self.path = Filepath(path)
|
|
195
|
+
super().__init__(
|
|
196
|
+
interpolation=ConfigInterpolation()
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if self.path.is_file():
|
|
200
|
+
self.read(path)
|
|
201
|
+
|
|
202
|
+
def optionxform(self, optionstr):
|
|
203
|
+
"""Overrides parent to allow customization of the option value"""
|
|
204
|
+
return self._interpolation.optionxform(self, optionstr)
|
|
205
|
+
|
|
206
|
+
def read_dict(self, dictionary, source='<dict>'):
|
|
207
|
+
"""Overrides parent to allow customization of the raw value
|
|
208
|
+
|
|
209
|
+
I had to copy this whole method body just to change one line because it
|
|
210
|
+
just casts value to str instead of allowing the value to be manipulated
|
|
211
|
+
in ._interpolation.before_set(). Sigh
|
|
212
|
+
"""
|
|
213
|
+
elements_added = set()
|
|
214
|
+
for section, keys in dictionary.items():
|
|
215
|
+
section = str(section)
|
|
216
|
+
try:
|
|
217
|
+
self.add_section(section)
|
|
218
|
+
|
|
219
|
+
except (DuplicateSectionError, ValueError):
|
|
220
|
+
if self._strict and section in elements_added:
|
|
221
|
+
raise
|
|
222
|
+
|
|
223
|
+
elements_added.add(section)
|
|
224
|
+
for key, value in keys.items():
|
|
225
|
+
key = self.optionxform(str(key))
|
|
226
|
+
|
|
227
|
+
# ADDED BY JAY
|
|
228
|
+
value = self._interpolation.before_set_value(
|
|
229
|
+
self,
|
|
230
|
+
section,
|
|
231
|
+
key,
|
|
232
|
+
value
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if self._strict and (section, key) in elements_added:
|
|
236
|
+
raise DuplicateOptionError(section, key, source)
|
|
237
|
+
|
|
238
|
+
elements_added.add((section, key))
|
|
239
|
+
self.set(section, key, value)
|
|
240
|
+
|
|
241
|
+
def write(self):
|
|
242
|
+
with self.path.open("w") as fp:
|
|
243
|
+
super().write(fp)
|
|
244
|
+
|
|
245
|
+
def _write_section(self, fp, section_name, section_items, delimiter):
|
|
246
|
+
"""Overrides parent to ignore empty sections"""
|
|
247
|
+
if section_items:
|
|
248
|
+
super()._write_section(fp, section_name, section_items, delimiter)
|
|
249
|
+
|
|
250
|
+
def jsonable(self):
|
|
251
|
+
d = {}
|
|
252
|
+
for k in self:
|
|
253
|
+
d[k] = dict(self[k])
|
|
254
|
+
|
|
255
|
+
return d
|
|
256
|
+
|