Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Alberto Vara <a.vara.1986@gmail.com>
Àlex Pérez <alexperezpujol@disroot.org>
Hugo Camino <hugo.camino.villacorta@gmail.com>
José Manuel <jmrivas86@gmail.com>
Javier Luna molina <javierlunamolina@gmail.com>
pilamb <perikopalotes@gmail.com>
José Manuel <jmrivas86@gmail.com>
Mike Rubin <miguelgr1988@gmail.com>
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ microservices with Python which handles cross-cutting concerns:
- Externalized configuration
- Logging
- Health checks
- Metrics (TODO)
- Metrics
- Distributed tracing

PyMS is powered by [Flask](https://flask.palletsprojects.com/en/1.1.x/), [Connexion](https://github.com/zalando/connexion) and [Opentracing](https://opentracing.io/).
Expand Down
14 changes: 7 additions & 7 deletions docs/ms_class.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,19 @@ Each keyword in our configuration block, can be accessed in our Microservice obj
```yaml
# Config.yml
pyms:
config:
app_name: "Python Microservice"
foo: "var"
multiplevars:
config1: "test1"
config2: "test2"
config:
app_name: "Python Microservice"
foo: "var"
multiplevars:
config1: "test1"
config2: "test2"

```
```python
#app.py
from pyms.flask.app import Microservice

ms = Microservice(service="example-config", path=__file__)
ms = Microservice(path=__file__)
print(ms.config.APP_NAME)
# >> "Python Microservice"
print(ms.config.app_name)
Expand Down
16 changes: 8 additions & 8 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ if __name__ == '__main__':
config.yml
```yaml
pyms:
services: # 1.2
requests:
data: {}
config: # 1.3
DEBUG: true
APP_NAME: business-glossary
APPLICATION_ROOT : ""
SECRET_KEY: "gjr39dkjn344_!67#"
services: # 1.2
requests:
data: {}
config: # 1.3
DEBUG: true
APP_NAME: business-glossary
APPLICATION_ROOT : ""
SECRET_KEY: "gjr39dkjn344_!67#"
```

## So what did that code do?
Expand Down
101 changes: 83 additions & 18 deletions pyms/config/conf.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pyms.config.confile import ConfFile
from pyms.exceptions import ServiceDoesNotExistException
from pyms.constants import PYMS_CONFIG_WHITELIST_KEYWORDS
from pyms.exceptions import ServiceDoesNotExistException, ConfigErrorException, AttrDoesNotExistException


def get_conf(*args, **kwargs):
Expand All @@ -8,26 +9,26 @@ def get_conf(*args, **kwargs):
parent and this name, in example of the next yaml, tracer will be `pyms.tracer`. If we have got his config file:
```
pyms:
metrics: true
requests:
data: data
swagger:
path: ""
file: "swagger.yaml"
tracer:
client: "jaeger"
host: "localhost"
component_name: "Python Microservice"
my-ms:
DEBUG: true
TESTING: true
services:
metrics: true
requests:
data: data
swagger:
path: ""
file: "swagger.yaml"
tracer:
client: "jaeger"
host: "localhost"
component_name: "Python Microservice"
config:
DEBUG: true
TESTING: true
```
* `pyms` block is the default key to load in the pyms.flask.app.create_app.Microservice class.
* `pyms.services`: block is the default key to load in the pyms.flask.app.create_app.Microservice class.
* `metrics`: is set as the service `pyms.metrics`
* `swagger`: is set as the service `pyms.swagger`
* `tracer`: is set as the service `pyms.tracer`
* `my-ms` block is defined by the env var `CONFIGMAP_SERVICE`. By default is `ms`. This block is the default flask
block config
* `pyms.config`: This block is the default flask block config
:param args:
:param kwargs:

Expand All @@ -36,6 +37,70 @@ def get_conf(*args, **kwargs):
service = kwargs.pop('service', None)
if not service:
raise ServiceDoesNotExistException("Service not defined")

config = ConfFile(*args, **kwargs)
return getattr(config, service)


def validate_conf(*args, **kwargs):
config = ConfFile(*args, **kwargs)
is_config_ok = True
try:
config.pyms
except AttrDoesNotExistException:
is_config_ok = False
if not is_config_ok:
raise ConfigErrorException("""Config file must start with `pyms` keyword, for example:
pyms:
services:
metrics: true
requests:
data: data
swagger:
path: ""
file: "swagger.yaml"
tracer:
client: "jaeger"
host: "localhost"
component_name: "Python Microservice"
config:
DEBUG: true
TESTING: true""")
try:
config.pyms.config
except AttrDoesNotExistException:
is_config_ok = False
if not is_config_ok:
raise ConfigErrorException("""`pyms` block must contain a `config` keyword in your Config file, for example:
pyms:
services:
metrics: true
requests:
data: data
swagger:
path: ""
file: "swagger.yaml"
tracer:
client: "jaeger"
host: "localhost"
component_name: "Python Microservice"
config:
DEBUG: true
TESTING: true""")
wrong_keywords = [i for i in config.pyms if i not in PYMS_CONFIG_WHITELIST_KEYWORDS]
if len(wrong_keywords) > 0:
raise ConfigErrorException("""{} isn`t a valid keyword for pyms block, for example:
pyms:
services:
metrics: true
requests:
data: data
swagger:
path: ""
file: "swagger.yaml"
tracer:
client: "jaeger"
host: "localhost"
component_name: "Python Microservice"
config:
DEBUG: true
TESTING: true""".format(wrong_keywords))
2 changes: 2 additions & 0 deletions pyms/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
CONFIG_BASE = "pyms.config"

SERVICE_BASE = "pyms.services"

PYMS_CONFIG_WHITELIST_KEYWORDS = ["config", "services"]
4 changes: 4 additions & 0 deletions pyms/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ class ConfigDoesNotFoundException(Exception):
pass


class ConfigErrorException(Exception):
pass


class PackageNotExists(Exception):
pass
4 changes: 2 additions & 2 deletions pyms/flask/app/create_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from flask_opentracing import FlaskTracing

from pyms.config import get_conf
from pyms.config.conf import validate_conf
from pyms.constants import LOGGER_NAME, CONFIG_BASE
from pyms.flask.healthcheck import healthcheck_blueprint
from pyms.flask.services.driver import ServicesManager
Expand Down Expand Up @@ -56,8 +57,6 @@ def example():
Environments variables of PyMS:
**CONFIGMAP_FILE**: The path to the configuration file. By default, PyMS search the configuration file in your
actual folder with the name "config.yml"
**CONFIGMAP_SERVICE**: the name of the keyword that define the block of key-value of [Flask Configuration Handling](http://flask.pocoo.org/docs/1.0/config/)
and your own configuration (see the next section to more info)

## Create configuration
Each microservice needs a config file in yaml or json format to work with it. This configuration contains
Expand Down Expand Up @@ -99,6 +98,7 @@ def example():

def __init__(self, *args, **kwargs):
self.path = os.path.dirname(kwargs.get("path", __file__))
validate_conf()
self.config = get_conf(service=CONFIG_BASE)
self.init_services()

Expand Down
2 changes: 0 additions & 2 deletions pyms/flask/app/create_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
def config():
"""The behavior of this function is to access to the configuration outer the scope of flask context, to prevent to
raise a `'working outside of application context`
**IMPORTANT:** If you use this method to get configuration out of context, you must set the `CONFIGMAP_SERVICE` or
set the default key `ms` for your configuration block in your config.yml
:return:
"""
ms = Microservice()
Expand Down
7 changes: 7 additions & 0 deletions pyms/flask/services/swagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@


class Service(DriverService):
"""The parameters you can add to your config are:
* **path:** The relative or absolute route to your swagger yaml file. The default value is the current directory
* **file:** The name of you swagger yaml file. The default value is `swagger.yaml`
* **url:** The url where swagger run in your server. The default value is `/ui/`.
* **project_dir:** Relative path of the project folder to automatic routing,
see [this link for more info](https://github.com/zalando/connexion#automatic-routing). The default value is `project`
"""
service = "swagger"
default_values = {
"path": SWAGGER_PATH,
Expand Down
10 changes: 10 additions & 0 deletions tests/config-tests-bad-structure.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
config:
debug: true
testing: true
app_name: "Python Microservice"
application_root: /
test_var: general
subservice1:
test: input
subservice2:
test: output
11 changes: 11 additions & 0 deletions tests/config-tests-bad-structure2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pyms:
config2:
debug: true
testing: true
app_name: "Python Microservice"
application_root: /
test_var: general
subservice1:
test: input
subservice2:
test: output
21 changes: 21 additions & 0 deletions tests/config-tests-bad-structure3.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
pyms:
metrics: true
requests:
data: data
swagger:
path: ""
file: "swagger.yaml"
tracer:
client: "jaeger"
host: "localhost"
component_name: "Python Microservice"
config:
debug: true
testing: true
app_name: "Python Microservice"
application_root: /
test_var: general
subservice1:
test: input
subservice2:
test: output
26 changes: 25 additions & 1 deletion tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
from unittest import mock

from pyms.config import get_conf, ConfFile
from pyms.config.conf import validate_conf
from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT, LOGGER_NAME, CONFIG_BASE
from pyms.exceptions import AttrDoesNotExistException, ConfigDoesNotFoundException, ServiceDoesNotExistException
from pyms.exceptions import AttrDoesNotExistException, ConfigDoesNotFoundException, ServiceDoesNotExistException, \
ConfigErrorException

logger = logging.getLogger(LOGGER_NAME)

Expand Down Expand Up @@ -99,6 +101,7 @@ def test_example_test_json_file(self):

class ConfCacheTests(unittest.TestCase):
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

def test_get_cache(self):
config = ConfFile(path=os.path.join(self.BASE_DIR, "config-tests-cache.yml"))
config.set_path(os.path.join(self.BASE_DIR, "config-tests-cache2.yml"))
Expand Down Expand Up @@ -148,3 +151,24 @@ def test_default_flask(self):
def test_without_params(self, mock_confile):
with self.assertRaises(ServiceDoesNotExistException):
get_conf()


class ConfValidateTests(unittest.TestCase):
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

def test_get_conf(self):
config = ConfFile(path=os.path.join(self.BASE_DIR, "config-tests-cache.yml"))
config.set_path(os.path.join(self.BASE_DIR, "config-tests-cache2.yml"))
self.assertEqual(config.pyms.config.my_cache, 1234)

def test_wrong_block_no_pyms(self):
with self.assertRaises(ConfigErrorException):
validate_conf(path=os.path.join(self.BASE_DIR, "config-tests-bad-structure.yml"))

def test_wrong_block_no_config(self):
with self.assertRaises(ConfigErrorException):
validate_conf(path=os.path.join(self.BASE_DIR, "config-tests-bad-structure2.yml"))

def test_wrong_block_not_valid_structure(self):
with self.assertRaises(ConfigErrorException):
validate_conf(path=os.path.join(self.BASE_DIR, "config-tests-bad-structure3.yml"))