Skip to content

Commit

Permalink
Merge pull request #128 from lofidevops/openapi
Browse files Browse the repository at this point in the history
Export title and version to openapi.yaml
  • Loading branch information
lofidevops authored Sep 8, 2022
2 parents 6515a36 + 6755c08 commit b985359
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 106 deletions.
13 changes: 11 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,13 @@ Acceptable will generate a JSON schema representation of the form for documentat
To generate API metadata, you should add 'acceptable' to INSTALLED_APPS. This
will provide an 'acceptable' management command::


./manage.py acceptable metadata > api.json # generate metadata

And also::

./manage.py acceptable api-version api.json # inspect the current version



Documentation (beta)
--------------------

Expand All @@ -109,6 +107,7 @@ This markdown is designed to rendered to html by

documentation-builder --base-directory docs


Includable Makefile
-------------------

Expand Down Expand Up @@ -150,3 +149,13 @@ Development
-----------

``make test`` and ``make tox`` should run without errors.

To run a single test module invoke::

python setup.py test --test-suite acceptable.tests.test_module

or::

tox -epy38 -- --test-suite acceptable.tests.test_module

...the latter runs "test_module" against Python 3.8 only.
51 changes: 33 additions & 18 deletions acceptable/openapi.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,52 @@
"""
Helpers to translate acceptable metadata to OpenAPI specifications (OAS).
"""

from dataclasses import dataclass, field
from typing import Any

import yaml

from acceptable._service import APIMetadata

def _to_dict(source: Any):

def _to_dict(source: Any):
if hasattr(source, "_to_dict"):
return source._to_dict() # noqa
elif type(source) == dict:
source_dict = {}
for key, value in source.items():
source_dict[key] = _to_dict(value)
return source_dict
return {key: _to_dict(value) for key, value in source.items()}
elif type(source) == list:
source_list = []
for item in source:
source_list.append(_to_dict(item))
return source_list
return [_to_dict(value) for value in source]
elif hasattr(source, "__dict__"):
return {key: _to_dict(value) for key, value in source.__dict__.items()}
else:
return source


def dump(metadata, stream):
@dataclass
class OasInfo(object):
description: str = ""
version: str = ""
title: str = ""
tags: list = field(default_factory=lambda: [])
contact: dict = field(default_factory=lambda: {"name": "", "email": ""})


@dataclass
class OasRoot31(object):
openapi: str = "3.1.0"
info: OasInfo = OasInfo()
servers: dict = field(default_factory=lambda: {})
paths: dict = field(default_factory=lambda: {})
components_schemas: dict = field(default_factory=lambda: {})


def dump(metadata: APIMetadata, stream=None):
service_name = None
if len(metadata.services) == 1:
service_name = list(metadata.services.keys())[0]

# TODO: parse metadata as OpenAPI specification
oas = OasRoot31()
oas.info.title = service_name or ""
oas.info.version = metadata.current_version or ""

return yaml.safe_dump(
_to_dict(None),
stream,
default_flow_style=False,
encoding=None,
)
return yaml.safe_dump(_to_dict(oas), stream, default_flow_style=False, encoding=None)
131 changes: 48 additions & 83 deletions acceptable/tests/test_main.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# Copyright 2017 Canonical Ltd. This software is licensed under the
# GNU Lesser General Public License version 3 (see the file LICENSE).
import argparse
from collections import OrderedDict
import contextlib
from functools import partial
import io
import json
import os
import subprocess
import sys
import tempfile
import yaml
from collections import OrderedDict
from functools import partial

import testtools
import fixtures
import testtools
import yaml

from acceptable import __main__ as main
from acceptable import get_metadata
Expand Down Expand Up @@ -148,7 +148,7 @@ def my_view():
'request_schema': {'type': 'object'},
'response_schema': {'type': 'object'},
'params_schema': {'type': 'object'},
'introduced_at': 1,
'introduced_at': 1,
'title': 'Root',
}
}
Expand Down Expand Up @@ -234,7 +234,7 @@ def my_view():
'request_schema': {'type': 'object'},
'response_schema': {'type': 'object'},
'params_schema': {'type': 'object', 'properties': {'test': {'type': 'string'}}},
'introduced_at': 1,
'introduced_at': 1,
'title': 'Root',
}
}
Expand Down Expand Up @@ -299,7 +299,7 @@ def metadata(self):
},
'request_schema': {'request_schema': 1},
'response_schema': {'response_schema': 2},
'introduced_at': 1,
'introduced_at': 1,
'title': 'Api1',
}
return metadata
Expand Down Expand Up @@ -349,7 +349,7 @@ def metadata(self):
},
'request_schema': {'request_schema': 1},
'response_schema': {'response_schema': 2},
'introduced_at': 1,
'introduced_at': 1,
'title': 'Api1',
}
metadata['group']['apis']['api2'] = {
Expand All @@ -363,7 +363,7 @@ def metadata(self):
},
'request_schema': {'request_schema': 1},
'response_schema': {'response_schema': 2},
'introduced_at': 1,
'introduced_at': 1,
'title': 'Api2',
}
return metadata
Expand All @@ -376,14 +376,7 @@ def test_render_markdown_success(self):
iterator = main.render_markdown(self.metadata(), args)
output = OrderedDict((str(k), v) for k, v in iterator)

self.assertEqual(set([
'en/group.md',
'en/index.md',
'en/metadata.yaml',
'metadata.yaml',
]),
set(output),
)
self.assertEqual({'en/group.md', 'en/index.md', 'en/metadata.yaml', 'metadata.yaml'}, set(output))

