Skip to content

Commit d4e8683

Browse files
himanshus-devHimanshu Sharma
and
Himanshu Sharma
authored
Added command create-config (#169) (#204)
* feature: Added command functionality * test: Added tests for command and helper functions * feature: Added methods to dump, remove config file * fix: Updated max-attributes in pylintrc NOTE: The command by default, doesn't create/overwrite if a config file already exists. Co-authored-by: Himanshu Sharma <[email protected]>
1 parent 970e735 commit d4e8683

File tree

7 files changed

+135
-8
lines changed

7 files changed

+135
-8
lines changed

pylintrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ valid-metaclass-classmethod-first-arg=mcs
400400
max-args=7
401401

402402
# Maximum number of attributes for a class (see R0902).
403-
max-attributes=7
403+
max-attributes=8
404404

405405
# Maximum number of boolean expressions in a if statement
406406
max-bool-expr=5

pyms/cmd/main.py

+32
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
import argparse
66
import os
77
import sys
8+
from distutils.util import strtobool
89

910
from pyms.crypt.fernet import Crypt
1011
from pyms.flask.services.swagger import merge_swagger_file
1112
from pyms.utils import check_package_exists, import_from, utils
13+
from pyms.config import create_conf_file
1214

1315

1416
class Command:
@@ -18,6 +20,7 @@ class Command:
1820

1921
args = []
2022

23+
# flake8: noqa: C901
2124
def __init__(self, *args, **kwargs):
2225
arguments = kwargs.get("arguments", False)
2326
autorun = kwargs.get("autorun", True)
@@ -53,6 +56,9 @@ def __init__(self, *args, **kwargs):
5356
"-f", "--file", default=os.path.join('project', 'swagger', 'swagger.yaml'),
5457
help='Swagger file path')
5558

59+
parser_create_config = commands.add_parser('create-config', help='Generate a config file')
60+
parser_create_config.add_argument("create_config", action='store_true', help='Generate a config file')
61+
5662
parser.add_argument("-v", "--verbose", default="", type=str, help="Verbose ")
5763

5864
args = parser.parse_args(arguments)
@@ -74,6 +80,10 @@ def __init__(self, *args, **kwargs):
7480
self.file = args.file
7581
except AttributeError:
7682
self.merge_swagger = False
83+
try:
84+
self.create_config = args.create_config
85+
except Exception:
86+
self.create_config = False
7787
self.verbose = len(args.verbose)
7888
if autorun: # pragma: no cover
7989
result = self.run()
@@ -91,6 +101,8 @@ def run(self):
91101
if self.create_key:
92102
path = crypt._loader.get_path_from_env() # pylint: disable=protected-access
93103
pwd = self.get_input('Type a password to generate the key file: ')
104+
# Should use yes_no_input insted of get input below
105+
# the result should be validated for Yes (Y|y) rather allowing anything other than 'n'
94106
generate_file = self.get_input('Do you want to generate a file in {}? [Y/n]'.format(path))
95107
generate_file = generate_file.lower() != "n"
96108
key = crypt.generate_key(pwd, generate_file)
@@ -99,6 +111,8 @@ def run(self):
99111
else:
100112
self.print_ok("Key generated: {}".format(key))
101113
if self.encrypt:
114+
# Spoted Unhandle exceptions - The encrypt function throws FileDoesNotExistException, ValueError
115+
# which are not currently handled
102116
encrypted = crypt.encrypt(self.encrypt)
103117
self.print_ok("Encrypted OK: {}".format(encrypted))
104118
if self.startproject:
@@ -113,8 +127,26 @@ def run(self):
113127
except FileNotFoundError as ex:
114128
self.print_error(ex.__str__())
115129
return False
130+
if self.create_config:
131+
use_requests = self.yes_no_input('Do you want to use request')
132+
use_swagger = self.yes_no_input('Do you want to use swagger')
133+
try:
134+
conf_file_path = create_conf_file(use_requests, use_swagger)
135+
self.print_ok(f'Config file "{conf_file_path}" created')
136+
return True
137+
except Exception as ex:
138+
self.print_error(ex.__str__())
139+
return False
116140
return True
117141

