Skip to content

Commit

Permalink
Feature bundled specs command (#116)
Browse files Browse the repository at this point in the history
* use prance library to get bundled OAS

Co-authored-by: Fernando Arruza <[email protected]>
  • Loading branch information
f-arruza and Fernando Arruza authored Apr 23, 2020
1 parent 0e3ab07 commit 1da70fd
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 24 deletions.
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)
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: "[email protected]"
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: "[email protected]"
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'), "[email protected]")

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 comments on commit 1da70fd

Please sign in to comment.