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.
Files changed (100) hide show
  1. swak-0.0.3/LICENSE +21 -0
  2. swak-0.0.3/MANIFEST.in +8 -0
  3. swak-0.0.3/PKG-INFO +48 -0
  4. swak-0.0.3/README.md +4 -0
  5. swak-0.0.3/pyproject.toml +79 -0
  6. swak-0.0.3/setup.cfg +4 -0
  7. swak-0.0.3/swak/__init__.py +0 -0
  8. swak-0.0.3/swak/cli/__init__.py +14 -0
  9. swak-0.0.3/swak/cli/argparser.py +159 -0
  10. swak-0.0.3/swak/cli/envparser.py +67 -0
  11. swak-0.0.3/swak/cli/exceptions.py +6 -0
  12. swak-0.0.3/swak/cli/importer.py +68 -0
  13. swak-0.0.3/swak/cloud/__init__.py +14 -0
  14. swak-0.0.3/swak/cloud/gcp/__init__.py +31 -0
  15. swak-0.0.3/swak/cloud/gcp/bucket.py +161 -0
  16. swak-0.0.3/swak/cloud/gcp/dataset.py +231 -0
  17. swak-0.0.3/swak/cloud/gcp/df2gbq.py +122 -0
  18. swak-0.0.3/swak/cloud/gcp/exceptions.py +6 -0
  19. swak-0.0.3/swak/cloud/gcp/gcs2df.py +121 -0
  20. swak-0.0.3/swak/cloud/gcp/gcs2local.py +170 -0
  21. swak-0.0.3/swak/cloud/gcp/query.py +86 -0
  22. swak-0.0.3/swak/cloud/gcp/query2df.py +54 -0
  23. swak-0.0.3/swak/cloud/gcp/query2gcs.py +171 -0
  24. swak-0.0.3/swak/dictionary/__init__.py +13 -0
  25. swak-0.0.3/swak/dictionary/valuesgetter.py +139 -0
  26. swak-0.0.3/swak/funcflow/__init__.py +51 -0
  27. swak-0.0.3/swak/funcflow/concurrent/__init__.py +16 -0
  28. swak-0.0.3/swak/funcflow/concurrent/processfork.py +189 -0
  29. swak-0.0.3/swak/funcflow/concurrent/processmap.py +147 -0
  30. swak-0.0.3/swak/funcflow/concurrent/threadfork.py +187 -0
  31. swak-0.0.3/swak/funcflow/concurrent/threadmap.py +137 -0
  32. swak-0.0.3/swak/funcflow/curry.py +57 -0
  33. swak-0.0.3/swak/funcflow/exceptions.py +79 -0
  34. swak-0.0.3/swak/funcflow/filter.py +86 -0
  35. swak-0.0.3/swak/funcflow/fork.py +141 -0
  36. swak-0.0.3/swak/funcflow/loggers/__init__.py +16 -0
  37. swak-0.0.3/swak/funcflow/loggers/stdout.py +231 -0
  38. swak-0.0.3/swak/funcflow/map.py +87 -0
  39. swak-0.0.3/swak/funcflow/misc.py +67 -0
  40. swak-0.0.3/swak/funcflow/partial.py +56 -0
  41. swak-0.0.3/swak/funcflow/pipe.py +139 -0
  42. swak-0.0.3/swak/funcflow/reduce.py +60 -0
  43. swak-0.0.3/swak/funcflow/route.py +207 -0
  44. swak-0.0.3/swak/funcflow/safe.py +62 -0
  45. swak-0.0.3/swak/funcflow/split.py +87 -0
  46. swak-0.0.3/swak/funcflow/sum.py +50 -0
  47. swak-0.0.3/swak/jsonobject/__init__.py +9 -0
  48. swak-0.0.3/swak/jsonobject/exceptions.py +18 -0
  49. swak-0.0.3/swak/jsonobject/fields/__init__.py +13 -0
  50. swak-0.0.3/swak/jsonobject/fields/custom.py +22 -0
  51. swak-0.0.3/swak/jsonobject/fields/flexidate.py +80 -0
  52. swak-0.0.3/swak/jsonobject/fields/flexitime.py +71 -0
  53. swak-0.0.3/swak/jsonobject/fields/maybe.py +43 -0
  54. swak-0.0.3/swak/jsonobject/jsonobject.py +523 -0
  55. swak-0.0.3/swak/jsonobject/jsonobjects.py +246 -0
  56. swak-0.0.3/swak/misc/__init__.py +12 -0
  57. swak-0.0.3/swak/misc/loggers.py +187 -0
  58. swak-0.0.3/swak/misc/repr.py +159 -0
  59. swak-0.0.3/swak/pd/__init__.py +25 -0
  60. swak-0.0.3/swak/pd/frame.py +248 -0
  61. swak-0.0.3/swak/pd/read.py +44 -0
  62. swak-0.0.3/swak/pt/__init__.py +29 -0
  63. swak-0.0.3/swak/pt/blocks.py +406 -0
  64. swak-0.0.3/swak/pt/create.py +179 -0
  65. swak-0.0.3/swak/pt/dists.py +88 -0
  66. swak-0.0.3/swak/pt/embed/__init__.py +25 -0
  67. swak-0.0.3/swak/pt/embed/activated.py +109 -0
  68. swak-0.0.3/swak/pt/embed/categorical.py +145 -0
  69. swak-0.0.3/swak/pt/embed/feature.py +114 -0
  70. swak-0.0.3/swak/pt/embed/gated.py +109 -0
  71. swak-0.0.3/swak/pt/embed/gated_residual.py +162 -0
  72. swak-0.0.3/swak/pt/embed/numerical.py +144 -0
  73. swak-0.0.3/swak/pt/exceptions.py +10 -0
  74. swak-0.0.3/swak/pt/io.py +219 -0
  75. swak-0.0.3/swak/pt/losses.py +503 -0
  76. swak-0.0.3/swak/pt/misc.py +312 -0
  77. swak-0.0.3/swak/pt/mix/__init__.py +21 -0
  78. swak-0.0.3/swak/pt/mix/activated.py +116 -0
  79. swak-0.0.3/swak/pt/mix/gated.py +117 -0
  80. swak-0.0.3/swak/pt/mix/gated_residual.py +162 -0
  81. swak-0.0.3/swak/pt/mix/weighted/__init__.py +21 -0
  82. swak-0.0.3/swak/pt/mix/weighted/activated.py +141 -0
  83. swak-0.0.3/swak/pt/mix/weighted/constant.py +86 -0
  84. swak-0.0.3/swak/pt/mix/weighted/gated.py +146 -0
  85. swak-0.0.3/swak/pt/mix/weighted/gated_residual.py +190 -0
  86. swak-0.0.3/swak/pt/mix/weighted/variable.py +91 -0
  87. swak-0.0.3/swak/pt/train/__init__.py +33 -0
  88. swak-0.0.3/swak/pt/train/callbacks.py +151 -0
  89. swak-0.0.3/swak/pt/train/checkpoints.py +233 -0
  90. swak-0.0.3/swak/pt/train/data.py +81 -0
  91. swak-0.0.3/swak/pt/train/schedulers.py +51 -0
  92. swak-0.0.3/swak/pt/train/trainer.py +310 -0
  93. swak-0.0.3/swak/pt/types.py +38 -0
  94. swak-0.0.3/swak/text/__init__.py +23 -0
  95. swak-0.0.3/swak/text/interpolate.py +171 -0
  96. swak-0.0.3/swak/text/misc.py +14 -0
  97. swak-0.0.3/swak/text/parse.py +37 -0
  98. swak-0.0.3/swak/text/read.py +171 -0
  99. swak-0.0.3/swak/text/resource.py +81 -0
  100. 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
@@ -0,0 +1,8 @@
1
+ prune tests
2
+ prune Notebooks
3
+ prune docs
4
+ prune site
5
+ prune .ruff_cache
6
+ prune *.egg-info
7
+ prune .github
8
+ exclude .gitignore .python_version version.env Pipfile Pipfile.lock
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,4 @@
1
+ # swak
2
+ _Swiss army knife for functional data-science projects._
3
+
4
+ Work in progress ...
@@ -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
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
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,6 @@
1
+ class ArgParseError(Exception):
2
+ pass
3
+
4
+
5
+ class ImporterError(Exception):
6
+ pass
@@ -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
+ ]