10000 Feature bundled specs command by f-arruza · Pull Request #116 · python-microservices/pyms · GitHub
[go: up one dir, main page]

Skip to content

Feature bundled specs command #116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ venv
tmp
*.sqlite3


# Test&converage
htmlcov/
coverage.xml
Expand All @@ -18,9 +17,6 @@ py_ms.egg-info/*
pylintReport.txt
.scannerwork/


# Docker

# Deploy
build/
dist/
Expand Down
28 changes: 26 additions & 2 deletions docs/command_line.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ pyms -h
Show you a list of options and help instructions to use this command like:

```bash
usage: main.py [-h] [-v VERBOSE] {encrypt,create-key,startproject} ...
usage: main.py [-h] [-v VERBOSE]
{encrypt,create-key,startproject,merge-swagger} ...

Python Microservices

Expand All @@ -20,11 +21,12 @@ optional arguments:
Commands:
Available commands

{encrypt,create-key,startproject}
{encrypt,create-key,startproject,merge-swagger}
encrypt Encrypt a string
create-key Generate a Key to encrypt strings in config
startproject Generate a project from https://github.com/python-
microservices/microservices-template
merge-swagger Merge swagger into a single file

```

Expand Down Expand Up @@ -67,3 +69,25 @@ pyms encrypt 'mysql+mysqlconnector://important_user:****@localhost/my_schema'
```

See [Encrypt/Decrypt Configuration](encrypt_decryt_configuration.md) for more information

## Merge swagger into a single file

Command:

```bash
pyms merge-swagger [-h] [-f FILE]
```

```bash
optional arguments:
-h, --help show this help message and exit
-f FILE, --file FILE Swagger file path
```

This command uses [prance](https://github.com/jfinkhaeuser/prance) to validate the API specification and generate a single YAML file. It has an optional argument to indicate the main file path of the API specification.

```bash
pyms merge-swagger --file 'app/swagger/swagger.yaml'
>> Swagger file generated [swagger-complete.yaml]
>> OK
```
38 changes: 31 additions & 7 deletions pyms/cmd/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
from __future__ import unicode_literals, print_function

import argparse
import os
import sys

from pyms.utils import check_package_exists, import_from
from pyms.crypt.fernet import Crypt
from pyms.flask.services.swagger import merge_swagger_file
from pyms.utils import check_package_exists, import_from


class Command:
Expand All @@ -33,13 +35,23 @@ def __init__(self, *args, **kwargs):
parser_create_key.add_argument("create_key", action='store_true',
help='Generate a Key to encrypt strings in config')

parser_startproject = commands.add_parser('startproject',
help='Generate a project from https://github.com/python-microservices/microservices-template')
parser_startproject.add_argument("startproject", action='store_true',
help='Generate a project from https://github.com/python-microservices/microservices-template')
parser_startproject = commands.add_parser(
'startproject',
help='Generate a project from https://github.com/python-microservices/microservices-template')
parser_startproject.add_argument(
"startproject", action='store_true',
help='Generate a project from https://github.com/python-microservices/microservices-template')

parser_startproject.add_argument(
"-b", "--branch",
help='Select a branch from https://github.com/python-microservices/microservices-template')

parser_startproject.add_argument("-b", "--branch",
help='Select a branch from https://github.com/python-microservices/microservices-template')
parser_merge_swagger = commands.add_parser('merge-swagger', help='Merge swagger into a single file')
parser_merge_swagger.add_argument("merge_swagger", action='store_true',
help='Merge swagger into a single file')
parser_merge_swagger.add_argument(
"-f", "--file", default=os.path.join('project', 'swagger', 'swagger.yaml'),
help='Swagger file path')

parser.add_argument("-v", "--verbose", default="", type=str, help="Verbose ")

Expand All @@ -57,6 +69,11 @@ def __init__(self, *args, **kwargs):
self.branch = args.branch
except AttributeError:
self.startproject = False
try:
self.merge_swagger = args.merge_swagger
self.file = args.file
except AttributeError:
self.merge_swagger = False
self.verbose = len(args.verbose)
if autorun: # pragma: no cover
result = self.run()
Expand Down Expand Up @@ -89,6 +106,13 @@ def run(self):
cookiecutter = import_from("cookiecutter.main", "cookiecutter")
cookiecutter('gh:python-microservices/cookiecutter-pyms', checkout=self.branch)
self.print_ok("Created project OK")
if self.merge_swagger:
try:
merge_swagger_file(main_file=self.file)
self.print_ok("Swagger file generated [swagger-complete.yaml]")
except FileNotFoundError as ex:
self.print_error(ex.__str__())
return False
return True

@staticmethod
Expand Down
40 changes: 32 additions & 8 deletions pyms/flask/services/swagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

try:
import prance
from prance.util import formats, fs
except ModuleNotFoundError: # pragma: no cover
prance = None

Expand All @@ -20,6 +21,36 @@
PROJECT_DIR = "project"


def get_bundled_specs(main_file: Path) -> Dict[str, Any]:
"""
Get bundled specs
:param main_file: Swagger file path
:return:
"""
parser = prance.ResolvingParser(str(main_file.absolute()),
lazy=True, backend='openapi-spec-validator')
parser.parse()
return parser.specification


def merge_swagger_file(main_file: str):
"""
Generate swagger into a single file
:param main_file: Swagger file path
:return:
"""
input_file = Path(main_file)
output_file = Path(input_file.parent, 'swagger-complete.yaml')

contents = formats.serialize_spec(
specs=get_bundled_specs(input_file),
filename=output_file,
)
fs.write_file(filename=output_file,
contents=contents,
encoding='utf-8')


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
Expand Down Expand Up @@ -47,13 +78,6 @@ def _get_application_root(config):
application_root = "/"
return application_root

@staticmethod
def get_bundled_specs(main_file: Path) -> Dict[str, Any]:
parser = prance.ResolvingParser(str(main_file.absolute()),
lazy=True, backend='openapi-spec-validator')
parser.parse()
return parser.specification

def init_app(self, config, path):
"""
Initialize Connexion App. See more info in [Connexion Github](https://github.com/zalando/connexion)
1E0A Expand Down Expand Up @@ -89,7 +113,7 @@ def init_app(self, config, path):
resolver=RestyResolver(self.project_dir))

params = {
"specification": self.get_bundled_specs(
"specification": get_bundled_specs(
Path(os.path.join(specification_dir, self.file))) if prance else self.file,
"arguments": {'title': config.APP_NAME},
"base_path": application_root,
Expand Down
6 changes: 6 additions & 0 deletions tests/swagger_for_tests/info.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
version: "1.0.0"
author: "API Team"
email: "apiteam@swagger.io"
url: "http://swagger.io"
...
13 changes: 10 additions & 3 deletions tests/swagger_for_tests/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@
swagger: "2.0"
info:
description: "This is a sample server Test server"
version: "1.0.0"
version:
$ref: 'info.yaml#/version'
title: "Swagger Test list"
termsOfService: "http://swagger.io/terms/"
contact:
email: "apiteam@swagger.io"
name:
$ref: 'info.yaml#/author'
url:
$ref: 'info.yaml#/url'
email:
$ref: 'info.yaml#/email'
license:
name: "Apache 2.0"
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
Expand Down Expand Up @@ -45,4 +51,5 @@ paths:
x-swagger-router-controller: "tests.test_flask"
externalDocs:
description: "Find out more about Swagger"
url: "http://swagger.io"
url: "http://swagger.io"
...
19 changes: 19 additions & 0 deletions tests/test_cmd.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Test common rest operations wrapper.
"""
import os
from pathlib import Path
import unittest
from unittest.mock import patch

Expand All @@ -9,6 +10,7 @@
from pyms.cmd import Command
from pyms.exceptions import FileDoesNotExistException, PackageNotExists
from pyms.crypt.fernet import Crypt
from pyms.flask.services.swagger import get_bundled_specs


class TestCmd(unittest.TestCase):
Expand Down Expand Up @@ -55,3 +57,20 @@ def test_startproject_error(self):
with pytest.raises(PackageNotExists) as excinfo:
cmd.run()
assert "cookiecutter is not installed. try with pip install -U cookiecutter" in str(excinfo.value)

def test_get_bundled_specs(self):
specs = get_bundled_specs(Path("tests/swagger_for_tests/swagger.yaml"))
self.assertEqual(specs.get('swagger'), "2.0")
self.assertEqual(specs.get('info').get('version'), "1.0.0")
self.assertEqual(specs.get('info').get('contact').get('email'), "apiteam@swagger.io")

def test_merge_swagger_ok(self):
arguments = ["merge-swagger", "--file", "tests/swagger_for_tests/swagger.yaml", ]
cmd = Command(arguments=arguments, autorun=False)
assert cmd.run()
os.remove("tests/swagger_for_tests/swagger-complete.yaml")

def test_merge_swagger_error(self):
arguments = ["merge-swagger", ]
cmd = Command(arguments=arguments, autorun=False)
assert not cmd.run()
0