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
datatypes/compat.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import unicode_literals, division, print_function, absolute_import
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
import cPickle as pickle
|
|
7
|
+
except ImportError:
|
|
8
|
+
import pickle
|
|
9
|
+
|
|
10
|
+
# shamelessly ripped from https://github.com/kennethreitz/requests/blob/master/requests/compat.py
|
|
11
|
+
# Syntax sugar.
|
|
12
|
+
_ver = sys.version_info
|
|
13
|
+
is_py2 = _ver[0] == 2
|
|
14
|
+
is_py3 = _ver[0] == 3
|
|
15
|
+
|
|
16
|
+
if is_py2:
|
|
17
|
+
basestring = basestring
|
|
18
|
+
unicode = unicode
|
|
19
|
+
range = xrange # range is now always an iterator
|
|
20
|
+
input = raw_input
|
|
21
|
+
cmp = cmp
|
|
22
|
+
|
|
23
|
+
import Queue as queue
|
|
24
|
+
|
|
25
|
+
# using _* makes __all__ not export this automatically, so you have to
|
|
26
|
+
# explicitly import this (from datatypes.compat import _thread), the reason
|
|
27
|
+
# is because the thread module is lowkey deprecated in favor of the
|
|
28
|
+
# threading module
|
|
29
|
+
#
|
|
30
|
+
# https://stackoverflow.com/questions/1141047/why-was-the-thread-module-renamed-to-thread-in-python-3-x
|
|
31
|
+
#
|
|
32
|
+
# I've chosen to keep the _thread syntax to prod me into finding a better
|
|
33
|
+
# way to do something, but I still have access to it explicitely if I need it
|
|
34
|
+
import thread as _thread
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from cStringIO import StringIO
|
|
38
|
+
except ImportError:
|
|
39
|
+
from StringIO import StringIO
|
|
40
|
+
|
|
41
|
+
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
|
42
|
+
from BaseHTTPServer import (
|
|
43
|
+
BaseHTTPRequestHandler,
|
|
44
|
+
HTTPServer,
|
|
45
|
+
)
|
|
46
|
+
import SocketServer as socketserver
|
|
47
|
+
import Cookie as cookies
|
|
48
|
+
import __builtin__ as builtins
|
|
49
|
+
|
|
50
|
+
from urllib import urlencode
|
|
51
|
+
from urllib2 import (
|
|
52
|
+
Request,
|
|
53
|
+
urlopen,
|
|
54
|
+
URLError,
|
|
55
|
+
HTTPError,
|
|
56
|
+
)
|
|
57
|
+
import urlparse as parse
|
|
58
|
+
|
|
59
|
+
import itertools
|
|
60
|
+
zip = itertools.izip
|
|
61
|
+
zip_longest = itertools.izip_longest
|
|
62
|
+
|
|
63
|
+
from collections import (
|
|
64
|
+
Iterable,
|
|
65
|
+
Mapping,
|
|
66
|
+
Callable,
|
|
67
|
+
MutableSequence,
|
|
68
|
+
Sequence,
|
|
69
|
+
Collection,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
from HTMLParser import HTMLParser
|
|
74
|
+
#unescape = HTMLParser().unescape
|
|
75
|
+
|
|
76
|
+
import cgi
|
|
77
|
+
class html(object):
|
|
78
|
+
@classmethod
|
|
79
|
+
def escape(cls, *args, **kwargs):
|
|
80
|
+
return cgi.escape(*args, **kwargs)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def unescape(cls, *args, **kwargs):
|
|
84
|
+
return HTMLParser().unescape(*args, **kwargs)
|
|
85
|
+
|
|
86
|
+
# NOTE -- getargspace isn't a full mapping of getfullargspec
|
|
87
|
+
from inspect import getargspec as getfullargspec
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
exec("""def reraise(exception_class, e, traceback=None):
|
|
91
|
+
try:
|
|
92
|
+
raise exception_class, e, traceback
|
|
93
|
+
finally:
|
|
94
|
+
traceback = None
|
|
95
|
+
""")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
else:
|
|
99
|
+
basestring = (str, bytes)
|
|
100
|
+
unicode = str
|
|
101
|
+
long = int
|
|
102
|
+
input = input
|
|
103
|
+
|
|
104
|
+
import queue
|
|
105
|
+
import _thread
|
|
106
|
+
from io import StringIO
|
|
107
|
+
from http.server import (
|
|
108
|
+
HTTPServer,
|
|
109
|
+
SimpleHTTPRequestHandler,
|
|
110
|
+
BaseHTTPRequestHandler,
|
|
111
|
+
)
|
|
112
|
+
import socketserver
|
|
113
|
+
from http import cookies
|
|
114
|
+
import builtins
|
|
115
|
+
|
|
116
|
+
from functools import reduce # removed from common functions
|
|
117
|
+
from html.parser import HTMLParser
|
|
118
|
+
from urllib import parse
|
|
119
|
+
from urllib.parse import urlencode
|
|
120
|
+
from urllib.request import Request, urlopen
|
|
121
|
+
from urllib.error import URLError, HTTPError
|
|
122
|
+
try:
|
|
123
|
+
import html
|
|
124
|
+
#from html import unescape
|
|
125
|
+
except ImportError:
|
|
126
|
+
import cgi
|
|
127
|
+
class html(object):
|
|
128
|
+
@classmethod
|
|
129
|
+
def escape(cls, *args, **kwargs):
|
|
130
|
+
return cgi.escape(*args, **kwargs)
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def unescape(cls, *args, **kwargs):
|
|
134
|
+
return HTMLParser().unescape(*args, **kwargs)
|
|
135
|
+
#unescape = HTMLParser.unescape
|
|
136
|
+
|
|
137
|
+
from inspect import getfullargspec
|
|
138
|
+
from itertools import zip_longest
|
|
139
|
+
|
|
140
|
+
from collections.abc import (
|
|
141
|
+
Iterable,
|
|
142
|
+
Mapping,
|
|
143
|
+
Callable,
|
|
144
|
+
MutableSequence,
|
|
145
|
+
Sequence,
|
|
146
|
+
Collection,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ripped from six https://github.com/benjaminp/six
|
|
151
|
+
def reraise(exception_class, e, traceback=None):
|
|
152
|
+
"""the 3 params correspond to the return value of sys.exc_info()
|
|
153
|
+
|
|
154
|
+
https://docs.python.org/3/library/sys.html#sys.exc_info
|
|
155
|
+
|
|
156
|
+
:param exception_class: BaseException, the class of the exception to
|
|
157
|
+
reraise
|
|
158
|
+
:param e: BaseException instance, the actual exception instance
|
|
159
|
+
:param traceback: traceback, the stack trace
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
e = exception_class("" if e is None else e)
|
|
163
|
+
if e.__traceback__ is not traceback:
|
|
164
|
+
raise e.with_traceback(traceback)
|
|
165
|
+
raise e
|
|
166
|
+
finally:
|
|
167
|
+
e = None
|
|
168
|
+
traceback = None
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# py3 has no cmp function for some strange reason
|
|
172
|
+
# https://codegolf.stackexchange.com/a/49779
|
|
173
|
+
def cmp(a, b):
|
|
174
|
+
return (a > b) - (a < b)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
Str = unicode if is_py2 else str
|
|
178
|
+
Bytes = str if is_py2 else bytes
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# TODO using reraise
|
|
182
|
+
|
|
183
|
+
# if py_2:
|
|
184
|
+
# #raise error_info[0].__class__, error_info[0], error_info[1][2]
|
|
185
|
+
# reraise(*error_info)
|
|
186
|
+
# #raise error_info[0].__class__, error_info[1], error_info[2]
|
|
187
|
+
#
|
|
188
|
+
# elif py_3:
|
|
189
|
+
# #e, exc_info = error_info
|
|
190
|
+
# #et, ei, tb = exc_info
|
|
191
|
+
#
|
|
192
|
+
# reraise(*error_info)
|
|
193
|
+
# #et, ei, tb = error_info
|
|
194
|
+
# #raise ei.with_traceback(tb)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# if not error_info:
|
|
198
|
+
# exc_info = sys.exc_info()
|
|
199
|
+
# #raise e.__class__, e, exc_info[2]
|
|
200
|
+
# #self.error_info = (e, exc_info)
|
|
201
|
+
# self.error_info = exc_info
|
|
202
|
+
#
|
|
203
|
+
# if error_info:
|
|
204
|
+
#
|
|
205
|
+
# reraise(*error_info)
|
|
206
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
|
|
5
|
+
from .base import Environ
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
###############################################################################
|
|
9
|
+
# Actual environment configuration that is used throughout the package
|
|
10
|
+
###############################################################################
|
|
11
|
+
environ = Environ("DATATYPES_")
|
|
12
|
+
|
|
13
|
+
environ.setdefault("ENCODING", "UTF-8")
|
|
14
|
+
"""For things that need encoding, this will be the default encoding if nothing
|
|
15
|
+
else is passed in"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
environ.setdefault("ENCODING_ERRORS", "replace")
|
|
19
|
+
"""For things that need encoding, this will handle any errors"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# 2021-1-15 - we place our cache by default into another directory because I
|
|
23
|
+
# have problems running some commands like touch on the base temp directory on
|
|
24
|
+
# MacOS on Python3, no idea why but I couldn't get around it
|
|
25
|
+
environ.setdefault(
|
|
26
|
+
"CACHE_DIR",
|
|
27
|
+
os.path.join(tempfile.gettempdir(), environ.namespace.lower().rstrip("_"))
|
|
28
|
+
)
|
|
29
|
+
"""the default caching directory for things that need a cache folder"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
environ.setdefault("USER_AGENT", "")
|
|
33
|
+
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from ...compat import *
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Environ(Mapping):
|
|
9
|
+
"""Create an Environ namespace instance
|
|
10
|
+
|
|
11
|
+
you would usually create this like this:
|
|
12
|
+
|
|
13
|
+
environ = Environ("PREFIX_")
|
|
14
|
+
|
|
15
|
+
Then you can access any environment variables with that prefix from the
|
|
16
|
+
`environ` instance.
|
|
17
|
+
|
|
18
|
+
:Example:
|
|
19
|
+
# in your environment
|
|
20
|
+
export PREFIX_FOOBAR=1
|
|
21
|
+
export PREFIX_CHE="this is the che value"
|
|
22
|
+
|
|
23
|
+
# in your python code
|
|
24
|
+
environ = Environ("PREFIX_")
|
|
25
|
+
|
|
26
|
+
print(environ.FOOBAR) # "1"
|
|
27
|
+
print(environ.CHE) # this is the che value
|
|
28
|
+
|
|
29
|
+
# by default, all environ values are string, but you can set defaults
|
|
30
|
+
# and set the type
|
|
31
|
+
environ.setdefault("BAZ", 1000, type=int)
|
|
32
|
+
|
|
33
|
+
print(environ.BAZ, type(environ.BAZ)) # 1000 <class 'int'>
|
|
34
|
+
"""
|
|
35
|
+
@classmethod
|
|
36
|
+
def find_namespace(cls, prefix):
|
|
37
|
+
namespace = ""
|
|
38
|
+
if prefix:
|
|
39
|
+
namespace = prefix.split(".", maxsplit=1)[0].upper()
|
|
40
|
+
if not namespace.endswith("_"):
|
|
41
|
+
namespace += "_"
|
|
42
|
+
return namespace
|
|
43
|
+
|
|
44
|
+
def __init__(self, namespace="", environ=None):
|
|
45
|
+
"""
|
|
46
|
+
:param namespace: str, usually __name__ from the calling module but can
|
|
47
|
+
also be "PREFIX_" or something like that
|
|
48
|
+
:param environ: Mapping|None, the environment you want this instance
|
|
49
|
+
to wrap, it defaults to os.environ if a mapping isn't passed in
|
|
50
|
+
"""
|
|
51
|
+
self.__dict__["namespace"] = self.find_namespace(namespace)
|
|
52
|
+
self.__dict__["defaults"] = {}
|
|
53
|
+
if environ is None:
|
|
54
|
+
self.__dict__["environ"] = os.environ
|
|
55
|
+
|
|
56
|
+
else:
|
|
57
|
+
self.__dict__["environ"] = environ
|
|
58
|
+
|
|
59
|
+
def setdefault(self, key, value, type=None, override=False):
|
|
60
|
+
"""Set a default value to be returned if there isn't a value set in
|
|
61
|
+
the environment
|
|
62
|
+
|
|
63
|
+
NOTE -- this works differently than dict.setdefault because it doesn't
|
|
64
|
+
actually set the value into the environment
|
|
65
|
+
|
|
66
|
+
:param key: str
|
|
67
|
+
:param value: Any
|
|
68
|
+
:param type: type, this will be used to normalize the actual
|
|
69
|
+
environment value if it exists also
|
|
70
|
+
:param override: bool, sometimes when setting default configuration
|
|
71
|
+
you might want to change the default value, this allows you to
|
|
72
|
+
do that. If True then any previously set default value will be
|
|
73
|
+
overridden
|
|
74
|
+
"""
|
|
75
|
+
ek = self.ekey(key)
|
|
76
|
+
if ek not in self.defaults or override:
|
|
77
|
+
self.defaults[ek] = {
|
|
78
|
+
"value": value,
|
|
79
|
+
"type": type,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
def set(self, key, value):
|
|
83
|
+
self.__setattr__(key, value)
|
|
84
|
+
#os.environ[self.key(key)] = value
|
|
85
|
+
|
|
86
|
+
def __setattr__(self, key, value):
|
|
87
|
+
self.__setitem__(key, value)
|
|
88
|
+
|
|
89
|
+
def __setitem__(self, key, value):
|
|
90
|
+
self.environ[self.key(key)] = value
|
|
91
|
+
|
|
92
|
+
def nset(self, key, values):
|
|
93
|
+
"""Given a list of values, this will set key_* where * is
|
|
94
|
+
1 -> len(values)
|
|
95
|
+
|
|
96
|
+
:param key: string, the key that will be added to the environment as
|
|
97
|
+
key_*
|
|
98
|
+
:param values: list, a list of values
|
|
99
|
+
"""
|
|
100
|
+
self.ndelete(key)
|
|
101
|
+
|
|
102
|
+
for i, v in enumerate(values, 1):
|
|
103
|
+
k = "{}_{}".format(key, i)
|
|
104
|
+
self.set(k, v)
|
|
105
|
+
|
|
106
|
+
def delete(self, key):
|
|
107
|
+
"""remove key from the environment"""
|
|
108
|
+
self.__delattr__(key)
|
|
109
|
+
#os.environ.pop(self.key(key), None)
|
|
110
|
+
|
|
111
|
+
def __delitem__(self, key):
|
|
112
|
+
#os.environ.pop(self.key(key), None)
|
|
113
|
+
self.environ.pop(self.key(key), None)
|
|
114
|
+
|
|
115
|
+
def ndelete(self, key):
|
|
116
|
+
"""remove all key_* from the environment"""
|
|
117
|
+
for k in self.nkeys(key):
|
|
118
|
+
#os.environ.pop(k)
|
|
119
|
+
self.environ.pop(k, None)
|
|
120
|
+
|
|
121
|
+
def get(self, key, default=None):
|
|
122
|
+
"""get a value for key from the environment
|
|
123
|
+
|
|
124
|
+
:param key: string, this will be normalized using the key() method
|
|
125
|
+
:returns: mixed, the value in the environment of key, or default if
|
|
126
|
+
key is not in the environment
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
return self[key]
|
|
130
|
+
|
|
131
|
+
except KeyError:
|
|
132
|
+
return default
|
|
133
|
+
#return os.environ.get(self.key(key), default)
|
|
134
|
+
|
|
135
|
+
def pop(self, key, *defaults):
|
|
136
|
+
try:
|
|
137
|
+
return self.__getitem__(key)
|
|
138
|
+
|
|
139
|
+
except KeyError:
|
|
140
|
+
if defaults:
|
|
141
|
+
return defaults[0]
|
|
142
|
+
|
|
143
|
+
else:
|
|
144
|
+
raise
|
|
145
|
+
|
|
146
|
+
def __getitem__(self, key):
|
|
147
|
+
ek = self.ekey(key)
|
|
148
|
+
k = self.key(key)
|
|
149
|
+
try:
|
|
150
|
+
#return os.environ[k]
|
|
151
|
+
v = self.environ[k]
|
|
152
|
+
|
|
153
|
+
except KeyError:
|
|
154
|
+
v = self.defaults[ek]["value"]
|
|
155
|
+
|
|
156
|
+
if ek in self.defaults:
|
|
157
|
+
if self.defaults[ek]["type"]:
|
|
158
|
+
v = self.defaults[ek]["type"](v)
|
|
159
|
+
|
|
160
|
+
return v
|
|
161
|
+
|
|
162
|
+
def __getattr__(self, key):
|
|
163
|
+
try:
|
|
164
|
+
return self.__getitem__(key)
|
|
165
|
+
|
|
166
|
+
except KeyError as e:
|
|
167
|
+
raise AttributeError(key) from e
|
|
168
|
+
|
|
169
|
+
def nget(self, key):
|
|
170
|
+
"""this will look for key, and key_N (where
|
|
171
|
+
N is 1 to infinity) in the environment
|
|
172
|
+
|
|
173
|
+
The number checks (eg *_1, *_2) go in order, so you can't do *_1, *_3,
|
|
174
|
+
because it will fail on missing *_2 and move on, so make sure your
|
|
175
|
+
number suffixes are in order (eg, 1, 2, 3, ...)
|
|
176
|
+
|
|
177
|
+
:param key: string, the name of the environment variables
|
|
178
|
+
:returns: generator, the found values for key and key_* variants
|
|
179
|
+
"""
|
|
180
|
+
for nkey in self.nkeys(key):
|
|
181
|
+
#yield os.environ[nkey]
|
|
182
|
+
yield self.environ[nkey]
|
|
183
|
+
|
|
184
|
+
def keys(self):
|
|
185
|
+
"""yields all the keys of the given namespace currently in the
|
|
186
|
+
environment"""
|
|
187
|
+
for k, _ in self.items():
|
|
188
|
+
yield k
|
|
189
|
+
|
|
190
|
+
def values(self):
|
|
191
|
+
"""yields all the keys of the given namespace currently in the
|
|
192
|
+
environment"""
|
|
193
|
+
for _, v in self.items():
|
|
194
|
+
yield v
|
|
195
|
+
|
|
196
|
+
def items(self):
|
|
197
|
+
#for k, v in os.environ.items():
|
|
198
|
+
seen = set()
|
|
199
|
+
for k, v in self.environ.items():
|
|
200
|
+
if k.startswith(self.namespace):
|
|
201
|
+
ek = self.ekey(k)
|
|
202
|
+
seen.add(ek)
|
|
203
|
+
yield ek, v
|
|
204
|
+
|
|
205
|
+
for ek, d in self.defaults.items():
|
|
206
|
+
if ek not in seen:
|
|
207
|
+
yield ek, d["value"]
|
|
208
|
+
|
|
209
|
+
def __iter__(self):
|
|
210
|
+
return self.keys()
|
|
211
|
+
|
|
212
|
+
def key(self, key):
|
|
213
|
+
"""normalizes key to have the namespace
|
|
214
|
+
|
|
215
|
+
:Example:
|
|
216
|
+
environ = Environ("FOO_")
|
|
217
|
+
k = environ.key("BAR")
|
|
218
|
+
print(k) # FOO_BAR
|
|
219
|
+
"""
|
|
220
|
+
if self.namespace and not key.startswith(self.namespace):
|
|
221
|
+
key = self.namespace + key
|
|
222
|
+
return key
|
|
223
|
+
|
|
224
|
+
def ekey(self, key):
|
|
225
|
+
"""Given a full namespaced key return the key name without the
|
|
226
|
+
namespace
|
|
227
|
+
|
|
228
|
+
:Example:
|
|
229
|
+
environ = Environ("FOO_")
|
|
230
|
+
k = environ.ekey("FOO_BAR")
|
|
231
|
+
print(k) # BAR
|
|
232
|
+
"""
|
|
233
|
+
return key.replace(self.namespace, "")
|
|
234
|
+
|
|
235
|
+
def nkey(self, key, n):
|
|
236
|
+
"""internal method. Helper method for nkeys"""
|
|
237
|
+
k = self.key(key)
|
|
238
|
+
for fmt in ["{key}_{n}", "{key}{n}"]:
|
|
239
|
+
nkey = fmt.format(key=k, n=n)
|
|
240
|
+
if nkey in self.environ:
|
|
241
|
+
#if nkey in os.environ:
|
|
242
|
+
return nkey
|
|
243
|
+
|
|
244
|
+
raise KeyError("Environ {} has no {} key".format(key, n))
|
|
245
|
+
|
|
246
|
+
def nkeys(self, key):
|
|
247
|
+
"""This returns the actual environment variable names from * -> *_N
|
|
248
|
+
|
|
249
|
+
:param key: str, the name of the environment variables
|
|
250
|
+
:returns: generator[str], the found environment names
|
|
251
|
+
"""
|
|
252
|
+
k = self.key(key)
|
|
253
|
+
#if k in os.environ:
|
|
254
|
+
if k in self.environ:
|
|
255
|
+
yield k
|
|
256
|
+
|
|
257
|
+
# now try importing _1 -> _N prefixes
|
|
258
|
+
n = 0
|
|
259
|
+
while True:
|
|
260
|
+
try:
|
|
261
|
+
yield self.nkey(key, n)
|
|
262
|
+
|
|
263
|
+
except KeyError:
|
|
264
|
+
# 0 is a special case, so if it fails we keep going
|
|
265
|
+
if n:
|
|
266
|
+
# we are done because we missed an n value
|
|
267
|
+
break
|
|
268
|
+
|
|
269
|
+
finally:
|
|
270
|
+
n += 1
|
|
271
|
+
|
|
272
|
+
def paths(self, key, sep=None):
|
|
273
|
+
"""splits the value at key by the path separator and yields each
|
|
274
|
+
individual path part
|
|
275
|
+
|
|
276
|
+
splits each value using the path separator
|
|
277
|
+
|
|
278
|
+
:param key: str, the key
|
|
279
|
+
:param sep: str, the value to split on, defaults to the OS's underlying
|
|
280
|
+
path separator
|
|
281
|
+
:returns: generator, each part of the value found at key
|
|
282
|
+
"""
|
|
283
|
+
for paths in self.nget(key):
|
|
284
|
+
for p in self.split_value(paths, sep=sep):
|
|
285
|
+
yield p
|
|
286
|
+
|
|
287
|
+
def split_value(self, value, sep=None):
|
|
288
|
+
"""Split value using sep, sep defaults to the os path separator
|
|
289
|
+
|
|
290
|
+
:param value: str, the value to split
|
|
291
|
+
:param sep: str, the value to split on, defaults to the OS's underlying
|
|
292
|
+
path separator
|
|
293
|
+
:returns: list[str]
|
|
294
|
+
"""
|
|
295
|
+
sep = sep or os.pathsep
|
|
296
|
+
return value.split(sep)
|
|
297
|
+
|
|
298
|
+
def has(self, key):
|
|
299
|
+
"""Return True if key is in the environment
|
|
300
|
+
|
|
301
|
+
:param k: str
|
|
302
|
+
:returns: bool
|
|
303
|
+
"""
|
|
304
|
+
return (
|
|
305
|
+
self.key(key) in self.environ
|
|
306
|
+
or self.ekey(key) in self.defaults
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
def __contains__(self, key):
|
|
310
|
+
return self.has(key)
|
|
311
|
+
|
|
312
|
+
def __len__(self):
|
|
313
|
+
return len(list(self.keys()))
|
|
314
|
+
|
|
315
|
+
def load_path(self, path):
|
|
316
|
+
"""Load the environment file found at `path` into this instance
|
|
317
|
+
|
|
318
|
+
:param path: str|Path
|
|
319
|
+
:returns: int, how many variables were loaded from the file
|
|
320
|
+
"""
|
|
321
|
+
from .token import EnvironTokenizer # avoid circular dependency
|
|
322
|
+
|
|
323
|
+
count = 0
|
|
324
|
+
path = Path(path)
|
|
325
|
+
with path.open() as fp:
|
|
326
|
+
ts = EnvironTokenizer(fp, self)
|
|
327
|
+
for t in ts:
|
|
328
|
+
self.set(t.name, t.value)
|
|
329
|
+
count += 1
|
|
330
|
+
|
|
331
|
+
return count
|
|
332
|
+
|