top_level_md = yaml.safe_load(output['metadata.yaml'])
self.assertEqual(
Expand All @@ -393,11 +386,11 @@ def test_render_markdown_success(self):

md = yaml.safe_load(output['en/metadata.yaml'])
self.assertEqual({
'navigation': [
{'location': 'index.md', 'title': 'Index'},
{'location': 'group.md', 'title': 'Group'},
],
},
'navigation': [
{'location': 'index.md', 'title': 'Index'},
{'location': 'group.md', 'title': 'Group'},
],
},
md
)

Expand All @@ -410,24 +403,17 @@ def test_render_markdown_undocumented(self):
iterator = main.render_markdown(m, args)
output = OrderedDict((str(k), v) for k, v in iterator)

self.assertEqual(set([
'en/group.md',
'en/index.md',
'en/metadata.yaml',
'metadata.yaml',
]),
set(output),
)
self.assertEqual({'en/group.md', 'en/index.md', 'en/metadata.yaml', 'metadata.yaml'}, set(output))

self.assertNotIn('api2', output['en/group.md'])

md = yaml.safe_load(output['en/metadata.yaml'])
self.assertEqual({
'navigation': [
{'location': 'index.md', 'title': 'Index'},
{'location': 'group.md', 'title': 'Group'},
],
},
'navigation': [
{'location': 'index.md', 'title': 'Index'},
{'location': 'group.md', 'title': 'Group'},
],
},
md
)

Expand All @@ -440,22 +426,15 @@ def test_render_markdown_deprecated_at(self):
iterator = main.render_markdown(m, args)
output = OrderedDict((str(k), v) for k, v in iterator)

self.assertEqual(set([
'en/group.md',
'en/index.md',
'en/metadata.yaml',
'metadata.yaml',
]),
set(output),
)
self.assertEqual({'en/group.md', 'en/index.md', 'en/metadata.yaml', 'metadata.yaml'}, set(output))

md = yaml.safe_load(output['en/metadata.yaml'])
self.assertEqual({
'navigation': [
{'location': 'index.md', 'title': 'Index'},
{'location': 'group.md', 'title': 'Group'},
],
},
'navigation': [
{'location': 'index.md', 'title': 'Index'},
{'location': 'group.md', 'title': 'Group'},
],
},
md
)

Expand All @@ -472,15 +451,8 @@ def test_render_markdown_multiple_groups(self):
iterator = main.render_markdown(metadata, args)
output = OrderedDict((str(k), v) for k, v in iterator)

self.assertEqual(set([
'en/group.md',
'en/group2.md',
'en/index.md',
'en/metadata.yaml',
'metadata.yaml',
]),
set(output),
)
self.assertEqual({'en/group.md', 'en/group2.md', 'en/index.md', 'en/metadata.yaml', 'metadata.yaml'},
set(output))

top_level_md = yaml.safe_load(output['metadata.yaml'])
self.assertEqual(
Expand All @@ -490,12 +462,12 @@ def test_render_markdown_multiple_groups(self):

md = yaml.safe_load(output['en/metadata.yaml'])
self.assertEqual({
'navigation': [
{'location': 'index.md', 'title': 'Index'},
{'location': 'group.md', 'title': 'Group'},
{'location': 'group2.md', 'title': 'Group2'},
],
},
'navigation': [
{'location': 'index.md', 'title': 'Index'},
{'location': 'group.md', 'title': 'Group'},
{'location': 'group2.md', 'title': 'Group2'},
],
},
md
)

Expand All @@ -513,14 +485,7 @@ def test_render_markdown_group_omitted_with_undocumented(self):
iterator = main.render_markdown(metadata, args)
output = OrderedDict((str(k), v) for k, v in iterator)

self.assertEqual(set([
'en/group.md',
'en/index.md',
'en/metadata.yaml',
'metadata.yaml',
]),
set(output),
)
self.assertEqual({'en/group.md', 'en/index.md', 'en/metadata.yaml', 'metadata.yaml'}, set(output))

top_level_md = yaml.safe_load(output['metadata.yaml'])
self.assertEqual(
Expand All @@ -530,11 +495,11 @@ def test_render_markdown_group_omitted_with_undocumented(self):

md = yaml.safe_load(output['en/metadata.yaml'])
self.assertEqual({
'navigation': [
{'location': 'index.md', 'title': 'Index'},
{'location': 'group.md', 'title': 'Group'},
],
},
'navigation': [
{'location': 'index.md', 'title': 'Index'},
{'location': 'group.md', 'title': 'Group'},
],
},
md
)

Expand Down Expand Up @@ -612,11 +577,6 @@ def test_render_cmd_with_documentation_builder(self):
' Documentation: API foo at response_schema.foo_result.introduced_at'),
]

EXPECTED_OPENAPI_RESULT = [
"null\n",
"...\n",
]


class LintTests(testtools.TestCase):

Expand Down Expand Up @@ -654,8 +614,13 @@ def test_openapi_output(self):
self.assertEqual([], output.getvalue().splitlines())

# And the OpenAPI file contains the expected value
with open("examples/oas_testcase_openapi.yaml", "r") as f:
self.assertListEqual(EXPECTED_OPENAPI_RESULT, f.readlines())
with open("examples/oas_testcase_openapi.yaml", "r") as _result:
result = _result.readlines()

with open("examples/oas_expected.yaml", "r") as _expected:
expected = _expected.readlines()

self.assertListEqual(expected, result)

# And we implicitly assume the files have not changed

Expand Down
Loading

0 comments on commit b985359

Please sign in to comment.