First implementation of the Graffiti service
This is just to get something into github Change-Id: I7d5d63e1d67ab89ed31831bba0fee34929413592
This commit is contained in:
parent
fed799bd04
commit
f68589b832
|
@ -0,0 +1 @@
|
|||
recursive-include public *
|
|
@ -0,0 +1,52 @@
|
|||
# Server Specific Configurations
|
||||
server = {
|
||||
'port': '21075',
|
||||
'host': '0.0.0.0'
|
||||
}
|
||||
|
||||
# Pecan Application Configurations
|
||||
app = {
|
||||
'root': 'graffiti.controllers.root.RootController',
|
||||
'modules': ['graffiti'],
|
||||
'static_root': '%(confdir)s/public',
|
||||
'template_path': '%(confdir)s/graffiti/templates',
|
||||
'debug': True,
|
||||
'errors': {
|
||||
404: '/error/404',
|
||||
'__force_dict__': True
|
||||
}
|
||||
}
|
||||
|
||||
logging = {
|
||||
'loggers': {
|
||||
'root': {'level': 'DEBUG', 'handlers': ['console']},
|
||||
'graffiti': {'level': 'DEBUG', 'handlers': ['console']},
|
||||
'wsme.api': {'level': 'DEBUG', 'handlers': ['console']},
|
||||
'py.warnings': {'handlers': ['console']},
|
||||
'__force_dict__': True
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'simple'
|
||||
}
|
||||
},
|
||||
'formatters': {
|
||||
'simple': {
|
||||
'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]'
|
||||
'[%(threadName)s] %(message)s')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wsme = {
|
||||
'debug': True
|
||||
}
|
||||
|
||||
# Custom Configurations must be in Python dictionary format::
|
||||
#
|
||||
# foo = {'bar':'baz'}
|
||||
#
|
||||
# All configurations are accessible at::
|
||||
# pecan.conf
|
Binary file not shown.
|
@ -0,0 +1,26 @@
|
|||
from pecan import make_app
|
||||
|
||||
from graffiti import model
|
||||
from graffiti.service import prepare_service
|
||||
|
||||
from graffiti.hooks import CorsHook
|
||||
|
||||
from oslo.config import cfg
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def setup_app(config):
|
||||
|
||||
model.init_model()
|
||||
app_conf = dict(config.app)
|
||||
|
||||
prepare_service()
|
||||
|
||||
app_hooks = [CorsHook()]
|
||||
|
||||
return make_app(
|
||||
app_conf.pop('root'),
|
||||
logging=getattr(config, 'logging', {}),
|
||||
hooks=app_hooks,
|
||||
**app_conf
|
||||
)
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,22 @@
|
|||
from pecan import expose
|
||||
from webob.exc import status_map
|
||||
|
||||
from graffiti.controllers.versions import V1Controller
|
||||
|
||||
|
||||
class RootController(object):
|
||||
|
||||
v1 = V1Controller()
|
||||
|
||||
@expose(generic=True, template='index.html')
|
||||
def index(self):
|
||||
return dict()
|
||||
|
||||
@expose('error.html')
|
||||
def error(self, status):
|
||||
try:
|
||||
status = int(status)
|
||||
except ValueError: # pragma: no cover
|
||||
status = 500
|
||||
message = getattr(status_map.get(status), 'explanation', '')
|
||||
return dict(status=status, message=message)
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,41 @@
|
|||
from pecan.rest import RestController
|
||||
|
||||
from wsme.api import Response
|
||||
from wsmeext.pecan import wsexpose
|
||||
|
||||
from graffiti.model.v1.resource import Resource
|
||||
|
||||
resources = []
|
||||
|
||||
class ResourceController(RestController):
|
||||
def __init__(self):
|
||||
super(ResourceController, self).__init__()
|
||||
|
||||
self.status = 200
|
||||
|
||||
@wsexpose()
|
||||
def options():
|
||||
pass
|
||||
|
||||
@wsexpose(Resource, unicode)
|
||||
def get_one(self, id):
|
||||
global resources
|
||||
|
||||
for res in resources:
|
||||
if res.id.lower() == id.lower():
|
||||
return res
|
||||
|
||||
res = Response(Resource(), status_code=404, error="You Suck")
|
||||
return res
|
||||
|
||||
@wsexpose([Resource])
|
||||
def get_all(self):
|
||||
global resources
|
||||
|
||||
return resources
|
||||
|
||||
@wsexpose(Resource, Resource)
|
||||
def post(self, resource):
|
||||
global resources
|
||||
|
||||
resources.append(resource)
|
Binary file not shown.
|
@ -0,0 +1,5 @@
|
|||
from graffiti.controllers.v1.resource import ResourceController
|
||||
|
||||
class V1Controller(object):
|
||||
|
||||
resource = ResourceController()
|
Binary file not shown.
|
@ -0,0 +1,16 @@
|
|||
import json
|
||||
|
||||
from pecan.hooks import PecanHook
|
||||
|
||||
class CorsHook(PecanHook):
|
||||
def after(self, state):
|
||||
state.response.headers['Access-Control-Allow-Origin'] = '*'
|
||||
state.response.headers['Access-Control-Allow-Methods'] = 'GET, PUT, POST, DELETE, OPTIONS'
|
||||
state.response.headers['Access-Control-Allow-Headers'] = 'origin, authorization, accept, content-type'
|
||||
if not state.response.headers['Content-Length']:
|
||||
state.response.headers['Content-Length'] = str(len(state.response.body))
|
||||
|
||||
if state.response.headers['Content-Type'].find('json') != -1:
|
||||
# Sort the Response Body's JSON
|
||||
json_str = json.loads(state.response.body)
|
||||
state.response.body = json.dumps(json_str, sort_keys=True)
|
Binary file not shown.
|
@ -0,0 +1,15 @@
|
|||
from pecan import conf # noqa
|
||||
|
||||
|
||||
def init_model():
|
||||
"""
|
||||
This is a stub method which is called at application startup time.
|
||||
|
||||
If you need to bind to a parse database configuration, set up tables or
|
||||
ORM classes, or perform any database initialization, this is the
|
||||
recommended place to do it.
|
||||
|
||||
For more information working with databases, and some common recipes,
|
||||
see http://pecan.readthedocs.org/en/latest/databases.html
|
||||
"""
|
||||
pass
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,15 @@
|
|||
import wsme
|
||||
from wsme import types
|
||||
|
||||
from graffiti.model.v1.property import Property
|
||||
|
||||
class Capability(types.Base):
|
||||
properties = wsme.wsattr([Property], mandatory=True)
|
||||
capability_type = wsme.wsattr(types.text, mandatory=True)
|
||||
capability_type_namespace = wsme.wsattr(types.text, mandatory=True)
|
||||
|
||||
_wsme_attr_order = ('properties', 'capability_type',
|
||||
'capability_type_namespace')
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Capability, self).__init__(**kwargs)
|
Binary file not shown.
|
@ -0,0 +1,11 @@
|
|||
import wsme
|
||||
from wsme import types
|
||||
|
||||
class Property(types.Base):
|
||||
name = wsme.wsattr(types.text, mandatory=True)
|
||||
value = wsme.wsattr(types.text, mandatory=True)
|
||||
|
||||
_wsme_attr_order = ('name', 'value')
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Property, self).__init__(**kwargs)
|
Binary file not shown.
|
@ -0,0 +1,10 @@
|
|||
import wsme
|
||||
from wsme import types
|
||||
|
||||
class Provider(types.Base):
|
||||
id = wsme.wsattr(types.text, mandatory=True)
|
||||
|
||||
_wsme_attr_order = ('id',)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Provider, self).__init__(**kwargs)
|
Binary file not shown.
|
@ -0,0 +1,13 @@
|
|||
import wsme
|
||||
from wsme import types
|
||||
|
||||
class Requirement(types.Base):
|
||||
criterion = wsme.wsattr(types.text, mandatory=True)
|
||||
capability_type = wsme.wsattr(types.text, mandatory=True)
|
||||
capability_type_namespace = wsme.wsattr(types.text, mandatory=True)
|
||||
|
||||
_wsme_attr_order = ('criterion', 'capability_type',
|
||||
'capability_type_namespace')
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Requirement, self).__init__(**kwargs)
|
Binary file not shown.
|
@ -0,0 +1,25 @@
|
|||
import wsme
|
||||
from wsme import types
|
||||
|
||||
from graffiti.model.v1.capability import Capability
|
||||
from graffiti.model.v1.property import Property
|
||||
from graffiti.model.v1.provider import Provider
|
||||
from graffiti.model.v1.requirement import Requirement
|
||||
|
||||
|
||||
class Resource(types.Base):
|
||||
id = wsme.wsattr(types.text, mandatory=True)
|
||||
type = wsme.wsattr(types.text, mandatory=True)
|
||||
name = wsme.wsattr(types.text, mandatory=True)
|
||||
description = wsme.wsattr(types.text, mandatory=False)
|
||||
provider = wsme.wsattr(Provider, mandatory=True)
|
||||
properties = wsme.wsattr([Property], mandatory=False)
|
||||
capabilities = wsme.wsattr([Capability], mandatory=True)
|
||||
requirements = wsme.wsattr([Requirement], mandatory=True)
|
||||
|
||||
_wsme_attr_order = ('id', 'name', 'description', 'type',
|
||||
'provider', 'properties', 'capabilities',
|
||||
'requirements')
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Resource, self).__init__(**kwargs)
|
Binary file not shown.
|
@ -0,0 +1,9 @@
|
|||
import sys
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
def prepare_service(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
|
||||
cfg.CONF(argv[3:], project='graffiti')
|
Binary file not shown.
|
@ -0,0 +1,12 @@
|
|||
<%inherit file="layout.html" />
|
||||
|
||||
## provide definitions for blocks we want to redefine
|
||||
<%def name="title()">
|
||||
Server Error ${status}
|
||||
</%def>
|
||||
|
||||
## now define the body of the template
|
||||
<header>
|
||||
<h1>Server Error ${status}</h1>
|
||||
</header>
|
||||
<p>${message}</p>
|
|
@ -0,0 +1,34 @@
|
|||
<%inherit file="layout.html" />
|
||||
|
||||
## provide definitions for blocks we want to redefine
|
||||
<%def name="title()">
|
||||
Welcome to Pecan!
|
||||
</%def>
|
||||
|
||||
## now define the body of the template
|
||||
<header>
|
||||
<h1><img src="/images/logo.png" /></h1>
|
||||
</header>
|
||||
|
||||
<div id="content">
|
||||
|
||||
<p>This is a sample Pecan project.</p>
|
||||
|
||||
<p>
|
||||
Instructions for getting started can be found online at <a
|
||||
href="http://pecanpy.org" target="window">pecanpy.org</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
...or you can search the documentation here:
|
||||
</p>
|
||||
|
||||
<form method="POST" action="/">
|
||||
<fieldset>
|
||||
<input name="q" />
|
||||
<input type="submit" value="Search" />
|
||||
<fieldset>
|
||||
<small>Enter search terms or a module, class or function name.</small>
|
||||
</form>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,22 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>${self.title()}</title>
|
||||
${self.style()}
|
||||
${self.javascript()}
|
||||
</head>
|
||||
<body>
|
||||
${self.body()}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<%def name="title()">
|
||||
Default Title
|
||||
</%def>
|
||||
|
||||
<%def name="style()">
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="/css/style.css" />
|
||||
</%def>
|
||||
|
||||
<%def name="javascript()">
|
||||
<script language="text/javascript" src="/javascript/shared.js"></script>
|
||||
</%def>
|
|
@ -0,0 +1,22 @@
|
|||
import os
|
||||
from unittest import TestCase
|
||||
from pecan import set_config
|
||||
from pecan.testing import load_test_app
|
||||
|
||||
__all__ = ['FunctionalTest']
|
||||
|
||||
|
||||
class FunctionalTest(TestCase):
|
||||
"""
|
||||
Used for functional tests where you need to test your
|
||||
literal application and its integration with the framework.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.app = load_test_app(os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'config.py'
|
||||
))
|
||||
|
||||
def tearDown(self):
|
||||
set_config({}, overwrite=True)
|
|
@ -0,0 +1,25 @@
|
|||
# Server Specific Configurations
|
||||
server = {
|
||||
'port': '8080',
|
||||
'host': '0.0.0.0'
|
||||
}
|
||||
|
||||
# Pecan Application Configurations
|
||||
app = {
|
||||
'root': 'graffiti.controllers.root.RootController',
|
||||
'modules': ['graffiti'],
|
||||
'static_root': '%(confdir)s/../../public',
|
||||
'template_path': '%(confdir)s/../templates',
|
||||
'debug': True,
|
||||
'errors': {
|
||||
'404': '/error/404',
|
||||
'__force_dict__': True
|
||||
}
|
||||
}
|
||||
|
||||
# Custom Configurations must be in Python dictionary format::
|
||||
#
|
||||
# foo = {'bar':'baz'}
|
||||
#
|
||||
# All configurations are accessible at::
|
||||
# pecan.conf
|
|
@ -0,0 +1,22 @@
|
|||
from unittest import TestCase
|
||||
from webtest import TestApp
|
||||
from graffiti.tests import FunctionalTest
|
||||
|
||||
|
||||
class TestRootController(FunctionalTest):
|
||||
|
||||
def test_get(self):
|
||||
response = self.app.get('/')
|
||||
assert response.status_int == 200
|
||||
|
||||
def test_search(self):
|
||||
response = self.app.post('/', params={'q': 'RestController'})
|
||||
assert response.status_int == 302
|
||||
assert response.headers['Location'] == (
|
||||
'http://pecan.readthedocs.org/en/latest/search.html'
|
||||
'?q=RestController'
|
||||
)
|
||||
|
||||
def test_get_not_found(self):
|
||||
response = self.app.get('/a/bogus/url', expect_errors=True)
|
||||
assert response.status_int == 404
|
|
@ -0,0 +1,7 @@
|
|||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestUnits(TestCase):
|
||||
|
||||
def test_units(self):
|
||||
assert 5 * 5 == 25
|
Binary file not shown.
|
@ -0,0 +1,43 @@
|
|||
body {
|
||||
background: #311F00;
|
||||
color: white;
|
||||
font-family: 'Helvetica Neue', 'Helvetica', 'Verdana', sans-serif;
|
||||
padding: 1em 2em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #FAFF78;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div#content {
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
input.error {
|
||||
background: #FAFF78;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Futura-CondensedExtraBold', 'Futura', 'Helvetica', sans-serif;
|
||||
text-transform: uppercase;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
|
@ -0,0 +1,6 @@
|
|||
[nosetests]
|
||||
match=^test
|
||||
where=graffiti
|
||||
nocapture=1
|
||||
cover-package=graffiti
|
||||
cover-erase=1
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
try:
|
||||
from setuptools import setup, find_packages
|
||||
except ImportError:
|
||||
from ez_setup import use_setuptools
|
||||
use_setuptools()
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='graffiti',
|
||||
version='0.1',
|
||||
description='',
|
||||
author='',
|
||||
author_email='',
|
||||
install_requires=[
|
||||
"pecan",
|
||||
],
|
||||
test_suite='graffiti',
|
||||
zip_safe=False,
|
||||
include_package_data=True,
|
||||
packages=find_packages(exclude=['ez_setup'])
|
||||
)
|
Loading…
Reference in New Issue