Skip to content

Commit a98b074

Browse files
author
Sergio García Prado
committed
ISSUE #54
* Remove dependency to `minos-apigateway-common`. * Migrate `minos.api_gateway.common.MinosConfig` to `ApiGatewayConfig`.
1 parent 85e7312 commit a98b074

File tree

21 files changed

+395
-150
lines changed

21 files changed

+395
-150
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ coverage: ## check code coverage quickly with the default Python
6767

6868
reformat: ## check code coverage quickly with the default Python
6969
poetry run black --line-length 120 minos tests
70-
poetry run isort --recursive minos tests
70+
poetry run isort minos tests
7171

7272
docs: ## generate Sphinx HTML documentation, including API docs
7373
rm -rf docs/api

minos/api_gateway/rest/__init__.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
"""
2-
Copyright (C) 2021 Clariteia SL
3-
4-
This file is part of minos framework.
5-
6-
Minos framework can not be copied and/or distributed without the express permission of Clariteia SL.
7-
"""
81
__version__ = "0.0.4"
92

3+
from .config import (
4+
ApiGatewayConfig,
5+
)
6+
from .exceptions import (
7+
ApiGatewayConfigException,
8+
ApiGatewayException,
9+
InvalidAuthenticationException,
10+
NoTokenException,
11+
)
1012
from .launchers import (
1113
EntrypointLauncher,
1214
)

minos/api_gateway/rest/__main__.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
"""
2-
Copyright (C) 2021 Clariteia SL
3-
4-
This file is part of minos framework.
5-
6-
Minos framework can not be copied and/or distributed without the express permission of Clariteia SL.
7-
"""
81
from .cli import (
92
main,
103
)

minos/api_gateway/rest/cli.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
"""
2-
Copyright (C) 2021 Clariteia SL
3-
4-
This file is part of minos framework.
5-
6-
Minos framework can not be copied and/or distributed without the express permission of Clariteia SL.
7-
"""
81
from pathlib import (
92
Path,
103
)
@@ -14,13 +7,13 @@
147

158
import typer
169

