iker-python-common 1.0.6__tar.gz → 1.0.8__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 (64) hide show
  1. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/PKG-INFO +1 -1
  2. iker_python_common-1.0.8/src/iker/common/utils/argutils.py +76 -0
  3. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/dbutils.py +17 -13
  4. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/sequtils.py +1 -1
  5. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/strutils.py +1 -1
  6. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker_python_common.egg-info/PKG-INFO +1 -1
  7. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker_python_common.egg-info/SOURCES.txt +2 -0
  8. iker_python_common-1.0.8/test/iker_tests/common/utils/argutils_test.py +158 -0
  9. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/dbutils_test.py +45 -45
  10. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/strutils_test.py +13 -6
  11. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/.editorconfig +0 -0
  12. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/.github/workflows/pr.yml +0 -0
  13. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/.github/workflows/push.yml +0 -0
  14. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/.gitignore +0 -0
  15. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/MANIFEST.in +0 -0
  16. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/README.md +0 -0
  17. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/VERSION +0 -0
  18. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/pyproject.toml +0 -0
  19. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/resources/unittest/shutils/dir.baz/file.bar.baz +0 -0
  20. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/resources/unittest/shutils/dir.baz/file.foo.bar +0 -0
  21. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/resources/unittest/shutils/dir.baz/file.foo.baz +0 -0
  22. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/resources/unittest/shutils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  23. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  24. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  25. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  26. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/resources/unittest/shutils/dir.foo/file.bar +0 -0
  27. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/resources/unittest/shutils/dir.foo/file.baz +0 -0
  28. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/resources/unittest/shutils/dir.foo/file.foo +0 -0
  29. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/setup.cfg +0 -0
  30. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/setup.py +0 -0
  31. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/__init__.py +0 -0
  32. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/__init__.py +0 -0
  33. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/config.py +0 -0
  34. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/dockerutils.py +0 -0
  35. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/dtutils.py +0 -0
  36. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/funcutils.py +0 -0
  37. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/logger.py +0 -0
  38. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/numutils.py +0 -0
  39. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/randutils.py +0 -0
  40. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/retry.py +0 -0
  41. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/s3utils.py +0 -0
  42. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/shutils.py +0 -0
  43. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/span.py +0 -0
  44. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker/common/utils/testutils.py +0 -0
  45. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker_python_common.egg-info/dependency_links.txt +0 -0
  46. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker_python_common.egg-info/not-zip-safe +0 -0
  47. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker_python_common.egg-info/requires.txt +0 -0
  48. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/src/iker_python_common.egg-info/top_level.txt +0 -0
  49. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/__init__.py +0 -0
  50. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/config_test.py +0 -0
  51. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/dockerutils_test.py +0 -0
  52. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/dtutils_test.py +0 -0
  53. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/funcutils_test.py +0 -0
  54. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/logger_test.py +0 -0
  55. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/numutils_test.py +0 -0
  56. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/randutils_test.py +0 -0
  57. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/retry_test.py +0 -0
  58. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/s3utils_test.py +0 -0
  59. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/sequtils_test.py +0 -0
  60. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/shutils_test.py +0 -0
  61. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/span_test.py +0 -0
  62. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/common/utils/testutils_test.py +0 -0
  63. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/docker_fixtures.py +0 -0
  64. {iker_python_common-1.0.6 → iker_python_common-1.0.8}/test/iker_tests/iker_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iker-python-common
