Skip to content

Commit 1ff0f9d

Browse files
author
Sergio García Prado
authored
Merge pull request #314 from minos-framework/issue-150-config-default-values
#150 - Add support for default values on `Config`
2 parents 587098f + 5252601 commit 1ff0f9d

File tree

27 files changed

+437
-109
lines changed

27 files changed

+437
-109
lines changed

packages/core/minos-microservice-aggregate/tests/test_aggregate/test_events/test_repositories/test_pg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def build_event_repository(self) -> EventRepository:
4444
return PostgreSqlEventRepository(**self.repository_db)
4545

4646
def test_constructor(self):
47-
repository = PostgreSqlEventRepository("host", 1234, "database", "user", "password")
47+
repository = PostgreSqlEventRepository("database", "host", 1234, "user", "password")
4848
self.assertIsInstance(repository, EventRepository)
4949
self.assertEqual("host", repository.host)
5050
self.assertEqual(1234, repository.port)

packages/core/minos-microservice-aggregate/tests/test_aggregate/test_transactions/test_repositories/test_pg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ async def test_subclass(self) -> None:
4343
self.assertTrue(issubclass(PostgreSqlTransactionRepository, TransactionRepository))
4444

4545
def test_constructor(self):
46-
repository = PostgreSqlTransactionRepository("host", 1234, "database", "user", "password")
46+
repository = PostgreSqlTransactionRepository("database", "host", 1234, "user", "password")
4747
self.assertIsInstance(repository, PostgreSqlTransactionRepository)
4848
self.assertEqual("host", repository.host)
4949
self.assertEqual(1234, repository.port)

packages/core/minos-microservice-common/minos/common/config/abc.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from typing import (
2424
TYPE_CHECKING,
2525
Any,
26+
Optional,
2627
Union,
2728
)
2829

@@ -43,13 +44,17 @@
4344
InjectableMixin,
4445
)
4546

47+
sentinel = object()
48+
4649

4750
@Injectable("config")
4851
class Config(ABC):
4952
"""Config base class."""
5053

5154
__slots__ = ("_file_path", "_data", "_with_environment", "_parameterized")
5255