142+
def yes_no_input(self, msg=""): # pragma: no cover
143+
answer = input(utils.colored_text(f'{msg}{"?" if not msg.endswith("?") else ""} [Y/n] :', utils.Colors.BLUE, True)) # nosec
144+
try:
145+
return strtobool(answer)
146+
except ValueError:
147+
self.print_error('Invalid input, Please answer with a "Y" or "n"')
148+
self.yes_no_input(msg)
149+
118150
@staticmethod
119151
def print_ok(msg=""):
120152
print(utils.colored_text(msg, utils.Colors.BRIGHT_GREEN, True))

pyms/config/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .conf import get_conf
21
from .confile import ConfFile
2+
from .conf import get_conf, create_conf_file
33

4-
__all__ = ['get_conf', 'ConfFile']
4+
__all__ = ['get_conf', 'create_conf_file', 'ConfFile']

pyms/config/conf.py

+52-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import logging
21
import os
3-
2+
import logging
3+
from typing import Union
4+
import yaml
5+
from pyms.utils import utils
46
from pyms.config.confile import ConfFile
57
from pyms.constants import PYMS_CONFIG_WHITELIST_KEYWORDS, CONFIGMAP_FILE_ENVIRONMENT_LEGACY, \
6-
CONFIGMAP_FILE_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT_LEGACY, LOGGER_NAME
8+
CONFIGMAP_FILE_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT_LEGACY, LOGGER_NAME, \
9+
DEFAULT_CONFIGMAP_FILENAME
710
from pyms.exceptions import ServiceDoesNotExistException, ConfigErrorException, AttrDoesNotExistException
8-
from pyms.utils import utils
911

1012
logger = logging.getLogger(LOGGER_NAME)
1113

@@ -144,3 +146,49 @@ def __verify_deprecated_env_variables(config):
144146
except AttrDoesNotExistException:
145147
pass
146148
logger.warning(msg)
149+
150+
151+
def create_conf_file(use_requests: bool = False, use_swagger: bool = False) -> Union[Exception, str]:
152+
"""
153+
Creates a configuration file defining
154+
155+
:param use_requests: Do you want to use requests, defaults to False
156+
:type use_requests: bool, optional
157+
:param use_swagger: Do you want to use swagger, defaults to False
158+
:type use_swagger: bool, optional
159+
:raises FileExistsError: Config file already exists
160+
:raises IOError: Config file creation failed.
161+
:return: Raises FileExistsError or IOError OR returns config_file_path
162+
:rtype: Union[Exception, str]
163+
"""
164+
# Try using env value for config file, if not found use default
165+
CONFIG_FILE = os.getenv(CONFIGMAP_FILE_ENVIRONMENT, None)
166+
if not CONFIG_FILE:
167+
CONFIG_FILE = DEFAULT_CONFIGMAP_FILENAME
168+
# Prevent overwriting existing file
169+
if os.path.exists(CONFIG_FILE):
170+
raise FileExistsError("Config file already exists at '{}'".format(os.path.abspath(CONFIG_FILE)))
171+
# Create config dict
172+
config = {"pyms": {}}
173+
# add services
174+
if use_requests:
175+
if not config["pyms"].get("services", None):
176+
config["pyms"]["services"] = {}
177+
config["pyms"]["services"]["requests"] = {"data": ""}
178+
if use_swagger:
179+
if not config["pyms"].get("services", None):
180+
config["pyms"]["services"] = {}
181+
config["pyms"]["services"]["swagger"] = {"path": "", "file": "swagger.yaml"}
182+
# add Basic Flask config
183+
config["pyms"]["config"] = {
184+
"DEBUG": True,
185+
"TESTING": False,
186+
"APP_NAME": "Python Microservice",
187+
"APPLICATION_ROOT": ""
188+
}
189+
try:
190+
with open(CONFIG_FILE, 'w', encoding='utf-8') as config_file:
191+
config_file.write(yaml.dump(config, default_flow_style=False, default_style=None, sort_keys=False))
192+
except Exception as ex:
193+
raise ex
194+
return CONFIG_FILE