3
- Version: 1.0.6
3
+ Version: 1.0.8
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.11
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -0,0 +1,76 @@
1
+ import argparse
2
+
3
+ __all__ = [
4
+ "ParserTreeNode",
5
+ "ParserTree",
6
+ ]
7
+
8
+
9
+ class ParserTreeNode(object):
10
+ def __init__(self, command: str, parser: argparse.ArgumentParser):
11
+ self.command = command
12
+ self.parser = parser
13
+ self.subparsers = None
14
+ self.child_nodes: list[ParserTreeNode] = []
15
+
16
+
17
+ def construct_parser_tree(
18
+ root_node: ParserTreeNode,
19
+ command_chain: list[str],
20
+ command_key_prefix: str,
21
+ **kwargs,
22
+ ) -> list[ParserTreeNode]:
23
+ node_path = [root_node]
24
+ if len(command_chain) == 0:
25
+ return node_path
26
+
27
+ node = root_node
28
+ for depth, command in enumerate(command_chain):
29
+ if node.subparsers is None:
30
+ node.subparsers = node.parser.add_subparsers(dest=f"{command_key_prefix}:{depth}")
31
+ for child_node in node.child_nodes:
32
+ if child_node.command == command:
33
+ node = child_node
34
+ break
35
+ else:
36
+ if depth == len(command_chain) - 1:
37
+ child_parser = node.subparsers.add_parser(command, **kwargs)
38
+ else:
39
+ child_parser = node.subparsers.add_parser(command)
40
+ child_node = ParserTreeNode(command, child_parser)
41
+ node.child_nodes.append(child_node)
42
+ node = child_node
43
+ node_path.append(node)
44
+
45
+ return node_path
46
+
47
+
48
+ class ParserTree(object):
49
+ def __init__(self, root_parser: argparse.ArgumentParser, command_key_prefix: str = "command"):
50
+ self.root_node = ParserTreeNode("", root_parser)
51
+ self.command_key_prefix = command_key_prefix
52
+
53
+ def add_subcommand_parser(self, command_chain: list[str], **kwargs) -> argparse.ArgumentParser:
54
+ *_, last_node = construct_parser_tree(self.root_node, command_chain, self.command_key_prefix, **kwargs)
55
+ return last_node.parser
56
+
57
+ def parse_args(self, args: list[str] | None = None) -> tuple[list[str], argparse.Namespace]:
58
+ # Before Python 3.12 the 'exit_on_error' attribute does not take effect properly
59
+ # if unknown arguments encountered. We have to employ this workaround
60
+ # TODO: remove this workaround when bumping the Python versions to above Python 3.12
61
+ if self.root_node.parser.exit_on_error:
62
+ known_args_namespace = self.root_node.parser.parse_args(args)
63
+ else:
64
+ known_args_namespace, unknown_args = self.root_node.parser.parse_known_args(args)
65
+ if len(unknown_args or []) > 0:
66
+ raise argparse.ArgumentError(None, "unrecognized arguments: %s" % " ".join(unknown_args))
67
+
68
+ command_pairs = []
69
+ namespace = argparse.Namespace()
70
+ for key, value in dict(vars(known_args_namespace)).items():
71
+ if key.startswith(self.command_key_prefix) and value is not None:
72
+ command_pairs.append((key, value))
73
+ else:
74
+ setattr(namespace, key, value)
75
+
76
+ return list(command for _, command in sorted(command_pairs)), namespace
@@ -13,6 +13,7 @@ from iker.common.utils.sequtils import head_or_none
13
13
  from iker.common.utils.strutils import is_blank
14
14
 
15
15
  __all__ = [
16
+ "ConnectionMaker",
16
17
  "DBAdapter",
17
18
  "orm_to_dict",
18
19
  "orm_clone",
@@ -21,9 +22,9 @@ __all__ = [
21
22
  ]
22
23
 
23
24
 
24
- class DBAdapter(object):
25
+ class ConnectionMaker(object):
25
26
  """
26
- Database adapter
27
+ Provides utilities that make it easier to establish database connections and sessions
27
28
  """
28
29
 
29
30
  class Drivers:
@@ -56,19 +57,19 @@ class DBAdapter(object):
56
57
  url: str | urllib.parse.ParseResult,
57
58
  engine_opts: dict[str, Any] | None = None,
58
59
  session_opts: dict[str, Any] | None = None,
59
- ) -> "DBAdapter":
60
+ ) -> "ConnectionMaker":
60
61
  if isinstance(url, str):
61
- return DBAdapter.from_url(driver, urllib.parse.urlparse(url), engine_opts, session_opts)
62
+ return ConnectionMaker.from_url(driver, urllib.parse.urlparse(url), engine_opts, session_opts)
62
63
  if isinstance(url, urllib.parse.ParseResult):
63
- return DBAdapter(driver,
64
- url.hostname,
65
- url.port,
66
- url.username,
67
- url.password,
68
- url.path.strip("/"),
69
- engine_opts,
70
- session_opts)
71
- raise ValueError("illegal parameter 'url'")
64
+ return ConnectionMaker(driver,
65
+ url.hostname,
66
+ url.port,
67
+ url.username,
68
+ url.password,
69
+ url.path.strip("/"),
70
+ engine_opts,
71
+ session_opts)
72
+ raise ValueError("malformed parameter 'url'")
72
73
 