17-
from minos.api_gateway.common import (
18-
MinosConfig,
10+
from .config import (
11+
ApiGatewayConfig,
1912
)
20-
from minos.api_gateway.rest.launchers import (
13+
from .launchers import (
2114
EntrypointLauncher,
2215
)
23-
from minos.api_gateway.rest.service import (
16+
from .service import (
2417
ApiGatewayRestService,
2518
)
2619

@@ -36,7 +29,7 @@ def start(
3629
"""Start Api Gateway services."""
3730

3831
try:
39-
config = MinosConfig(file_path)
32+
config = ApiGatewayConfig(file_path)
4033
except Exception as exc:
4134
typer.echo(f"Error loading config: {exc!r}")
4235
raise typer.Exit(code=1)

minos/api_gateway/rest/config.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
from __future__ import (
2+
annotations,
3+
)
4+
5+
import abc
6+
import collections
7+
import os
8+
import typing as t
9+
from distutils import (
10+
util,
11+
)
12+
from pathlib import (
13+
Path,
14+
)
15+
16+
import yaml
17+
18+
from .exceptions import (
19+
ApiGatewayConfigException,
20+
)
21+
22+
CONNECTION = collections.namedtuple("Connection", "host port")
23+
ENDPOINT = collections.namedtuple("Endpoint", "name route method controller action")
24+
REST = collections.namedtuple("Rest", "connection endpoints")
25+
DISCOVERY_CONNECTION = collections.namedtuple("DiscoveryConnection", "host port path")
26+
DATABASE = collections.namedtuple("Database", "host port password")
27+
DISCOVERY = collections.namedtuple("Discovery", "connection endpoints database")
28+
CORS = collections.namedtuple("Cors", "enabled")
29+
30+
_ENVIRONMENT_MAPPER = {
31+
"rest.host": "API_GATEWAY_REST_HOST",
32+
"rest.port": "API_GATEWAY_REST_PORT",
33+
"cors.enabled": "API_GATEWAY_CORS_ENABLED",
34+
"discovery.host": "DISCOVERY_SERVICE_HOST",
35+
"discovery.port": "DISCOVERY_SERVICE_PORT",
36+
"discovery.db.host": "DISCOVERY_SERVICE_DB_HOST",
37+
"discovery.db.port": "DISCOVERY_SERVICE_DB_PORT",
38+
"discovery.db.password": "DISCOVERY_SERVICE_DB_PASSWORD",
39+
}
40+
41+
_PARAMETERIZED_MAPPER = {
42+
"rest.host": "api_gateway_rest_host",
43+
"rest.port": "api_gateway_rest_port",
44+
"cors.enabled": "api_gateway_cors_enabled",
45+
"discovery.host": "discovery_service_host",
46+
"discovery.port": "discovery_service_port",
47+
"discovery.db.host": "discovery_service_db_host",
48+
"discovery.db.port": "discovery_service_db_port",
49+
"discovery.db.password": "discovery_service_db_password",
50+
}
51+
52+
53+
class ApiGatewayConfig(abc.ABC):
54+
"""Api Gateway config class."""
55+
56+
__slots__ = ("_services", "_path", "_data", "_with_environment", "_parameterized")
57+
58+
def __init__(self, path: t.Union[Path, str], with_environment: bool = True, **kwargs):
59+
if isinstance(path, Path):
60+
path = str(path)
61+
self._services = {}
62+
self._path = path
63+
self._load(path)
64+
self._with_environment = with_environment
65+
self._parameterized = kwargs
66+
67+
@staticmethod
68+
def _file_exit(path: str) -> bool:
69+
if os.path.isfile(path):
70+
return True
71+
return False
72+
73+
def _load(self, path):
74+
if self._file_exit(path):
75+
with open(path) as f:
76+
self._data = yaml.load(f, Loader=yaml.FullLoader)
77+
else:
78+
raise ApiGatewayConfigException(f"Check if this path: {path} is correct")
79+
80+
def _get(self, key: str, **kwargs: t.Any) -> t.Any:
81+
if key in _PARAMETERIZED_MAPPER and _PARAMETERIZED_MAPPER[key] in self._parameterized:
82+
return self._parameterized[_PARAMETERIZED_MAPPER[key]]
83+
84+
if self._with_environment and key in _ENVIRONMENT_MAPPER and _ENVIRONMENT_MAPPER[key] in os.environ:
85+
if os.environ[_ENVIRONMENT_MAPPER[key]] in ["true", "True", "false", "False"]:
86+
return bool(util.strtobool(os.environ[_ENVIRONMENT_MAPPER[key]]))
87+
return os.environ[_ENVIRONMENT_MAPPER[key]]
88+
89+
def _fn(k: str, data: dict[str, t.Any]) -> t.Any:
90+
current, _, following = k.partition(".")
91+
92+
part = data[current]
93+
if not following:
94+
return part
95+
96+
return _fn(following, part)
97+
98+
return _fn(key, self._data)
99+
100+
@property
101+
def rest(self) -> REST:
102+
"""Get the rest config.
103+
104+
:return: A ``REST`` NamedTuple instance.
105+
"""
106+
connection = self._rest_connection
107+
endpoints = self._rest_endpoints
108+
return REST(connection=connection, endpoints=endpoints)
109+
110+
@property
111+
def _rest_connection(self):
112+
connection = CONNECTION(host=self._get("rest.host"), port=int(self._get("rest.port")))
113+
return connection
114+
115+
@property
116+
def _rest_endpoints(self) -> list[ENDPOINT]:
117+
info = self._get("rest.endpoints")
118+
endpoints = [self._rest_endpoints_entry(endpoint) for endpoint in info]
119+
return endpoints
120+
121+
@staticmethod
122+
def _rest_endpoints_entry(endpoint: dict[str, t.Any]) -> ENDPOINT:
123+
return ENDPOINT(
124+
name=endpoint["name"],
125+
route=endpoint["route"],
126+
method=endpoint["method"].upper(),
127+
controller=endpoint["controller"],
128+
action=endpoint["action"],
129+
)
130+
131+
@property
132+
def cors(self) -> CORS:
133+
"""Get the cors config.
134+
135+
:return: A ``CORS`` NamedTuple instance.
136+
"""
137+
return CORS(enabled=self._get("cors.enabled"))
138+
139+
@property
140+
def discovery(self) -> DISCOVERY:
141+
"""Get the rest config.
142+
143+
:return: A ``REST`` NamedTuple instance.
144+
"""
145+
connection = self._discovery_connection
146+
endpoints = self._discovery_endpoints
147+
database = self._discovery_database
148+
return DISCOVERY(connection=connection, endpoints=endpoints, database=database)
149+
150+
@property
151+
def _discovery_connection(self):
152+
connection = DISCOVERY_CONNECTION(
153+
host=self._get("discovery.host"), port=int(self._get("discovery.port")), path=self._get("discovery.path")
154+
)
155+
return connection
156+
157+
@property
158+
def _discovery_database(self):
159+
connection = DATABASE(
160+
host=self._get("discovery.db.host"),
161+
port=int(self._get("discovery.db.port")),
162+
password=self._get("discovery.db.password"),
163+
)
164+
return connection
165+
166+
@property
167+
def _discovery_endpoints(self) -> list[ENDPOINT]:
168+
info = self._get("discovery.endpoints")
169+
endpoints = [self._discovery_endpoints_entry(endpoint) for endpoint in info]
170+
return endpoints
171+
172+
@staticmethod
173+
def _discovery_endpoints_entry(endpoint: dict[str, t.Any]) -> ENDPOINT:
174+
return ENDPOINT(
175+
name=endpoint["name"],
176+
route=endpoint["route"],
177+
method=endpoint["method"].upper(),
178+
controller=endpoint["controller"],
179+
action=endpoint["action"],
180+
)
Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
class InvalidAuthenticationException(Exception):
2-
pass
1+
class ApiGatewayException(Exception):
2+
"""TODO"""
33

44

5-
class NoTokenException(Exception):
6-
pass
5+
class InvalidAuthenticationException(ApiGatewayException):
6+
"""TODO"""
7+
8+
9+
class NoTokenException(ApiGatewayException):
10+
"""TODO"""
11+
12+
13+
class ApiGatewayConfigException(ApiGatewayException):
14+
"""Base config exception."""

minos/api_gateway/rest/handler.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
"""minos.api_gateway.rest.handler module."""
2-
31
import logging
42
from typing import (
53
Any,
@@ -15,7 +13,7 @@
1513
URL,
1614
)
1715

18-
from minos.api_gateway.rest.exceptions import (
16+
from .exceptions import (
1917
InvalidAuthenticationException,
2018
NoTokenException,
2119
)

minos/api_gateway/rest/launchers.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
"""
2-
Copyright (C) 2021 Clariteia SL
3-
4-
This file is part of minos framework.
5-
6-
Minos framework can not be copied and/or distributed without the express permission of Clariteia SL.
7-
"""
8-
91
import logging
102
from typing import (
113
NoReturn,
@@ -21,8 +13,8 @@
2113
cached_property,
2214
)
2315

24-
from minos.api_gateway.common import (
25-
MinosConfig,
16+
from .config import (
17+
ApiGatewayConfig,
2618
)
2719

2820
logger = logging.getLogger(__name__)
@@ -31,7 +23,7 @@
3123
class EntrypointLauncher:
3224
"""EntryPoint Launcher class."""
3325

34-
def __init__(self, config: MinosConfig, services: tuple, *args, **kwargs):
26+
def __init__(self, config: ApiGatewayConfig, services: tuple, *args, **kwargs):
3527
self.config = config
3628
self.services = services
3729

minos/api_gateway/rest/service.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
"""minos.api_gateway.rest.service module."""
2-
31
import logging
42

53
from aiohttp import (
@@ -12,18 +10,18 @@
1210
AIOHTTPService,
1311
)
1412

15-
from minos.api_gateway.common import (
16-
MinosConfig,
13+
from .config import (
14+
ApiGatewayConfig,
1715
)
18-
from minos.api_gateway.rest import (
19-
handler,
16+
from .handler import (
17+
orchestrate,
2018
)
2119

2220
logger = logging.getLogger(__name__)
2321

2422

2523
class ApiGatewayRestService(AIOHTTPService):
26-
def __init__(self, address: str, port: int, config: MinosConfig):
24+
def __init__(self, address: str, port: int, config: ApiGatewayConfig):
2725
self.config = config
2826
super().__init__(address, port)
2927

@@ -36,6 +34,6 @@ async def create_application(self) -> web.Application:
3634

3735
app["config"] = self.config
3836

39-
app.router.add_route("*", "/{endpoint:.*}", handler.orchestrate)
37+
app.router.add_route("*", "/{endpoint:.*}", orchestrate)
4038

4139
return app

0 commit comments

Comments
 (0)