tests/common.py

+14
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1+
import os
12
from pyms.flask.app import Microservice
3+
from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT, DEFAULT_CONFIGMAP_FILENAME
24

35
class MyMicroserviceNoSingleton(Microservice):
46
_singleton = False
57

68

79
class MyMicroservice(Microservice):
810
pass
11+
12+
13+
def remove_conf_file():
14+
"""
15+
Remove the YAML config file
16+
"""
17+
CONFIG_FILE = os.getenv(CONFIGMAP_FILE_ENVIRONMENT, None)
18+
if not CONFIG_FILE:
19+
CONFIG_FILE = DEFAULT_CONFIGMAP_FILENAME
20+
# Dlete file, if exists
21+
if os.path.exists(CONFIG_FILE):
22+
os.remove(CONFIG_FILE)

tests/test_cmd.py

+11
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pyms.exceptions import FileDoesNotExistException, PackageNotExists
1212
from pyms.crypt.fernet import Crypt
1313
from pyms.flask.services.swagger import get_bundled_specs
14+
from tests.common import remove_conf_file
1415

1516

1617
class TestCmd(unittest.TestCase):
@@ -75,3 +76,13 @@ def test_merge_swagger_error(self):
7576
cmd = Command(arguments=arguments, autorun=False)
7677
with pytest.raises(ResolutionError) as excinfo:
7778
cmd.run()
79+
80+
@patch('pyms.cmd.main.Command.yes_no_input', return_value=True)
81+
def test_create_config_all(self, input):
82+
# Remove config file if already exists for test
83+
remove_conf_file()
84+
arguments = ["create-config"]
85+
cmd = Command(arguments=arguments, autorun=False)
86+
assert cmd.run()
87+
assert not cmd.run()
88+
remove_conf_file()

tests/test_config.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import unittest
44
from unittest import mock
55

6-
from pyms.config import get_conf, ConfFile
6+
from pyms.config import get_conf, ConfFile, create_conf_file
77
from pyms.config.conf import validate_conf
88
from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT, CONFIGMAP_FILE_ENVIRONMENT_LEGACY, LOGGER_NAME, CONFIG_BASE, \
99
CRYPT_FILE_KEY_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT_LEGACY
@@ -190,3 +190,25 @@ def all_validate_conf_combinations(self, legacy=False):
190190
validate_conf()
191191
os.environ[config_env] = os.path.join(self.BASE_DIR, "config-tests-debug-off.yml")
192192
validate_conf()
193+
194+
195+
class ConfiFileTest(unittest.TestCase):
196+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
197+
198+
def setUp(self):
199+
os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(self.BASE_DIR, "config-file.yml")
200+
201+
def tearDown(self):
202+
del os.environ[CONFIGMAP_FILE_ENVIRONMENT]
203+
204+
def test_create_config(self):
205+
create_conf_file(use_requests=True, use_swagger=True)
206+
self.assertTrue(os.path.exists(os.environ[CONFIGMAP_FILE_ENVIRONMENT]))
207+
validate_conf()
208+
209+
def test_create_config_failure(self):
210+
with self.assertRaises(FileExistsError):
211+
create_conf_file(use_requests=True, use_swagger=True)
212+
# Delete the file
213+
if os.path.exists(os.environ[CONFIGMAP_FILE_ENVIRONMENT]):
214+
os.remove(os.environ[CONFIGMAP_FILE_ENVIRONMENT])

0 commit comments

Comments
 (0)