python-ubercode-utils 2.0.2__py3-none-any.whl → 2.0.4__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.
- {python_ubercode_utils-2.0.2.dist-info → python_ubercode_utils-2.0.4.dist-info}/METADATA +1 -1
- python_ubercode_utils-2.0.4.dist-info/RECORD +14 -0
- {python_ubercode_utils-2.0.2.dist-info → python_ubercode_utils-2.0.4.dist-info}/WHEEL +1 -1
- ubercode/utils/convert.py +5 -5
- ubercode/utils/environment.py +40 -0
- ubercode/utils/urls.py +95 -2
- python_ubercode_utils-2.0.2.dist-info/RECORD +0 -14
- {python_ubercode_utils-2.0.2.dist-info → python_ubercode_utils-2.0.4.dist-info}/LICENSE +0 -0
- {python_ubercode_utils-2.0.2.dist-info → python_ubercode_utils-2.0.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
ubercode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
ubercode/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
ubercode/utils/convert.py,sha256=INxAIlIkwTeHSdVpqm8R_N5MzrSrjY9CduioHipu39g,11370
|
|
4
|
+
ubercode/utils/cursor.py,sha256=zHqbmUavCF0l7ck_Tu4PfgSzRO762ULKHYsnqa3r0uY,1118
|
|
5
|
+
ubercode/utils/data.py,sha256=DStj9BAXPSPvQRZwfEQVYt19EoMavyNFt11U8ZnhagE,4364
|
|
6
|
+
ubercode/utils/dataframe.py,sha256=3AYoZGZB7wtN5brBDfRgnuIaEubhRrSc7qx8hi_vaaE,1616
|
|
7
|
+
ubercode/utils/environment.py,sha256=97uYs0zGBkk7TfKuml6Th27kpdtYX_ILwfZTQW3Xac8,15818
|
|
8
|
+
ubercode/utils/logging.py,sha256=s4yIJWHo9MVO4oSk5IF3z6XTk-FOuyEiKnPFkiza3h4,9204
|
|
9
|
+
ubercode/utils/urls.py,sha256=N2B0s849WRpV_JWc20hBb4129UZZkeWEcPjfd9rggrY,13399
|
|
10
|
+
python_ubercode_utils-2.0.4.dist-info/LICENSE,sha256=lch0WEJaqJY3C5aCYSr0u6Gw0set96a2fp9ZWJRYuR8,1069
|
|
11
|
+
python_ubercode_utils-2.0.4.dist-info/METADATA,sha256=L5_YcnH89oIyTP_EeOLQR-AycJUR4YLHvEly5_RSRV0,1225
|
|
12
|
+
python_ubercode_utils-2.0.4.dist-info/WHEEL,sha256=hPN0AlP2dZM_3ZJZWP4WooepkmU9wzjGgCLCeFjkHLA,92
|
|
13
|
+
python_ubercode_utils-2.0.4.dist-info/top_level.txt,sha256=5BojzbvNCpPkFXcVQVr7cfovhbYQYAlMKgiHSMgi7VU,9
|
|
14
|
+
python_ubercode_utils-2.0.4.dist-info/RECORD,,
|
ubercode/utils/convert.py
CHANGED
|
@@ -13,7 +13,7 @@ FALSE_VALUES = [None, False, 0, "0", "n", "f", "false", "no", "off"]
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
# -------- helper utilities ----------
|
|
16
|
-
def strip(value: str
|
|
16
|
+
def strip(value: str | None, strip_chars: str = None, left: bool = True, right: bool = True) -> str | None:
|
|
17
17
|
"""
|
|
18
18
|
return stripped value if possible or original value
|
|
19
19
|
:param value: str value to be stripped
|
|
@@ -94,7 +94,7 @@ def to_bool(value) -> bool:
|
|
|
94
94
|
return bool(value)
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
def is_true(value: int
|
|
97
|
+
def is_true(value: int | bool | str) -> bool:
|
|
98
98
|
"""
|
|
99
99
|
Convert <value> to a True boolean value. Useful when you want to convert a passed parameter to True if it matches
|
|
100
100
|
one of the defined TRUE_VALUES above; otherwise False.
|
|
@@ -121,7 +121,7 @@ def to_js_bool(bool_value: bool) -> str:
|
|
|
121
121
|
return "false"
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
def to_int(value, default: int = 0, none_to_default: bool = True, suppress_warnings: bool = True) -> int
|
|
124
|
+
def to_int(value, default: int | None = 0, none_to_default: bool = True, suppress_warnings: bool = True) -> int | None:
|
|
125
125
|
"""
|
|
126
126
|
Convert <value> to int. Will always return integer or none instead of throwing exception
|
|
127
127
|
@param value: value to be converted
|
|
@@ -201,7 +201,7 @@ def from_iso8601_compact(value: Any = None, tz: timezone = timezone.utc):
|
|
|
201
201
|
return _value
|
|
202
202
|
|
|
203
203
|
|
|
204
|
-
def to_date(value: Any = None, tz: timezone
|
|
204
|
+
def to_date(value: Any = None, tz: timezone | None = timezone.utc, none_to_now: bool = True, suppress_warnings: bool = True):
|
|
205
205
|
"""
|
|
206
206
|
Convert string to python date. Currently, only concerned about iso8601 and db type formats.
|
|
207
207
|
None returns current date by default but can be overridden with none_to_now optional parameter
|
|
@@ -232,7 +232,7 @@ def to_date(value: Any = None, tz: timezone or None = timezone.utc, none_to_now:
|
|
|
232
232
|
|
|
233
233
|
|
|
234
234
|
# -------- helper conversions --------
|
|
235
|
-
def to_mask(value: str
|
|
235
|
+
def to_mask(value: str | None) -> str | None:
|
|
236
236
|
_mask = value
|
|
237
237
|
if isinstance(value, str) and value is not None and len(value) > 0:
|
|
238
238
|
# if we are less than 4 chars then mask the entire string
|
ubercode/utils/environment.py
CHANGED
|
@@ -7,8 +7,10 @@ import time
|
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
from typing import Any, Tuple
|
|
9
9
|
from pathlib import Path
|
|
10
|
+
|
|
10
11
|
from ubercode.utils.logging import ColorLogger
|
|
11
12
|
from ubercode.utils import convert
|
|
13
|
+
from ubercode.utils.urls import DjUrl
|
|
12
14
|
|
|
13
15
|
_utils_settings_logger = ColorLogger("utils.environment")
|
|
14
16
|
|
|
@@ -225,6 +227,44 @@ class Environment:
|
|
|
225
227
|
f"{db_parts[0]}[{db_parts[1]}][{db_parts[2]}] has a database or property naming issue!")
|
|
226
228
|
return db_dict
|
|
227
229
|
|
|
230
|
+
def override_database_urls(self, db_dict: dict) -> dict:
|
|
231
|
+
"""
|
|
232
|
+
Much like above,iterates over environment variables and overrides any database variables. However, instead
|
|
233
|
+
of looking for each variable with a pattern like DATABASES__default__ENGINE it looks for DJ_URL_ prefix and
|
|
234
|
+
parses the url into a DjUrl object. This allows property files to use one line per database config
|
|
235
|
+
instead of one key,value pair per variable like: DJ_URL_default = 'django.db.backends.mysql://scott:tiger@localhost:1366/test'
|
|
236
|
+
NOTE: you can omit everything except what you want to replace like: '://:newpassword@' which
|
|
237
|
+
will only replace the password
|
|
238
|
+
|
|
239
|
+
:param db_dict: settings database dictionary to replace variables in ex: DATABASES
|
|
240
|
+
:return: new dict with updated overridden values
|
|
241
|
+
"""
|
|
242
|
+
items = []
|
|
243
|
+
if hasattr(self._env_map, "items"):
|
|
244
|
+
items = self._env_map.items()
|
|
245
|
+
elif hasattr(os.environ, "items"):
|
|
246
|
+
items = os.environ.items()
|
|
247
|
+
for k, v in items:
|
|
248
|
+
# unlike override_database_variables() we will look for prefix DJ_URL_
|
|
249
|
+
if k and str(k).upper().startswith('DJ_URL_') and v:
|
|
250
|
+
# the actual key is the remaining part left
|
|
251
|
+
key = str(k)[len('DJ_URL_'):]
|
|
252
|
+
djurl = DjUrl(str(v))
|
|
253
|
+
for attr, value in vars(djurl).items():
|
|
254
|
+
if value:
|
|
255
|
+
if not db_dict.get(key):
|
|
256
|
+
self._logger.warn(f'Missing database key [{key}]! creating...')
|
|
257
|
+
db_dict[key] = {}
|
|
258
|
+
_log_from_value = db_dict[key].get(attr.upper(), 'None')
|
|
259
|
+
_log_to_value = value
|
|
260
|
+
if attr.upper() in self._secret_properties:
|
|
261
|
+
_log_to_value = convert.to_mask(value)
|
|
262
|
+
db_dict[key][attr.upper()] = value
|
|
263
|
+
self._logger.info(
|
|
264
|
+
f'set [{key}][{attr.upper()}] from [{_log_from_value}] to [{_log_to_value}]')
|
|
265
|
+
return db_dict
|
|
266
|
+
|
|
267
|
+
|
|
228
268
|
class FauxApp:
|
|
229
269
|
def __init__(self, logger: ColorLogger = None, notebook_path: Path = Path(), default_dict: dict = None) -> None:
|
|
230
270
|
self._logger = logger if logger else _utils_settings_logger
|
ubercode/utils/urls.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from urllib.parse import urlsplit
|
|
3
|
-
from ubercode.utils.convert import to_str
|
|
3
|
+
from ubercode.utils.convert import to_str, to_int
|
|
4
4
|
from pathlib import PurePath
|
|
5
|
-
|
|
5
|
+
from .convert import to_mask
|
|
6
6
|
|
|
7
7
|
class ParsedQueryString:
|
|
8
8
|
"""
|
|
@@ -216,6 +216,99 @@ class ParsedUrl:
|
|
|
216
216
|
"""
|
|
217
217
|
return self.url
|
|
218
218
|
|
|
219
|
+
class DjUrl:
|
|
220
|
+
engine = None
|
|
221
|
+
user = None
|
|
222
|
+
password = None
|
|
223
|
+
host = None
|
|
224
|
+
port = None
|
|
225
|
+
name = None
|
|
226
|
+
|
|
227
|
+
def __init__(self, dj_url: str = None) -> None:
|
|
228
|
+
"""
|
|
229
|
+
parses a packed "django_url" into its parts following similar rules to SqlAlchemy
|
|
230
|
+
format: engine://user:password@host[:port]/dbname
|
|
231
|
+
example: django.db.backends.mysql://scott:tiger@localhost:1366/test
|
|
232
|
+
|
|
233
|
+
NOTE: asking for the string value will give back the original packed url masking the password
|
|
234
|
+
NOTE: to_dict will give back the dictionary values to be added or replaced in the DATABASES dict
|
|
235
|
+
|
|
236
|
+
:param url: packed django url ex: django.db.backends.mysql://scott:tiger@localhost:1366/test
|
|
237
|
+
"""
|
|
238
|
+
dj_url = dj_url or ""
|
|
239
|
+
dj_url = dj_url.strip()
|
|
240
|
+
encoded = False
|
|
241
|
+
if dj_url.endswith('?--atencoded'):
|
|
242
|
+
encoded = True
|
|
243
|
+
dj_url = dj_url[:-len('?--atencoded')].strip()
|
|
244
|
+
if not dj_url:
|
|
245
|
+
return
|
|
246
|
+
pos = dj_url.find('://')
|
|
247
|
+
if pos > -1:
|
|
248
|
+
self.engine = dj_url[:pos].strip() or None
|
|
249
|
+
constr = dj_url[pos + len('://'):].strip()
|
|
250
|
+
else:
|
|
251
|
+
constr = dj_url
|
|
252
|
+
pos = constr.find("@")
|
|
253
|
+
if pos > -1:
|
|
254
|
+
loginstr = constr[:pos].strip()
|
|
255
|
+
constr = constr[pos + len("@"):].strip()
|
|
256
|
+
pos = loginstr.find(':')
|
|
257
|
+
if pos > -1:
|
|
258
|
+
self.user = loginstr[:pos].strip() or None
|
|
259
|
+
self.password = loginstr[pos + len(':'):].strip() or None
|
|
260
|
+
if encoded:
|
|
261
|
+
self.password = self.password.replace('%40', '@')
|
|
262
|
+
elif len(loginstr) > 0:
|
|
263
|
+
self.user = loginstr or None
|
|
264
|
+
else:
|
|
265
|
+
# since password is the most common replacement look for that specifically next if we didn't have an @
|
|
266
|
+
# NOTE: since password can contain / we will assume the only thing there is the password otherwise use @
|
|
267
|
+
# Ex: DjUrl(':newpassword/newdatabase') -> password=newpassword/newdatabase name=None
|
|
268
|
+
# DjUrl(':newpassword@/newdatabase') -> password=newpassword name=newdatabase
|
|
269
|
+
# DjUrl(':asfcasdf23%401:/!?--atencoded') -> password=asfcasdf23@1:/! name=None port=None
|
|
270
|
+
if constr.startswith(':'):
|
|
271
|
+
self.password = constr.strip(':')
|
|
272
|
+
constr = ''
|
|
273
|
+
if encoded:
|
|
274
|
+
self.password = self.password.replace('%40', '@')
|
|
275
|
+
# NOTE: constr now contains everything after @ - no engine, user, password
|
|
276
|
+
# NOTE: may have port but no db ex: @:8080
|
|
277
|
+
pos = constr.find('/')
|
|
278
|
+
if pos > -1:
|
|
279
|
+
hoststr = constr[:pos].strip()
|
|
280
|
+
self.name = constr[pos + len('/'):].strip() or None
|
|
281
|
+
else:
|
|
282
|
+
hoststr = constr
|
|
283
|
+
# all that is left is hoststr
|
|
284
|
+
pos = hoststr.find(':')
|
|
285
|
+
if pos > -1:
|
|
286
|
+
self.host = hoststr[:pos].strip() or None
|
|
287
|
+
self.port = hoststr[pos + len(':'):].strip() or None
|
|
288
|
+
if self.port:
|
|
289
|
+
self.port = to_int(self.port, default=None, none_to_default=False)
|
|
290
|
+
elif len(hoststr) > 0:
|
|
291
|
+
self.host = hoststr
|
|
292
|
+
|
|
293
|
+
def to_dict(self) -> dict:
|
|
294
|
+
dct = {}
|
|
295
|
+
for attr, value in vars(self).items():
|
|
296
|
+
dct[attr.upper()] = value
|
|
297
|
+
return dct
|
|
298
|
+
|
|
299
|
+
def __str__(self) -> str:
|
|
300
|
+
url = f"{self.engine or ''}://"
|
|
301
|
+
if self.user:
|
|
302
|
+
url += self.user
|
|
303
|
+
if self.password:
|
|
304
|
+
url += f":{to_mask(self.password)}"
|
|
305
|
+
url += f"@{self.host or ''}"
|
|
306
|
+
if self.port:
|
|
307
|
+
url += f":{self.port}"
|
|
308
|
+
if self.name:
|
|
309
|
+
url += f"/{self.name}"
|
|
310
|
+
return url
|
|
311
|
+
|
|
219
312
|
|
|
220
313
|
if __name__ == "__main__":
|
|
221
314
|
# test_uri = "http://localhost:8000/test1/?id=1&x=2"
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
ubercode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
ubercode/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
ubercode/utils/convert.py,sha256=YWIacPg3MaUTGSANZ2yzxanIkUCH2oyqOR6Wvtz91Fg,11371
|
|
4
|
-
ubercode/utils/cursor.py,sha256=zHqbmUavCF0l7ck_Tu4PfgSzRO762ULKHYsnqa3r0uY,1118
|
|
5
|
-
ubercode/utils/data.py,sha256=DStj9BAXPSPvQRZwfEQVYt19EoMavyNFt11U8ZnhagE,4364
|
|
6
|
-
ubercode/utils/dataframe.py,sha256=3AYoZGZB7wtN5brBDfRgnuIaEubhRrSc7qx8hi_vaaE,1616
|
|
7
|
-
ubercode/utils/environment.py,sha256=zSgemMspWx1mBPeI2CSStCSj_kjVm_rEVZaNRkEMsZM,13657
|
|
8
|
-
ubercode/utils/logging.py,sha256=s4yIJWHo9MVO4oSk5IF3z6XTk-FOuyEiKnPFkiza3h4,9204
|
|
9
|
-
ubercode/utils/urls.py,sha256=PaksDHVg_aSXEqcN4SlLgY-erqeMCNw8mYK9qt3q05I,9652
|
|
10
|
-
python_ubercode_utils-2.0.2.dist-info/LICENSE,sha256=lch0WEJaqJY3C5aCYSr0u6Gw0set96a2fp9ZWJRYuR8,1069
|
|
11
|
-
python_ubercode_utils-2.0.2.dist-info/METADATA,sha256=Oe6G8phzfGZ6SFVZdd7FDgsNGjjF999kF4e70dU4dEE,1225
|
|
12
|
-
python_ubercode_utils-2.0.2.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
|
|
13
|
-
python_ubercode_utils-2.0.2.dist-info/top_level.txt,sha256=5BojzbvNCpPkFXcVQVr7cfovhbYQYAlMKgiHSMgi7VU,9
|
|
14
|
-
python_ubercode_utils-2.0.2.dist-info/RECORD,,
|
|
File without changes
|
{python_ubercode_utils-2.0.2.dist-info → python_ubercode_utils-2.0.4.dist-info}/top_level.txt
RENAMED
|
File without changes
|