73
74
  @property
74
75
  def connection_string(self) -> str:
@@ -129,6 +130,9 @@ class DBAdapter(object):
129
130
  return head_or_none(self.query_all(sql, **params))
130
131
 
131
132
 
133
+ DBAdapter = ConnectionMaker
134
+
135
+
132
136
  def orm_to_dict(orm: sqlalchemy.orm.DeclarativeBase, exclude: set[str] = None) -> dict[str, Any]:
133
137
  if not isinstance(orm, sqlalchemy.orm.DeclarativeBase):
134
138
  raise TypeError('not a SQLAlchemy ORM declarative base')
@@ -101,7 +101,7 @@ def deduped(ms: Sequence[T], comp_func: Callable[[T, T], bool]) -> list[T]:
101
101
  return deduped_ms
102
102
 
103
103
 
104
- def batch_yield(ms: Iterable[T], batch_size: int) -> Generator[list[T]]:
104
+ def batch_yield(ms: Iterable[T], batch_size: int) -> Generator[list[T], None, None]:
105
105
  """
106
106
  Splits the given input sequence into batches according to the specific batch size
107
107
 
@@ -131,7 +131,7 @@ def parse_params_string(s: str, delimiter: str = ",", kv_delimiter: str = "=") -
131
131
 
132
132
  result = {}
133
133
  for param in s.split(delimiter):
134
- segments = param.split(kv_delimiter)
134
+ segments = param.split(kv_delimiter, 1)
135
135
  if len(segments) == 1:
136
136
  result[head(segments)] = str(True)
137
137
  elif len(segments) == 2:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iker-python-common
3
- Version: 1.0.6
3
+ Version: 1.0.8
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.11
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -19,6 +19,7 @@ resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.baz
19
19
  resources/unittest/shutils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz
20
20
  src/iker/common/__init__.py
21
21
  src/iker/common/utils/__init__.py
22
+ src/iker/common/utils/argutils.py
22
23
  src/iker/common/utils/config.py
23
24
  src/iker/common/utils/dbutils.py
24
25
  src/iker/common/utils/dockerutils.py
@@ -43,6 +44,7 @@ src/iker_python_common.egg-info/top_level.txt
43
44
  test/iker_tests/__init__.py
44
45
  test/iker_tests/docker_fixtures.py
45
46
  test/iker_tests/iker_test.py
47
+ test/iker_tests/common/utils/argutils_test.py
46
48
  test/iker_tests/common/utils/config_test.py
47
49
  test/iker_tests/common/utils/dbutils_test.py
48
50
  test/iker_tests/common/utils/dockerutils_test.py
@@ -0,0 +1,158 @@
1
+ import argparse
2
+ import unittest
3
+
4
+ import ddt
5
+ import pytest
6
+
7
+ from iker.common.utils.argutils import ParserTree
8
+
9
+
10
+ def dummy_parser_tree():
11
+ parser_tree = ParserTree(argparse.ArgumentParser(description="dummy argument parser", exit_on_error=False))
12
+
13
+ for command_chain in [
14
+ [],
15
+ ["foo"],
16
+ ["foo", "bar"],
17
+ ["foo", "baz"],
18
+ ["bar"],
19
+ ["bar", "foo"],
20
+ ["bar", "baz"],
21
+ ["baz", "foo"],
22
+ ["baz", "bar"],
23
+ ["baz", "bar", "foo"],
24
+ ]:
25
+ parser = parser_tree.add_subcommand_parser(command_chain, exit_on_error=False)
26
+
27
+ option_infix = "-".join(command_chain)
28
+ if len(option_infix) == 0:
29
+ option_infix = "x"
30
+
31
+ parser.add_argument(f"--option-{option_infix}-str", type=str, default="")
32
+ parser.add_argument(f"--option-{option_infix}-int", type=int, default=0)
33
+ parser.add_argument(f"--option-{option_infix}-float", type=float, default=0.0)
34
+ parser.add_argument(f"--option-{option_infix}-switch", action="store_true")
35
+ parser.add_argument(f"--option-{option_infix}-nargs", type=str, action="append", default=[])
36
+
37
+ return parser_tree
38
+
39
+
40
+ @ddt.ddt
41
+ class ArgUtilsTest(unittest.TestCase):
42
+
43
+ @ddt.data(
44
+ (
45
+ [],
46
+ [],
47
+ [
48
+ ("option_x_str", ""),
49
+ ("option_x_int", 0),
50
+ ("option_x_float", 0.0),
51
+ ("option_x_switch", False),
52
+ ("option_x_nargs", []),
53
+ ],
54
+ ),
55
+ (
56
+ [
57
+ "--option-x-str", "dummy",
58
+ "--option-x-int", "1",
59
+ "--option-x-float", "1e6",
60
+ "--option-x-switch",
61
+ "--option-x-nargs", "dummy_1",
62
+ "--option-x-nargs", "dummy_2",
63
+ ],
64
+ [],
65
+ [
66
+ ("option_x_str", "dummy"),
67
+ ("option_x_int", 1),
68
+ ("option_x_float", 1e6),
69
+ ("option_x_switch", True),
70
+ ("option_x_nargs", ["dummy_1", "dummy_2"]),
71
+ ],
72
+ ),
73
+ (
74
+ ["foo"],
75
+ ["foo"],
76
+ [
77
+ ("option_foo_str", ""),
78
+ ("option_foo_int", 0),
79
+ ("option_foo_float", 0.0),
80
+ ("option_foo_switch", False),
81
+ ("option_foo_nargs", []),
82
+ ],
83
+ ),
84
+ (
85
+ [
86
+ "foo", "baz",
87
+ "--option-foo-baz-str", "dummy",
88
+ "--option-foo-baz-int", "1",
89
+ "--option-foo-baz-float", "1e6",
90
+ "--option-foo-baz-switch",
91
+ "--option-foo-baz-nargs", "dummy_1",
92
+ "--option-foo-baz-nargs", "dummy_2",
93
+ ],
94
+ ["foo", "baz"],
95
+ [
96
+ ("option_foo_baz_str", "dummy"),
97
+ ("option_foo_baz_int", 1),
98
+ ("option_foo_baz_float", 1e6),
99
+ ("option_foo_baz_switch", True),
100
+ ("option_foo_baz_nargs", ["dummy_1", "dummy_2"]),
101
+ ],
102
+ ),
103
+ (
104
+ [
105
+ "baz", "foo",
106
+ "--option-baz-foo-str", "dummy",
107
+ "--option-baz-foo-int", "1",
108
+ "--option-baz-foo-float", "1e6",
109
+ "--option-baz-foo-switch",
110
+ "--option-baz-foo-nargs", "dummy_1",
111
+ "--option-baz-foo-nargs", "dummy_2",
112
+ ],
113
+ ["baz", "foo"],
114
+ [
115
+ ("option_baz_foo_str", "dummy"),
116
+ ("option_baz_foo_int", 1),
117
+ ("option_baz_foo_float", 1e6),
118
+ ("option_baz_foo_switch", True),
119
+ ("option_baz_foo_nargs", ["dummy_1", "dummy_2"]),
120
+ ],
121
+ ),
122
+ (
123
+ [
124
+ "baz", "bar", "foo",
125
+ "--option-baz-bar-foo-str", "dummy",
126
+ "--option-baz-bar-foo-int", "1",
127
+ "--option-baz-bar-foo-float", "1e6",
128
+ "--option-baz-bar-foo-switch",
129
+ "--option-baz-bar-foo-nargs", "dummy_1",
130
+ "--option-baz-bar-foo-nargs", "dummy_2",
131
+ ],
132
+ ["baz", "bar", "foo"],
133
+ [
134
+ ("option_baz_bar_foo_str", "dummy"),
135
+ ("option_baz_bar_foo_int", 1),
136
+ ("option_baz_bar_foo_float", 1e6),
137
+ ("option_baz_bar_foo_switch", True),
138
+ ("option_baz_bar_foo_nargs", ["dummy_1", "dummy_2"]),
139
+ ],
140
+ ),
141
+ )
142
+ @ddt.unpack
143
+ def test_parser_tree(self, args, expect_commands, expect_options):
144
+ commands, args = dummy_parser_tree().parse_args(args)
145
+ self.assertEqual(commands, expect_commands)
146
+ for key, value in expect_options:
147
+ self.assertEqual(getattr(args, key), value)
148
+
149
+ @ddt.data(
150
+ (["foo", "foo"],),
151
+ (["foo", "--option-x-switch"],),
152
+ (["foo", "bar", "--option-bar-foo-switch"],),
153
+ (["baz", "foo", "bar"],),
154
+ )
155
+ @ddt.unpack
156
+ def test_parser_tree__exception(self, args):
157
+ with pytest.raises(argparse.ArgumentError):
158
+ dummy_parser_tree().parse_args(args)
@@ -12,7 +12,7 @@ from sqlalchemy.exc import IntegrityError
12
12
  from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass
13
13
  from sqlalchemy.orm.exc import FlushError
14
14
 
15
- from iker.common.utils.dbutils import DBAdapter
15
+ from iker.common.utils.dbutils import ConnectionMaker
16
16
  from iker.common.utils.dbutils import mysql_insert_ignore, postgresql_insert_on_conflict_do_nothing
17
17
  from iker.common.utils.dbutils import orm_clone
18
18
  from iker.common.utils.funcutils import unique_returns
@@ -107,18 +107,18 @@ class PostgresqlDummyRecord(PostgresqlBaseModel):
107
107
 
108
108
 
109
109
  @pytest.mark.skipif(sys.platform == "win32", reason="Test not applicable on Windows")
110
- def test_mysql_adapter(fixture_mysql_test_proc, fixture_mysql_test):
111
- adapter = DBAdapter(DBAdapter.Drivers.Mysql,
112
- "127.0.0.1",
113
- fixture_mysql_test_proc.port,
114
- fixture_mysql_test_proc.user,
115
- None,
116
- "test",
117
- session_opts=dict(
118
- expire_on_commit=False,
119
- ))
120
-
121
- adapter.create_model(MysqlBaseModel)
110
+ def test_mysql_connection_maker(fixture_mysql_test_proc, fixture_mysql_test):
111
+ maker = ConnectionMaker(ConnectionMaker.Drivers.Mysql,
112
+ "127.0.0.1",
113
+ fixture_mysql_test_proc.port,
114
+ fixture_mysql_test_proc.user,
115
+ None,
116
+ "test",
117
+ session_opts=dict(
118
+ expire_on_commit=False,
119
+ ))
120
+
121
+ maker.create_model(MysqlBaseModel)
122
122
 
123
123
  rng = randomizer()
124
124
 
@@ -144,28 +144,28 @@ def test_mysql_adapter(fixture_mysql_test_proc, fixture_mysql_test):
144
144
  dummy_unique_string=dummy_unique_string(),
145
145
  )
146
146
 
147
- with adapter.make_session() as session:
147
+ with maker.make_session() as session:
148
148
  original_records = [random_record() for _ in range(0, 1000)]
149
149
  session.bulk_save_objects(original_records, return_defaults=True)
150
150
  session.commit()
151
151
 
152
- with adapter.make_session() as session:
152
+ with maker.make_session() as session:
153
153
  count = session.query(sqlalchemy.func.count()).select_from(MysqlDummyRecord).scalar()
154
154
 
155
155
  assert count == 1000
156
156
 
157
- with adapter.make_session() as session:
157
+ with maker.make_session() as session:
158
158
  delete_rows = session.query(MysqlDummyRecord).order_by(MysqlDummyRecord.dummy_id).offset(500).all()
159
159
  for delete_row in delete_rows:
160
160
  session.delete(delete_row)
161
161
  session.commit()
162
162
 
163
- with adapter.make_session() as session:
163
+ with maker.make_session() as session:
164
164
  count = session.query(sqlalchemy.func.count()).select_from(MysqlDummyRecord).scalar()
165
165
 
166
166
  assert count == 500
167
167
 
168
- with adapter.make_session() as session:
168
+ with maker.make_session() as session:
169
169
  records = session.query(MysqlDummyRecord).order_by(MysqlDummyRecord.dummy_id).all()
170
170
 
171
171
  for expect, actual in zip(original_records[:count], records[:count]):
@@ -189,12 +189,12 @@ def test_mysql_adapter(fixture_mysql_test_proc, fixture_mysql_test):
189
189
 
190
190
  mysql_insert_ignore()
191
191
 
192
- with adapter.make_session() as session:
192
+ with maker.make_session() as session:
193
193
  # PK violation, but suppressed by the compiler plugin
194
194
  session.add(orm_clone(result))
195
195
  session.commit()
196
196
 
197
- with adapter.make_session() as session:
197
+ with maker.make_session() as session:
198
198
  # Unique index violation, but suppressed by the plugin
199
199
  # Unlike the Postgresql implementation, MySQL will not raise a 'FlushError'.
200
200
  # Instead, it will fill the auto increment column with zero
@@ -203,34 +203,34 @@ def test_mysql_adapter(fixture_mysql_test_proc, fixture_mysql_test):
203
203
 
204
204
  mysql_insert_ignore(enabled=False)
205
205
 
206
- with adapter.make_session() as session:
206
+ with maker.make_session() as session:
207
207
  # PK violation, which causes 'IntegrityError'
208
208
  with pytest.raises(IntegrityError):
209
209
  session.add(orm_clone(result))
210
210
  session.commit()
211
211
 
212
- with adapter.make_session() as session:
212
+ with maker.make_session() as session:
213
213
  # Unique index violation, which causes 'IntegrityError'
214
214
  with pytest.raises(IntegrityError):
215
215
  session.add(orm_clone(result, no_autoincrement=True))
216
216
  session.commit()
217
217
 
218
- adapter.drop_model(MysqlBaseModel)
218
+ maker.drop_model(MysqlBaseModel)
219
219
 
220
220
 
221
221
  @pytest.mark.skipif(sys.platform == "win32", reason="Test not applicable on Windows")
222
- def test_postgresql_adapter(fixture_postgresql_test_proc, fixture_postgresql_test):
223
- adapter = DBAdapter(DBAdapter.Drivers.Postgresql,
224
- fixture_postgresql_test.info.host,
225
- fixture_postgresql_test.info.port,
226
- fixture_postgresql_test.info.user,
227
- None,
228
- fixture_postgresql_test.info.dbname,
229
- session_opts=dict(
230
- expire_on_commit=False,
231
- ))
232
-
233
- adapter.create_model(PostgresqlBaseModel)
222
+ def test_postgresql_connection_maker(fixture_postgresql_test_proc, fixture_postgresql_test):
223
+ maker = ConnectionMaker(ConnectionMaker.Drivers.Postgresql,
224
+ fixture_postgresql_test.info.host,
225
+ fixture_postgresql_test.info.port,
226
+ fixture_postgresql_test.info.user,
227
+ None,
228
+ fixture_postgresql_test.info.dbname,
229
+ session_opts=dict(
230
+ expire_on_commit=False,
231
+ ))
232
+
233
+ maker.create_model(PostgresqlBaseModel)
234
234
 
235
235
  rng = randomizer()
236
236
 
@@ -260,28 +260,28 @@ def test_postgresql_adapter(fixture_postgresql_test_proc, fixture_postgresql_tes
260
260
  dummy_unique_string=dummy_unique_string(),
261
261
  )
262
262
 
263
- with adapter.make_session() as session:
263
+ with maker.make_session() as session:
264
264
  original_records = [random_record() for _ in range(0, 1000)]
265
265
  session.bulk_save_objects(original_records, return_defaults=True)
266
266
  session.commit()
267
267
 
268
- with adapter.make_session() as session:
268
+ with maker.make_session() as session:
269
269
  count = session.query(sqlalchemy.func.count()).select_from(PostgresqlDummyRecord).scalar()
270
270
 
271
271
  assert count == 1000
272
272
 
273
- with adapter.make_session() as session:
273
+ with maker.make_session() as session:
274
274
  delete_rows = session.query(PostgresqlDummyRecord).order_by(PostgresqlDummyRecord.dummy_id).offset(500).all()
275
275
  for delete_row in delete_rows:
276
276
  session.delete(delete_row)
277
277
  session.commit()
278
278
 
279
- with adapter.make_session() as session:
279
+ with maker.make_session() as session:
280
280
  count = session.query(sqlalchemy.func.count()).select_from(PostgresqlDummyRecord).scalar()
281
281
 
282
282
  assert count == 500
283
283
 
284
- with adapter.make_session() as session:
284
+ with maker.make_session() as session:
285
285
  records = session.query(PostgresqlDummyRecord).order_by(PostgresqlDummyRecord.dummy_id).all()
286
286
 
287
287
  for expect, actual in zip(original_records[:count], records[:count]):
@@ -309,12 +309,12 @@ def test_postgresql_adapter(fixture_postgresql_test_proc, fixture_postgresql_tes
309
309
 
310
310
  postgresql_insert_on_conflict_do_nothing()
311
311
 
312
- with adapter.make_session() as session:
312
+ with maker.make_session() as session:
313
313
  # PK violation, but suppressed by the compiler plugin
314
314
  session.add(orm_clone(result))
315
315
  session.commit()
316
316
 
317
- with adapter.make_session() as session:
317
+ with maker.make_session() as session:
318
318
  # Unique index violation, but suppressed by the plugin
319
319
  # However, since insertion is not performed, it fails to return and
320
320
  # flush the auto-generated primary key of the ORM, which raises 'FlushError'
@@ -324,16 +324,16 @@ def test_postgresql_adapter(fixture_postgresql_test_proc, fixture_postgresql_tes
324
324
 
325
325
  postgresql_insert_on_conflict_do_nothing(enabled=False)
326
326
 
327
- with adapter.make_session() as session:
327
+ with maker.make_session() as session:
328
328
  # PK violation, which causes 'IntegrityError'
329
329
  with pytest.raises(IntegrityError):
330
330
  session.add(orm_clone(result))
331
331
  session.commit()
332
332
 
333
- with adapter.make_session() as session:
333
+ with maker.make_session() as session:
334
334
  # Unique index violation, which causes 'IntegrityError'
335
335
  with pytest.raises(IntegrityError):
336
336
  session.add(orm_clone(result, no_autoincrement=True))
337
337
  session.commit()
338
338
 
339
- adapter.drop_model(PostgresqlBaseModel)
339
+ maker.drop_model(PostgresqlBaseModel)
@@ -206,21 +206,28 @@ class StrUtilsTest(unittest.TestCase):
206
206
  (" \t", {}),
207
207
  ("foo=bar", {"foo": "bar"}),
208
208
  (
209
- "dummy_key_1=dummy_value,dummy_key_2=dummy_value_2",
210
- {"dummy_key_1": "dummy_value", "dummy_key_2": "dummy_value_2"},
209
+ "dummy_key_1=dummy_value_1,dummy_key_2=dummy_value_2",
210
+ {"dummy_key_1": "dummy_value_1", "dummy_key_2": "dummy_value_2"},
211
211
  ),
212
212
  (
213
- "dummy_key_1=dummy_value,dummy_key_2=dummy_value_2,dummy_key_3",
214
- {"dummy_key_1": "dummy_value", "dummy_key_2": "dummy_value_2", "dummy_key_3": "True"},
213
+ "dummy_key_1=dummy_value_1,dummy_key_2=dummy_value_2,dummy_key_3",
214
+ {"dummy_key_1": "dummy_value_1", "dummy_key_2": "dummy_value_2", "dummy_key_3": "True"},
215
215
  ),
216
216
  (
217
- "dummy_key_1=dummy_value,dummy_key_2=dummy_value_2,dummy_key_3=do not use spaces in strings CLI",
217
+ "dummy_key_1=dummy_value_1,dummy_key_2=dummy_value_2,dummy_key_3=do not use spaces in strings CLI",
218
218
  {
219
- "dummy_key_1": "dummy_value",
219
+ "dummy_key_1": "dummy_value_1",
220
220
  "dummy_key_2": "dummy_value_2",
221
221
  "dummy_key_3": "do not use spaces in strings CLI",
222
222
  },
223
223
  ),
224
+ (
225
+ "dummy_key_1=dummy_value_1_key=dummy_value_1_value,dummy_key_2=dummy_value_2_key=dummy_value_2_value",
226
+ {
227
+ "dummy_key_1": "dummy_value_1_key=dummy_value_1_value",
228
+ "dummy_key_2": "dummy_value_2_key=dummy_value_2_value",
229
+ },
230
+ ),
224
231
  )
225
232
  @ddt.unpack
226
233
  def test_parse_params_string(self, s, params_dict):