56+
DEFAULT_VALUES: dict[str, Any] = dict()
57+
5358
def __init__(self, path: Union[Path, str], with_environment: bool = True, **kwargs):
5459
super().__init__()
5560
if isinstance(path, str):
@@ -282,7 +287,7 @@ def get_by_key(self, key: str) -> Any:
282287
:return: A value instance.
283288
"""
284289

285-
def _fn(k: str, data: dict[str, Any], previous: str) -> Any:
290+
def _fn(k: str, data: dict[str, Any], previous: str = "", default: Optional[Any] = sentinel) -> Any:
286291
current, _sep, following = k.partition(".")
287292
full = f"{previous}.{current}".lstrip(".")
288293

@@ -293,20 +298,33 @@ def _fn(k: str, data: dict[str, Any], previous: str) -> Any:
293298
with suppress(KeyError):
294299
return os.environ[self._to_environment_variable(full)]
295300

296-
part = data[current]
297-
if not following:
298-
if not isinstance(part, dict):
299-
return part
301+
if default is not sentinel and current in default:
302+
default_part = default[current]
303+
else:
304+
default_part = sentinel
305+
306+
if current not in data and default_part is not sentinel:
307+
part = default_part
308+
else:
309+
part = data[current]
310+
311+
if following:
312+
return _fn(following, part, full, default_part)
313+
314+
if not isinstance(part, dict):
315+
return part
300316

301-
result = dict()
302-
for subpart in part:
303-
result[subpart] = _fn(subpart, part, full)
304-
return result
317+
keys = part.keys()
318+
if isinstance(default_part, dict):
319+
keys |= default_part.keys()
305320

306-
return _fn(following, part, full)
321+
result = dict()
322+
for subpart in keys:
323+
result[subpart] = _fn(subpart, part, full, default_part)
324+
return result
307325

308326
try:
309-
return _fn(key, self._data, str())
327+
return _fn(key, self._data, default=self.DEFAULT_VALUES)
310328
except Exception:
311329
raise MinosConfigException(f"{key!r} field is not defined on the configuration!")
312330

packages/core/minos-microservice-common/minos/common/config/v1.py

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
from contextlib import (
66
suppress,
77
)
8-
from pathlib import (
9-
Path,
10-
)
118
from typing import (
129
TYPE_CHECKING,
1310
Any,
@@ -156,12 +153,14 @@ def _get_interface_http(self) -> dict[str, Any]:
156153
except Exception as exc:
157154
raise MinosConfigException(f"The 'http' interface is not available: {exc!r}")
158155

156+
try:
157+
connector = self.get_by_key("rest")
158+
except MinosConfigException:
159+
connector = dict()
160+
159161
return {
160162
"port": import_module(port),
161-
"connector": {
162-
"host": self.get_by_key("rest.host"),
163-
"port": int(self.get_by_key("rest.port")),
164-
},
163+
"connector": connector,
165164
}
166165

167166
def _get_interface_broker(self) -> dict[str, Any]:
@@ -170,18 +169,27 @@ def _get_interface_broker(self) -> dict[str, Any]:
170169
except Exception as exc:
171170
raise MinosConfigException(f"The 'broker' interface is not available: {exc!r}")
172171

172+
try:
173+
common = self.get_by_key("broker")
174+
except MinosConfigException:
175+
common = dict()
176+
177+
try:
178+
common["queue"] = self.get_by_key("broker.queue")
179+
common["queue"].pop("database", None)
180+
common["queue"].pop("port", None)
181+
common["queue"].pop("host", None)
182+
common["queue"].pop("port", None)
183+
common["queue"].pop("user", None)
184+
common["queue"].pop("password", None)
185+
except MinosConfigException:
186+
common["queue"] = dict()
187+
173188
return {
174189
"port": import_module(port),
175190
"publisher": dict(),
176191
"subscriber": dict(),
177-
"common": {
178-
"host": self.get_by_key("broker.host"),
179-
"port": int(self.get_by_key("broker.port")),
180-
"queue": {
181-
"records": int(self.get_by_key("broker.queue.records")),
182-
"retry": int(self.get_by_key("broker.queue.retry")),
183-
},
184-
},
192+
"common": common,
185193
}
186194

187195
def _get_interface_periodic(self):
@@ -254,10 +262,8 @@ def _get_database_broker(self):
254262
return self._get_database_by_name("broker.queue")
255263

256264
def _get_database_saga(self) -> dict[str, Any]:
257-
raw = self.get_by_key("saga.storage.path")
258-
return {
259-
"path": Path(raw) if raw.startswith("/") else self.file_path.parent / raw,
260-
}
265+
raw = self.get_by_key("saga.storage")
266+
return raw
261267

262268
def _get_database_event(self) -> dict[str, Any]:
263269
return self._get_database_by_name("repository")
@@ -269,20 +275,15 @@ def _get_database_snapshot(self) -> dict[str, Any]:
269275
return self._get_database_by_name("snapshot")
270276

271277
def _get_database_by_name(self, prefix: str):
272-
return {
273-
"database": self.get_by_key(f"{prefix}.database"),
274-
"user": self.get_by_key(f"{prefix}.user"),
275-
"password": self.get_by_key(f"{prefix}.password"),
276-
"host": self.get_by_key(f"{prefix}.host"),
277-
"port": int(self.get_by_key(f"{prefix}.port")),
278-
}
278+
data = self.get_by_key(prefix)
279+
data.pop("records", None)
280+
data.pop("retry", None)
281+
return data
279282

280283
def _get_discovery(self) -> dict[str, Any]:
281-
return {
282-
"client": self.get_type_by_key("discovery.client"),
283-
"host": self.get_by_key("discovery.host"),
284-
"port": self.get_by_key("discovery.port"),
285-
}
284+
data = self.get_by_key("discovery")
285+
data["client"] = self.get_type_by_key("discovery.client")
286+
return data
286287

287288
def _to_parameterized_variable(self, key: str) -> str:
288289
return self._PARAMETERIZED_MAPPER[key]

packages/core/minos-microservice-common/minos/common/config/v2.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
from copy import (
99
deepcopy,
1010
)
11-
from pathlib import (
12-
Path,
13-
)
1411
from typing import (
1512
TYPE_CHECKING,
1613
Any,
@@ -98,15 +95,8 @@ def _get_injections(self) -> list[type[InjectableMixin]]:
9895

9996
def _get_databases(self) -> dict[str, dict[str, Any]]:
10097
data = deepcopy(self.get_by_key("databases"))
101-
102-
if "saga" in data:
103-
if "path" in data["saga"]:
104-
data["saga"]["path"] = self._str_to_path(data["saga"]["path"])
10598
return data
10699

107-
def _str_to_path(self, raw: str) -> Path:
108-
return Path(raw) if raw.startswith("/") else self.file_path.parent / raw
109-
110100
def _get_interfaces(self) -> dict[str, dict[str, Any]]:
111101
data = deepcopy(self.get_by_key("interfaces"))
112102

packages/core/minos-microservice-common/minos/common/database/abc.py

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,66 @@
3232
class PostgreSqlMinosDatabase(SetupMixin):
3333
"""PostgreSql Minos Database base class."""
3434

35-
def __init__(self, host: str, port: int, database: str, user: str, password: str, *args, **kwargs):
35+
def __init__(
36+
self,
37+
database: str,
38+
host: Optional[str] = None,
39+
port: Optional[int] = None,
40+
user: Optional[str] = None,
41+
password: Optional[str] = None,
42+
*args,
43+
**kwargs,
44+
):
3645
super().__init__(*args, **kwargs)
37-
self.host = host
38-
self.port = port
39-
self.database = database
40-
self.user = user
41-
self.password = password
46+
self._database = database
47+
self._host = host
48+
self._port = port
49+
self._user = user
50+
self._password = password
4251

4352
self._pool = None
4453
self._owned_pool = False
4554

55+
@property
56+
def database(self) -> str:
57+
"""Get the database's database.
58+
59+
:return: A ``str`` value.
60+
"""
61+
return self.pool.database
62+
63+
@property
64+
def host(self) -> str:
65+
"""Get the database's host.
66+
67+
:return: A ``str`` value.
68+
"""
69+
return self.pool.host
70+
71+
@property
72+
def port(self) -> int:
73+
"""Get the database's port.
74+
75+
:return: An ``int`` value.
76+
"""
77+
return self.pool.port
78+
79+
@property
80+
def user(self) -> str:
81+
"""Get the database's user.
82+
83+
:return: A ``str`` value.
84+
"""
85+
return self.pool.user
86+
87+
@property
88+
def password(self) -> str:
89+
"""Get the database's password.
90+
91+
:return: A ``str`` value.
92+
"""
93+
return self.pool.password
94+
4695
async def _destroy(self) -> None:
4796
if self._owned_pool:
4897
await self._pool.destroy()
@@ -178,6 +227,6 @@ def _build_pool(self, pool: PostgreSqlPool = None) -> tuple[PostgreSqlPool, bool
178227
return pool, False
179228

180229
pool = PostgreSqlPool(
181-
host=self.host, port=self.port, database=self.database, user=self.user, password=self.password
230+
host=self._host, port=self._port, database=self._database, user=self._user, password=self._password
182231
)
183232
return pool, True

packages/core/minos-microservice-common/minos/common/database/pools.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,30 @@
4040
class PostgreSqlPool(Pool[ContextManager]):
4141
"""Postgres Pool class."""
4242

43-
def __init__(self, host: str, port: int, database: str, user: str, password: str, *args, **kwargs):
43+
def __init__(
44+
self,
45+
database: str,
46+
host: Optional[str] = None,
47+
port: Optional[int] = None,
48+
user: Optional[str] = None,
49+
password: Optional[str] = None,
50+
*args,
51+
**kwargs,
52+
):
4453
super().__init__(*args, **kwargs)
54+
55+
if host is None:
56+
host = "localhost"
57+
if port is None:
58+
port = 5432
59+
if user is None:
60+
user = "postgres"
61+
if password is None:
62+
password = ""
63+
64+
self.database = database
4565
self.host = host
4666
self.port = port
47-
self.database = database
4867
self.user = user
4968
self.password = password
5069

packages/core/minos-microservice-common/minos/common/storage/lmdb.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ def _get_table(self, table: str):
9797
return self._tables[table]
9898

9999
@classmethod
100-
def build(cls, path: Union[str, Path], max_db: int = 10, map_size: int = int(1e9), **kwargs) -> MinosStorageLmdb:
100+
def build(
101+
cls, path: Optional[Union[str, Path]] = None, max_db: int = 10, map_size: int = int(1e9), **kwargs
102+
) -> MinosStorageLmdb:
101103
"""Build a new instance.
102104
103105
:param path: Path in which the database is stored.
@@ -106,6 +108,8 @@ def build(cls, path: Union[str, Path], max_db: int = 10, map_size: int = int(1e9
106108
:param kwargs: Additional named arguments.
107109
:return: A ``MinosStorageLmdb`` instance.
108110
"""
111+
if path is None:
112+
path = ".lmdb"
109113

110114
env: lmdb.Environment = lmdb.open(str(path), max_dbs=max_db, map_size=map_size)
111115
return cls(env, **kwargs)

packages/core/minos-microservice-common/tests/test_common/test_config/test_abc.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
class _Config(Config):
2626
"""For testing purposes."""
2727

28+
DEFAULT_VALUES = {"foo": {"bar": 56}, "saga": {"name": "foobar"}}
29+
2830
# noinspection PyPropertyDefinition
2931
@property
3032
def _version(self) -> int:
@@ -88,6 +90,13 @@ def test_file_raises(self):
8890
def test_get_by_key(self):
8991
self.assertEqual("Order", self.config.get_by_key("service.name"))
9092

93+
def test_get_by_key_with_default_without_overlap(self):
94+
self.assertEqual(56, self.config.get_by_key("foo.bar"))
95+
96+
def test_get_by_key_with_default_with_overlap(self):
97+
expected = {"storage": {"path": "./order.lmdb"}, "name": "foobar"}
98+
self.assertEqual(expected, self.config.get_by_key("saga"))
99+
91100
def test_get_by_key_raises(self):
92101
with self.assertRaises(MinosConfigException):
93102
self.assertEqual("Order", self.config.get_by_key("something"))

0 commit comments

Comments
 (0)