swak 0.0.3__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.
- swak-0.0.3/LICENSE +21 -0
- swak-0.0.3/MANIFEST.in +8 -0
- swak-0.0.3/PKG-INFO +48 -0
- swak-0.0.3/README.md +4 -0
- swak-0.0.3/pyproject.toml +79 -0
- swak-0.0.3/setup.cfg +4 -0
- swak-0.0.3/swak/__init__.py +0 -0
- swak-0.0.3/swak/cli/__init__.py +14 -0
- swak-0.0.3/swak/cli/argparser.py +159 -0
- swak-0.0.3/swak/cli/envparser.py +67 -0
- swak-0.0.3/swak/cli/exceptions.py +6 -0
- swak-0.0.3/swak/cli/importer.py +68 -0
- swak-0.0.3/swak/cloud/__init__.py +14 -0
- swak-0.0.3/swak/cloud/gcp/__init__.py +31 -0
- swak-0.0.3/swak/cloud/gcp/bucket.py +161 -0
- swak-0.0.3/swak/cloud/gcp/dataset.py +231 -0
- swak-0.0.3/swak/cloud/gcp/df2gbq.py +122 -0
- swak-0.0.3/swak/cloud/gcp/exceptions.py +6 -0
- swak-0.0.3/swak/cloud/gcp/gcs2df.py +121 -0
- swak-0.0.3/swak/cloud/gcp/gcs2local.py +170 -0
- swak-0.0.3/swak/cloud/gcp/query.py +86 -0
- swak-0.0.3/swak/cloud/gcp/query2df.py +54 -0
- swak-0.0.3/swak/cloud/gcp/query2gcs.py +171 -0
- swak-0.0.3/swak/dictionary/__init__.py +13 -0
- swak-0.0.3/swak/dictionary/valuesgetter.py +139 -0
- swak-0.0.3/swak/funcflow/__init__.py +51 -0
- swak-0.0.3/swak/funcflow/concurrent/__init__.py +16 -0
- swak-0.0.3/swak/funcflow/concurrent/processfork.py +189 -0
- swak-0.0.3/swak/funcflow/concurrent/processmap.py +147 -0
- swak-0.0.3/swak/funcflow/concurrent/threadfork.py +187 -0
- swak-0.0.3/swak/funcflow/concurrent/threadmap.py +137 -0
- swak-0.0.3/swak/funcflow/curry.py +57 -0
- swak-0.0.3/swak/funcflow/exceptions.py +79 -0
- swak-0.0.3/swak/funcflow/filter.py +86 -0
- swak-0.0.3/swak/funcflow/fork.py +141 -0
- swak-0.0.3/swak/funcflow/loggers/__init__.py +16 -0
- swak-0.0.3/swak/funcflow/loggers/stdout.py +231 -0
- swak-0.0.3/swak/funcflow/map.py +87 -0
- swak-0.0.3/swak/funcflow/misc.py +67 -0
- swak-0.0.3/swak/funcflow/partial.py +56 -0
- swak-0.0.3/swak/funcflow/pipe.py +139 -0
- swak-0.0.3/swak/funcflow/reduce.py +60 -0
- swak-0.0.3/swak/funcflow/route.py +207 -0
- swak-0.0.3/swak/funcflow/safe.py +62 -0
- swak-0.0.3/swak/funcflow/split.py +87 -0
- swak-0.0.3/swak/funcflow/sum.py +50 -0
- swak-0.0.3/swak/jsonobject/__init__.py +9 -0
- swak-0.0.3/swak/jsonobject/exceptions.py +18 -0
- swak-0.0.3/swak/jsonobject/fields/__init__.py +13 -0
- swak-0.0.3/swak/jsonobject/fields/custom.py +22 -0
- swak-0.0.3/swak/jsonobject/fields/flexidate.py +80 -0
- swak-0.0.3/swak/jsonobject/fields/flexitime.py +71 -0
- swak-0.0.3/swak/jsonobject/fields/maybe.py +43 -0
- swak-0.0.3/swak/jsonobject/jsonobject.py +523 -0
- swak-0.0.3/swak/jsonobject/jsonobjects.py +246 -0
- swak-0.0.3/swak/misc/__init__.py +12 -0
- swak-0.0.3/swak/misc/loggers.py +187 -0
- swak-0.0.3/swak/misc/repr.py +159 -0
- swak-0.0.3/swak/pd/__init__.py +25 -0
- swak-0.0.3/swak/pd/frame.py +248 -0
- swak-0.0.3/swak/pd/read.py +44 -0
- swak-0.0.3/swak/pt/__init__.py +29 -0
- swak-0.0.3/swak/pt/blocks.py +406 -0
- swak-0.0.3/swak/pt/create.py +179 -0
- swak-0.0.3/swak/pt/dists.py +88 -0
- swak-0.0.3/swak/pt/embed/__init__.py +25 -0
- swak-0.0.3/swak/pt/embed/activated.py +109 -0
- swak-0.0.3/swak/pt/embed/categorical.py +145 -0
- swak-0.0.3/swak/pt/embed/feature.py +114 -0
- swak-0.0.3/swak/pt/embed/gated.py +109 -0
- swak-0.0.3/swak/pt/embed/gated_residual.py +162 -0
- swak-0.0.3/swak/pt/embed/numerical.py +144 -0
- swak-0.0.3/swak/pt/exceptions.py +10 -0
- swak-0.0.3/swak/pt/io.py +219 -0
- swak-0.0.3/swak/pt/losses.py +503 -0
- swak-0.0.3/swak/pt/misc.py +312 -0
- swak-0.0.3/swak/pt/mix/__init__.py +21 -0
- swak-0.0.3/swak/pt/mix/activated.py +116 -0
- swak-0.0.3/swak/pt/mix/gated.py +117 -0
- swak-0.0.3/swak/pt/mix/gated_residual.py +162 -0
- swak-0.0.3/swak/pt/mix/weighted/__init__.py +21 -0
- swak-0.0.3/swak/pt/mix/weighted/activated.py +141 -0
- swak-0.0.3/swak/pt/mix/weighted/constant.py +86 -0
- swak-0.0.3/swak/pt/mix/weighted/gated.py +146 -0
- swak-0.0.3/swak/pt/mix/weighted/gated_residual.py +190 -0
- swak-0.0.3/swak/pt/mix/weighted/variable.py +91 -0
- swak-0.0.3/swak/pt/train/__init__.py +33 -0
- swak-0.0.3/swak/pt/train/callbacks.py +151 -0
- swak-0.0.3/swak/pt/train/checkpoints.py +233 -0
- swak-0.0.3/swak/pt/train/data.py +81 -0
- swak-0.0.3/swak/pt/train/schedulers.py +51 -0
- swak-0.0.3/swak/pt/train/trainer.py +310 -0
- swak-0.0.3/swak/pt/types.py +38 -0
- swak-0.0.3/swak/text/__init__.py +23 -0
- swak-0.0.3/swak/text/interpolate.py +171 -0
- swak-0.0.3/swak/text/misc.py +14 -0
- swak-0.0.3/swak/text/parse.py +37 -0
- swak-0.0.3/swak/text/read.py +171 -0
- swak-0.0.3/swak/text/resource.py +81 -0
- swak-0.0.3/swak.egg-info/SOURCES.txt +97 -0
swak-0.0.3/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Georg Heimel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
swak-0.0.3/MANIFEST.in
ADDED
swak-0.0.3/PKG-INFO
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: swak
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: Swiss army knife for functional data-science projects.
|
|
5
|
+
Author-email: yedivanseven <yedivanseven@outlook.de>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2024 Georg Heimel
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Keywords: functional,tools,utilities,helpers
|
|
29
|
+
Classifier: Development Status :: 4 - Beta
|
|
30
|
+
Classifier: Intended Audience :: Developers
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
32
|
+
Classifier: Topic :: Utilities
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Requires-Python: >=3.12
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
License-File: LICENSE
|
|
37
|
+
Requires-Dist: pandas>=2.2
|
|
38
|
+
Requires-Dist: pyarrow>=17.0
|
|
39
|
+
Requires-Dist: pyyaml>=6.0
|
|
40
|
+
Provides-Extra: cloud
|
|
41
|
+
Requires-Dist: google-cloud-bigquery>=3.25; extra == "cloud"
|
|
42
|
+
Requires-Dist: google-cloud-storage>=2.18; extra == "cloud"
|
|
43
|
+
Requires-Dist: pandas-gbq>=0.23; extra == "cloud"
|
|
44
|
+
|
|
45
|
+
# swak
|
|
46
|
+
_Swiss army knife for functional data-science projects._
|
|
47
|
+
|
|
48
|
+
Work in progress ...
|
swak-0.0.3/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
"setuptools>=70",
|
|
4
|
+
"setuptools_scm>=8.1",
|
|
5
|
+
]
|
|
6
|
+
build-backend = "setuptools.build_meta"
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "swak"
|
|
10
|
+
dynamic = ["version"]
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"pandas>=2.2",
|
|
14
|
+
"pyarrow>=17.0",
|
|
15
|
+
"pyyaml>=6.0",
|
|
16
|
+
]
|
|
17
|
+
authors = [{name = "yedivanseven", email = "yedivanseven@outlook.de"}]
|
|
18
|
+
description = "Swiss army knife for functional data-science projects."
|
|
19
|
+
readme = "README.md"
|
|
20
|
+
license = {file = "LICENSE"}
|
|
21
|
+
keywords = [
|
|
22
|
+
"functional",
|
|
23
|
+
"tools",
|
|
24
|
+
"utilities",
|
|
25
|
+
"helpers",
|
|
26
|
+
]
|
|
27
|
+
classifiers = [
|
|
28
|
+
"Development Status :: 4 - Beta",
|
|
29
|
+
"Intended Audience :: Developers",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Topic :: Utilities",
|
|
32
|
+
"License :: OSI Approved :: MIT License",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
cloud = [
|
|
37
|
+
"google-cloud-bigquery>=3.25",
|
|
38
|
+
"google-cloud-storage>=2.18",
|
|
39
|
+
"pandas-gbq>=0.23"
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[tool.setuptools_scm]
|
|
43
|
+
version_file = "version.env"
|
|
44
|
+
version_file_template = "SETUPTOOLS_SCM_PRETEND_VERSION={version}"
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
line-length = 79
|
|
48
|
+
|
|
49
|
+
[tool.ruff.format]
|
|
50
|
+
quote-style = "single"
|
|
51
|
+
|
|
52
|
+
[tool.ruff.lint]
|
|
53
|
+
ignore = ["PLR"]
|
|
54
|
+
select = [
|
|
55
|
+
"F",
|
|
56
|
+
"E",
|
|
57
|
+
"W",
|
|
58
|
+
"N",
|
|
59
|
+
"T",
|
|
60
|
+
"UP",
|
|
61
|
+
"YTT",
|
|
62
|
+
"ASYNC",
|
|
63
|
+
"C4",
|
|
64
|
+
"T100",
|
|
65
|
+
"EXE",
|
|
66
|
+
"ISC",
|
|
67
|
+
"ICN",
|
|
68
|
+
"PIE",
|
|
69
|
+
"PYI",
|
|
70
|
+
"RSE",
|
|
71
|
+
"SLOT",
|
|
72
|
+
"SIM",
|
|
73
|
+
"INT",
|
|
74
|
+
"PTH",
|
|
75
|
+
"PL",
|
|
76
|
+
"NPY",
|
|
77
|
+
"PERF",
|
|
78
|
+
"FURB"
|
|
79
|
+
]
|
swak-0.0.3/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Tools to assist in consolidating sources of project configurations."""
|
|
2
|
+
|
|
3
|
+
from .importer import Importer
|
|
4
|
+
from .envparser import EnvParser
|
|
5
|
+
from .argparser import ArgParser, USAGE, DESCRIPTION, EPILOG
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'Importer',
|
|
9
|
+
'EnvParser',
|
|
10
|
+
'ArgParser',
|
|
11
|
+
'USAGE',
|
|
12
|
+
'DESCRIPTION',
|
|
13
|
+
'EPILOG'
|
|
14
|
+
]
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import json
|
|
3
|
+
from json import JSONDecodeError
|
|
4
|
+
from ast import literal_eval
|
|
5
|
+
from argparse import ArgumentParser, RawTextHelpFormatter
|
|
6
|
+
from typing import Any
|
|
7
|
+
from itertools import takewhile, dropwhile, chain
|
|
8
|
+
from .exceptions import ArgParseError
|
|
9
|
+
|
|
10
|
+
type Parsed = tuple[list[str], dict[str, Any]]
|
|
11
|
+
|
|
12
|
+
USAGE = '%(prog)s [action(s)] [-h]'
|
|
13
|
+
|
|
14
|
+
DESCRIPTION = 'Refer to the README.md for the available actions!'
|
|
15
|
+
|
|
16
|
+
EPILOG = """
|
|
17
|
+
Additionally, all fields of the program's config can be set via
|
|
18
|
+
long-format options. Nested fields can be set by dot-separating
|
|
19
|
+
levels, e.g., "--root.level1.level2 value"
|
|
20
|
+
|
|
21
|
+
{!r}
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ArgParser:
|
|
26
|
+
"""Parse the command line for actions and any long-format options.
|
|
27
|
+
|
|
28
|
+
Using this command-line argument parser alleviates the need for
|
|
29
|
+
defining any groups or options beforehand. Arguments immediately
|
|
30
|
+
following the program call are interpreted as actions to perform as long
|
|
31
|
+
as they do not start with a hyphen. Starting with the first argument
|
|
32
|
+
that starts with a hyphen, command-line arguments will be interpreted as
|
|
33
|
+
``--key value`` pairs and this long format is the only one allowed.
|
|
34
|
+
Abbreviated options (``-k value``) right after actions (and before any
|
|
35
|
+
long-format options) are ignored.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
default_action: str, optional
|
|
40
|
+
Default action to return if none is found in the command-line
|
|
41
|
+
arguments. Defaults to no action.
|
|
42
|
+
usage: str, optional
|
|
43
|
+
Program usage message.
|
|
44
|
+
description: str, optional
|
|
45
|
+
Program description.
|
|
46
|
+
epilog: str, optional
|
|
47
|
+
Text displayed after the help on command-line options.
|
|
48
|
+
fmt_cls: type, optional
|
|
49
|
+
Option passed on to the underlying ``argparse.ArgumentParser``.
|
|
50
|
+
Defaults to ``argparse.RawTextHelpFormatter``
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
default_action: str | None = None,
|
|
57
|
+
usage: str = USAGE,
|
|
58
|
+
description: str = DESCRIPTION,
|
|
59
|
+
epilog: str = EPILOG,
|
|
60
|
+
fmt_cls: type = RawTextHelpFormatter
|
|
61
|
+
) -> None:
|
|
62
|
+
self.default_action = default_action
|
|
63
|
+
self.usage = usage
|
|
64
|
+
self.description = description
|
|
65
|
+
self.epilog = epilog
|
|
66
|
+
self.fmt_cls = fmt_cls
|
|
67
|
+
self.__parse = ArgumentParser(
|
|
68
|
+
usage=usage,
|
|
69
|
+
description=description,
|
|
70
|
+
epilog=epilog,
|
|
71
|
+
formatter_class=fmt_cls
|
|
72
|
+
).parse_known_args
|
|
73
|
+
|
|
74
|
+
def __repr__(self) -> str:
|
|
75
|
+
cls = self.__class__.__name__
|
|
76
|
+
return f'{cls}({self.default_action}, ...)'
|
|
77
|
+
|
|
78
|
+
def __call__(self, args: list[str] | None = None) -> Parsed:
|
|
79
|
+
"""Parse the command-line arguments into actions and options.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
args: list of str, optional
|
|
84
|
+
The command-line arguments to parse. Mainly a debugging feature.
|
|
85
|
+
If none is given, ``sys.argv[1:]`` will be parsed.
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
actions: list of str
|
|
90
|
+
A list with the actions (as strings) to perform. If none are
|
|
91
|
+
found on the command line and no `default_action` is specified,
|
|
92
|
+
that list will be empty.
|
|
93
|
+
options: dict
|
|
94
|
+
Dictionary with keys and values parsed from long-format
|
|
95
|
+
command line arguments.
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
args = sys.argv[1:] if args is None else args
|
|
99
|
+
_, unknowns = self.__parse(args)
|
|
100
|
+
actions, args = self.__split(unknowns)
|
|
101
|
+
actions = self.__valid(actions)
|
|
102
|
+
args = self.__compatible(args)
|
|
103
|
+
options = self.__mapped(args)
|
|
104
|
+
return actions, options
|
|
105
|
+
|
|
106
|
+
def __split(self, args: list[str]) -> tuple[list[str], list[str]]:
|
|
107
|
+
"""Split command-line options into actions and config settings."""
|
|
108
|
+
# Everything before the first option (starting with "-") are actions.
|
|
109
|
+
actions = tuple(takewhile(lambda arg: not arg.startswith('-'), args))
|
|
110
|
+
# If there are none, fall back onto the default action (if specified).
|
|
111
|
+
if not actions and self.default_action is not None:
|
|
112
|
+
actions = self.default_action,
|
|
113
|
+
# Either way, replace dashes with underscores
|
|
114
|
+
actions = (action.lower().replace('-', '_') for action in actions)
|
|
115
|
+
|
|
116
|
+
# Everything after the action(s) are options (starting with "--").
|
|
117
|
+
args = dropwhile(lambda arg: not arg.startswith('--'), args)
|
|
118
|
+
# In case options are given as "--key=value", we split.
|
|
119
|
+
args = chain.from_iterable(arg.split('=') for arg in args)
|
|
120
|
+
return list(actions), list(args)
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def __valid(actions: list[str]) -> list[str]:
|
|
124
|
+
"""Raise if any action string is not a valid python identifier."""
|
|
125
|
+
for action in actions:
|
|
126
|
+
if not action.isidentifier():
|
|
127
|
+
msg = 'Actions must be valid identifiers, unlike "{}"!'
|
|
128
|
+
raise ArgParseError(msg.format(action))
|
|
129
|
+
return actions
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def __compatible(args: list[str]) -> list[str]:
|
|
133
|
+
"""Raise if a command-line option is not in long format with value."""
|
|
134
|
+
long_form = all(arg.startswith('--') for arg in args[::2])
|
|
135
|
+
alternating = all(not arg.startswith('-') for arg in args[1::2])
|
|
136
|
+
even_number = len(args) % 2 == 0
|
|
137
|
+
if not (long_form and alternating and even_number):
|
|
138
|
+
msg = ('Command-line arguments must be passed in long format (i.e.'
|
|
139
|
+
', as "--key value"), and a value must always be present!')
|
|
140
|
+
raise ArgParseError(msg)
|
|
141
|
+
return args
|
|
142
|
+
|
|
143
|
+
def __mapped(self, args: list[str]) -> dict[str, Any]:
|
|
144
|
+
"""Create dictionary wih config keys and (string) values from args."""
|
|
145
|
+
zipped = list(zip(args[::2], args[1::2]))
|
|
146
|
+
# Drop the first two characters (always "--") and replace "-" with "_".
|
|
147
|
+
return {k[2:].replace('-', '_'): self.__parsed(v) for k, v in zipped}
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def __parsed(value: str) -> Any:
|
|
151
|
+
"""Try to parse (string) command-line options into python objects."""
|
|
152
|
+
try:
|
|
153
|
+
parsed = json.loads(value)
|
|
154
|
+
except (TypeError, JSONDecodeError):
|
|
155
|
+
try:
|
|
156
|
+
parsed = literal_eval(value)
|
|
157
|
+
except (TypeError, ValueError, SyntaxError):
|
|
158
|
+
parsed = value
|
|
159
|
+
return parsed
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
from json import JSONDecodeError
|
|
4
|
+
from ast import literal_eval
|
|
5
|
+
from typing import Any
|
|
6
|
+
from ..misc import ArgRepr
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EnvParser(ArgRepr):
|
|
10
|
+
"""Parse OS environment variables, preferring prefixed over pure versions.
|
|
11
|
+
|
|
12
|
+
Sometimes, environment variables desired for individual use are already
|
|
13
|
+
taken by the operating system or some other system component. In these
|
|
14
|
+
cases, one can resort to prefixing these to avoid conflicts. The present
|
|
15
|
+
class is instantiated with that prefix and will resolve conflicts when
|
|
16
|
+
objects are called, returning the OS environment as a dictionary with
|
|
17
|
+
the values of all variables parsed into python literals.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
prefix: str, optional
|
|
22
|
+
Prefix of environment variables that would otherwise be shadowed
|
|
23
|
+
by existing ones. Defaults to empty string.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, prefix: str = '') -> None:
|
|
28
|
+
super().__init__(prefix)
|
|
29
|
+
self.prefix = prefix
|
|
30
|
+
|
|
31
|
+
def __call__(self, env: dict[str, str] | None = None) -> dict[str, Any]:
|
|
32
|
+
"""Parse the OS environment, resolving potentially prefixed variables.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
env: dict, optional
|
|
37
|
+
Dictionary to be parsed and resolved. Defaults to ``os.environ``.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
dict
|
|
42
|
+
Environment with prefixed keys removed and the values of their
|
|
43
|
+
non-prefixed counterparts updated accordingly.
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
env = os.environ if env is None else env
|
|
47
|
+
prefixed = {}
|
|
48
|
+
original = {}
|
|
49
|
+
for key in env:
|
|
50
|
+
if key.startswith(self.prefix):
|
|
51
|
+
prefixed[key.removeprefix(self.prefix)] = env[key]
|
|
52
|
+
else:
|
|
53
|
+
original[key] = env[key]
|
|
54
|
+
merged = original | prefixed
|
|
55
|
+
return {key: self.__parsed(value) for key, value in merged.items()}
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def __parsed(value: str) -> Any:
|
|
59
|
+
"""Try to parse (string) environment variables into python objects."""
|
|
60
|
+
try:
|
|
61
|
+
parsed = json.loads(value)
|
|
62
|
+
except (TypeError, JSONDecodeError):
|
|
63
|
+
try:
|
|
64
|
+
parsed = literal_eval(value)
|
|
65
|
+
except (TypeError, ValueError, SyntaxError):
|
|
66
|
+
parsed = value
|
|
67
|
+
return parsed
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from importlib import import_module
|
|
2
|
+
from ..misc import ArgRepr
|
|
3
|
+
from .exceptions import ImporterError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Importer(ArgRepr):
|
|
7
|
+
"""Programmatically import objects from a module under a top-level package.
|
|
8
|
+
|
|
9
|
+
For ease of use and clarity in API, relative imports are not supported.
|
|
10
|
+
Objects are instantiated with where to import from and called with what
|
|
11
|
+
to import.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
package: str
|
|
16
|
+
Name of the top-level package to import from. Must not start with dots
|
|
17
|
+
but can contain any number of dots to indicate sub-packages.
|
|
18
|
+
module: str, optional
|
|
19
|
+
The specific module to import objects from. May contain dots to
|
|
20
|
+
indicate that it is located further down within some sub-package.
|
|
21
|
+
Defaults to "steps".
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, package: str, module: str = 'steps') -> None:
|
|
26
|
+
self.package = package.strip(' ./')
|
|
27
|
+
self.module = module.strip(' ./')
|
|
28
|
+
super().__init__(self.package, self.module)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def path(self) -> str:
|
|
32
|
+
"""Full path specification of (sub-)package and module concatenated."""
|
|
33
|
+
return '.'.join([self.package, self.module])
|
|
34
|
+
|
|
35
|
+
def __call__(self, *names: str) -> list:
|
|
36
|
+
"""Import any number of objects from the specified package.module.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
*names: str
|
|
41
|
+
Name(s) of object(s) to import from ``package.module``.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
list
|
|
46
|
+
Imported objects.
|
|
47
|
+
|
|
48
|
+
Raises
|
|
49
|
+
------
|
|
50
|
+
ImporterError
|
|
51
|
+
When the ``package.module`` is mis-specified, can't be found, or
|
|
52
|
+
when the specified object(s) can't be found in it.
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
location = import_module(self.path)
|
|
57
|
+
except (TypeError, ModuleNotFoundError) as error:
|
|
58
|
+
msg = 'Could not import module "{}"!'
|
|
59
|
+
raise ImporterError(msg.format(self.path)) from error
|
|
60
|
+
imports = []
|
|
61
|
+
for name in names:
|
|
62
|
+
try:
|
|
63
|
+
imported = getattr(location, name)
|
|
64
|
+
except AttributeError as err:
|
|
65
|
+
msg = 'Could not import "{}" from module "{}"!'
|
|
66
|
+
raise ImporterError(msg.format(name, self.path)) from err
|
|
67
|
+
imports.append(imported)
|
|
68
|
+
return imports
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Tools to interact with hosted cloud services.
|
|
2
|
+
|
|
3
|
+
Current only supports elements of the Google Cloud Project (GCP) but, in
|
|
4
|
+
the future, Amazon Web Services (AWS) might get implemented as well.
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from importlib.util import find_spec
|
|
9
|
+
|
|
10
|
+
required = 'google_cloud_bigquery', 'google_cloud_storage', 'pandas_gbq'
|
|
11
|
+
|
|
12
|
+
if any(find_spec(package) for package in required) is None:
|
|
13
|
+
msg = 'Install {} with the [cloud] extra to unlock this subpackage!'
|
|
14
|
+
raise ImportError(msg.format(__package__.split('.')[0]))
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Tools to interact with elements of the Google Cloud Project (GCP).
|
|
2
|
+
|
|
3
|
+
Specifically, data scientists tend to interact mostly with Google's BigQuery
|
|
4
|
+
(BQ) data-warehouse solution and the Google Cloud Storage (GCS).
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .dataset import Collation, Rounding, Billing, GbqDataset
|
|
9
|
+
from .bucket import Storage, GcsBucket
|
|
10
|
+
from .query import GbqQuery
|
|
11
|
+
from .query2gcs import GbqQuery2GcsParquet
|
|
12
|
+
from .gcs2local import GcsDir2LocalDir
|
|
13
|
+
from .gcs2df import GcsParquet2DataFrame
|
|
14
|
+
from .query2df import GbqQuery2DataFrame
|
|
15
|
+
from .df2gbq import IfExists, DataFrame2Gbq
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
'Collation',
|
|
19
|
+
'Rounding',
|
|
20
|
+
'Billing',
|
|
21
|
+
'GbqDataset',
|
|
22
|
+
'Storage',
|
|
23
|
+
'GcsBucket',
|
|
24
|
+
'GbqQuery',
|
|
25
|
+
'GbqQuery2GcsParquet',
|
|
26
|
+
'GcsDir2LocalDir',
|
|
27
|
+
'GcsParquet2DataFrame',
|
|
28
|
+
'GbqQuery2DataFrame',
|
|
29
|
+
'IfExists',
|
|
30
|
+
'DataFrame2Gbq'
|
|
31
|
+
]
|