Compare commits
20 Commits
Author | SHA1 | Date |
---|---|---|
androiddrew | c7d65e06d2 | 7 years ago |
androiddrew | 3ef86bca1e | 7 years ago |
androiddrew | e67b099e89 | 7 years ago |
androiddrew | ad25ca2bf9 | 7 years ago |
androiddrew | fd6b3a36ce | 7 years ago |
androiddrew | f55b21a6d6 | 7 years ago |
androiddrew | de9ff6adc9 | 7 years ago |
androiddrew | 12bc49100b | 7 years ago |
androiddrew | 442b8acdc5 | 7 years ago |
androiddrew | 101698c348 | 7 years ago |
androiddrew | a2e3ad84c1 | 7 years ago |
androiddrew | b048fe1254 | 7 years ago |
androiddrew | bd818f1cf2 | 7 years ago |
androiddrew | 4f4099d998 | 7 years ago |
androiddrew | e3e4ba7ace | 7 years ago |
androiddrew | 922f8c7d52 | 7 years ago |
androiddrew | d0a9b556c6 | 7 years ago |
androiddrew | 7b23ad97d8 | 7 years ago |
androiddrew | 3ef15e7b48 | 7 years ago |
androiddrew | a52d431e0d | 7 years ago |
@ -0,0 +1,2 @@
|
|||||||
|
recursive-include cookie_api/templates *
|
||||||
|
recursive-include cookie_api/static *
|
@ -0,0 +1,33 @@
|
|||||||
|
# Cookie API
|
||||||
|
|
||||||
|
The Cookie API serves as a simple experimentation application for testing concepts in the APIStar platform. It is intended to help drive the design of the APIStar framework, uncover new design patterns, and apply best practices in application development.
|
||||||
|
|
||||||
|
## Features include:
|
||||||
|
|
||||||
|
* CRUD Interface for a simple domain problem
|
||||||
|
* JWT Authentication
|
||||||
|
* Custom JSON responses
|
||||||
|
* Automated Testing
|
||||||
|
* Logging
|
||||||
|
|
||||||
|
## Features not yet implemented:
|
||||||
|
|
||||||
|
* Template rendering for a Vuejs SPA client (not yet implemented)
|
||||||
|
* Email Confirmation
|
||||||
|
* Password Reset
|
||||||
|
* Authorization model for user
|
||||||
|
* Rate Limiting
|
||||||
|
* Automated deployment
|
||||||
|
* JSON API Responses
|
||||||
|
* Multipart file uploads
|
||||||
|
* Server Side Rendering of Vuejs Client
|
||||||
|
* Async Task Workers
|
||||||
|
* Error monitoring through Sentry
|
||||||
|
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
|
||||||
|
```python
|
||||||
|
pip install -e .[testing]
|
||||||
|
pytest
|
||||||
|
```
|
@ -1,3 +1 @@
|
|||||||
from cookie_api.renders import JSONRenderer
|
from cookie_api.app import application_factory
|
||||||
from cookie_api.app import application_factory
|
|
||||||
from cookie_api.models import Base
|
|
@ -1,16 +0,0 @@
|
|||||||
from apistar import Command
|
|
||||||
from apistar.backends.sqlalchemy_backend import Session
|
|
||||||
|
|
||||||
from cookie_api.models import User
|
|
||||||
|
|
||||||
|
|
||||||
def create_user(session: Session, email, password):
|
|
||||||
user = User(email, password)
|
|
||||||
session.add(user)
|
|
||||||
session.commit()
|
|
||||||
print('User added')
|
|
||||||
|
|
||||||
|
|
||||||
commands = [
|
|
||||||
Command('create_user', create_user)
|
|
||||||
]
|
|
@ -1,25 +0,0 @@
|
|||||||
import datetime as dt
|
|
||||||
import decimal
|
|
||||||
import json
|
|
||||||
|
|
||||||
from apistar import http
|
|
||||||
from apistar.renderers import Renderer
|
|
||||||
|
|
||||||
|
|
||||||
def extended_encoder(obj):
|
|
||||||
"""JSON encoder function with support for ISO 8601 datetime serialization and Decimal to float casting"""
|
|
||||||
if isinstance(obj, dt.datetime):
|
|
||||||
return obj.isoformat()
|
|
||||||
elif isinstance(obj, decimal.Decimal):
|
|
||||||
return float(obj)
|
|
||||||
|
|
||||||
|
|
||||||
class JSONRenderer(Renderer):
|
|
||||||
"""JSON Render with support for ISO 8601 datetime serialization and Decimal to float casting"""
|
|
||||||
media_type = 'application/json'
|
|
||||||
charset = None
|
|
||||||
|
|
||||||
def render(self, data: http.ResponseData) -> bytes:
|
|
||||||
return json.dumps(data, default=extended_encoder).encode('utf-8')
|
|
||||||
|
|
||||||
# TODO add an XML render
|
|
@ -0,0 +1 @@
|
|||||||
|
from .cookies import cookie_routes
|
@ -0,0 +1,57 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
|
import logbook
|
||||||
|
from apistar import http, Route, App
|
||||||
|
from apistar_jwt import authentication_required, JWTUser
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from cookie_api.models import Cookie
|
||||||
|
from cookie_api.schema import CookieSchema
|
||||||
|
from cookie_api.util import MetaJSONResponse
|
||||||
|
|
||||||
|
logger = logbook.Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cookies(session: Session) -> typing.List[CookieSchema]:
|
||||||
|
cookies = session.query(Cookie).all()
|
||||||
|
logger.debug('Cookies collection hit')
|
||||||
|
return MetaJSONResponse([CookieSchema(cookie) for cookie in cookies])
|
||||||
|
|
||||||
|
|
||||||
|
def get_cookie(session: Session, id) -> CookieSchema:
|
||||||
|
cookie = session.query(Cookie).filter_by(id=id).one_or_none()
|
||||||
|
if cookie is None:
|
||||||
|
msg = {"error": "404 Not Found"}
|
||||||
|
return MetaJSONResponse(msg, 404)
|
||||||
|
return MetaJSONResponse(CookieSchema(cookie))
|
||||||
|
|
||||||
|
|
||||||
|
@authentication_required
|
||||||
|
def create_cookie(session: Session, cookie_data: CookieSchema, app: App, user: JWTUser):
|
||||||
|
cookie = Cookie(**cookie_data)
|
||||||
|
session.add(cookie)
|
||||||
|
session.commit()
|
||||||
|
headers = {'Location': app.reverse_url('get_cookie', id=cookie.id)}
|
||||||
|
return MetaJSONResponse(CookieSchema(cookie), 201, headers)
|
||||||
|
|
||||||
|
|
||||||
|
@authentication_required
|
||||||
|
def delete_cookie(session: Session, id: int, user: JWTUser):
|
||||||
|
cookie = session.query(Cookie).filter_by(id=id).one_or_none()
|
||||||
|
if cookie is None:
|
||||||
|
msg = {"error": "404 Not Found"}
|
||||||
|
return MetaJSONResponse(msg, 204)
|
||||||
|
|
||||||
|
logger.debug("Deleting cookie {} {}".format(cookie.id, cookie.name))
|
||||||
|
session.delete(cookie)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
return MetaJSONResponse({}, 204)
|
||||||
|
|
||||||
|
|
||||||
|
cookie_routes = [
|
||||||
|
Route('/cookies', 'GET', get_cookies),
|
||||||
|
Route('/cookies', 'POST', create_cookie),
|
||||||
|
Route('/cookies/{id}', 'GET', get_cookie),
|
||||||
|
Route('/cookies/{id}', 'DELETE', delete_cookie)
|
||||||
|
]
|
@ -1,12 +1,27 @@
|
|||||||
from marshmallow import Schema, fields
|
from apistar import types, validators
|
||||||
|
from cookie_api.util import Decimal
|
||||||
|
|
||||||
class CookieSchema(Schema):
|
|
||||||
id = fields.Int()
|
class CookieSchema(types.Type):
|
||||||
created_date = fields.DateTime()
|
id = validators.Integer(allow_null=True)
|
||||||
modified_date = fields.DateTime()
|
created_date = validators.DateTime(allow_null=True)
|
||||||
name = fields.Str(required=True)
|
modified_date = validators.DateTime(allow_null=True)
|
||||||
recipe_url = fields.Str()
|
name = validators.String(max_length=50)
|
||||||
sku = fields.Str(required=True)
|
recipe_url = validators.String(max_length=255)
|
||||||
qoh = fields.Int(required=True)
|
sku = validators.String(max_length=55)
|
||||||
unit_cost = fields.Decimal(required=True)
|
qoh = validators.Integer(minimum=0)
|
||||||
|
unit_cost = Decimal(minimum=0.0)
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreateSchema(types.Type):
|
||||||
|
email = validators.String(max_length=255)
|
||||||
|
password = validators.String(max_length=255)
|
||||||
|
|
||||||
|
|
||||||
|
class UserExportSchema(types.Type):
|
||||||
|
id = validators.Integer()
|
||||||
|
created_date = validators.DateTime(allow_null=True)
|
||||||
|
modified_date = validators.DateTime(allow_null=True)
|
||||||
|
email = validators.String()
|
||||||
|
admin = validators.Boolean()
|
||||||
|
confirmed = validators.Boolean()
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Cookie API</title>
|
||||||
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>APIStar Cookies</h1>
|
||||||
|
<div id="app">
|
||||||
|
<ul>
|
||||||
|
<li v-for="cookie in cookies">
|
||||||
|
{{ cookie.name }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let vm = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
cookies: []
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
axios.get("http://localhost:5000/cookies").then(
|
||||||
|
(response) => {
|
||||||
|
this.cookies = response.data.data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,310 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
|
||||||
|
</title>
|
||||||
|
<!--[if !mso]><!-- -->
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<!--<![endif]-->
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<style type="text/css">
|
||||||
|
#outlook a {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReadMsgBody {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ExternalClass {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ExternalClass * {
|
||||||
|
line-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table,
|
||||||
|
td {
|
||||||
|
border-collapse: collapse;
|
||||||
|
mso-table-lspace: 0pt;
|
||||||
|
mso-table-rspace: 0pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border: 0;
|
||||||
|
height: auto;
|
||||||
|
line-height: 100%;
|
||||||
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
|
-ms-interpolation-mode: bicubic;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
display: block;
|
||||||
|
margin: 13px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<style type="text/css">
|
||||||
|
@media only screen and (max-width:480px) {
|
||||||
|
@-ms-viewport {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
@viewport {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!--<![endif]-->
|
||||||
|
<!--[if mso]>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:AllowPNG/>
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if lte mso 11]>
|
||||||
|
<style type="text/css">
|
||||||
|
.outlook-group-fix { width:100% !important; }
|
||||||
|
</style>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css">
|
||||||
|
<style type="text/css">
|
||||||
|
@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
|
||||||
|
</style>
|
||||||
|
<!--<![endif]-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
@media only screen and (min-width:480px) {
|
||||||
|
.mj-column-per-100 {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table
|
||||||
|
align="center" border="0" cellpadding="0" cellspacing="0" style="width:600px;" width="600"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
|
||||||
|
<div style="Margin:0px auto;max-width:600px;">
|
||||||
|
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;vertical-align:top;">
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td
|
||||||
|
style="vertical-align:top;width:600px;"
|
||||||
|
>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<div class="mj-column-per-100 outlook-group-fix" style="font-size:13px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||||
|
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
|
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width:100px;">
|
||||||
|
|
||||||
|
<img height="auto" src="{{ logog_url }}" style="border:0;display:block;outline:none;text-decoration:none;width:100%;" width="100" />
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
|
|
||||||
|
<div style="font-family:helvetica;font-size:32px;line-height:1;text-align:center;color:#01B48F;">
|
||||||
|
Cookie API
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
|
|
||||||
|
<p style="border-top:solid 4px #01B48F;font-size:1;margin:0px auto;width:100%;">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table
|
||||||
|
align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 4px #01B48F;font-size:1;margin:0px auto;width:550px;" role="presentation" width="550px"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td style="height:0;line-height:0;">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table
|
||||||
|
align="center" border="0" cellpadding="0" cellspacing="0" style="width:600px;" width="600"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
|
||||||
|
<div style="Margin:0px auto;max-width:600px;">
|
||||||
|
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;vertical-align:top;">
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td
|
||||||
|
style="vertical-align:top;width:600px;"
|
||||||
|
>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<div class="mj-column-per-100 outlook-group-fix" style="font-size:13px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||||
|
|
||||||
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
|
|
||||||
|
<div style="font-family:helvetica;font-size:16px;line-height:1;text-align:center;color:#000000;">
|
||||||
|
Welcome to the Cookie API.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
|
|
||||||
|
<div style="font-family:helvetica;font-size:16px;line-height:1;text-align:center;color:#000000;">
|
||||||
|
Please click the button below to confirm your email account.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="center" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
|
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
|
||||||
|
<tr>
|
||||||
|
<td align="center" bgcolor="#FF9A57" role="presentation" style="border:none;border-radius:3px;color:#ffffff;cursor:auto;padding:10px 25px;" valign="middle">
|
||||||
|
<a href="{{ confirm_url }}" style="background:#FF9A57;color:#ffffff;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;font-weight:normal;line-height:120%;Margin:0;text-decoration:none;text-transform:uppercase;" target="_blank">
|
||||||
|
Confirm Email
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!--[if mso | IE]>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -1,19 +0,0 @@
|
|||||||
import datetime
|
|
||||||
import dateutil
|
|
||||||
from apistar.typesystem import TypeSystemError
|
|
||||||
|
|
||||||
|
|
||||||
class Datetime(datetime.datetime):
|
|
||||||
native_type = datetime.datetime
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs) -> datetime:
|
|
||||||
if args and isinstance(args[0], cls.native_type):
|
|
||||||
return args[0]
|
|
||||||
if args and isinstance(args[0], str):
|
|
||||||
try:
|
|
||||||
return dateutil.parser.parse(args[0])
|
|
||||||
except ValueError:
|
|
||||||
raise TypeSystemError(cls=cls, code='type') from None
|
|
||||||
return cls.native_type(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
|||||||
|
from .app import MetaApp
|
||||||
|
from .http import MetaJSONResponse, ExtJSONResponse
|
||||||
|
from .component import SQLAlchemySession, DBSession
|
||||||
|
from .hook import SQLAlchemyHook
|
||||||
|
from .validators import Decimal
|
@ -0,0 +1,28 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
from apistar import App, exceptions
|
||||||
|
from apistar.http import Response, HTMLResponse
|
||||||
|
from apistar.server.components import ReturnValue
|
||||||
|
from .http import MetaJSONResponse
|
||||||
|
|
||||||
|
|
||||||
|
class MetaApp(App):
|
||||||
|
"""
|
||||||
|
A WSGI App subclass with a MetaJSONResponse default response type
|
||||||
|
"""
|
||||||
|
|
||||||
|
def render_response(self, return_value: ReturnValue) -> Response:
|
||||||
|
if isinstance(return_value, Response):
|
||||||
|
return return_value
|
||||||
|
elif isinstance(return_value, str):
|
||||||
|
return HTMLResponse(return_value)
|
||||||
|
return MetaJSONResponse(return_value)
|
||||||
|
|
||||||
|
def exception_handler(self, exc: Exception) -> Response:
|
||||||
|
if isinstance(exc, exceptions.HTTPException):
|
||||||
|
|
||||||
|
return MetaJSONResponse(exc.detail, status_code=exc.status_code, headers=exc.get_headers())
|
||||||
|
raise
|
||||||
|
|
||||||
|
def error_handler(self) -> Response:
|
||||||
|
return MetaJSONResponse('Server error', status_code=500, exc_info=sys.exc_info())
|
@ -0,0 +1,17 @@
|
|||||||
|
from apistar import Component
|
||||||
|
from sqlalchemy.engine import Engine
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from sqlalchemy.orm import sessionmaker, Session, scoped_session
|
||||||
|
|
||||||
|
DBSession = scoped_session(sessionmaker())
|
||||||
|
|
||||||
|
|
||||||
|
class SQLAlchemySession(Component):
|
||||||
|
def __init__(self, engine=None):
|
||||||
|
if not isinstance(engine, Engine):
|
||||||
|
raise ValueError('SQLAlchemySession must be instantiated with a sqlalchemy.engine.Engine object')
|
||||||
|
self.engine = engine
|
||||||
|
DBSession.configure(bind=self.engine)
|
||||||
|
|
||||||
|
def resolve(self) -> Session:
|
||||||
|
return DBSession()
|
@ -0,0 +1,15 @@
|
|||||||
|
from apistar.http import Response
|
||||||
|
from .component import Session, DBSession
|
||||||
|
|
||||||
|
class SQLAlchemyHook:
|
||||||
|
def on_request(self, session: Session):
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_response(self, session: Session, response: Response):
|
||||||
|
DBSession.remove()
|
||||||
|
return response
|
||||||
|
|
||||||
|
def on_error(self, session: Session, response: Response):
|
||||||
|
session.rollback()
|
||||||
|
DBSession.remove()
|
||||||
|
return response
|
@ -0,0 +1,85 @@
|
|||||||
|
import datetime as dt
|
||||||
|
import decimal
|
||||||
|
import json
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from apistar import types, exceptions
|
||||||
|
from apistar.http import JSONResponse, Response, StrMapping, StrPairs, MutableHeaders
|
||||||
|
from werkzeug.http import HTTP_STATUS_CODES
|
||||||
|
|
||||||
|
|
||||||
|
class ExtJSONResponse(JSONResponse):
|
||||||
|
"""JSON Response with support for ISO 8601 datetime serialization and Decimal to float casting"""
|
||||||
|
|
||||||
|
def default(self, obj: typing.Any) -> typing.Any:
|
||||||
|
if isinstance(obj, types.Type):
|
||||||
|
return dict(obj)
|
||||||
|
if isinstance(obj, dt.datetime):
|
||||||
|
return obj.isoformat()
|
||||||
|
elif isinstance(obj, decimal.Decimal):
|
||||||
|
return float(obj)
|
||||||
|
error = "Object of type '%s' is not JSON serializable."
|
||||||
|
return TypeError(error % type(obj).__name_)
|
||||||
|
|
||||||
|
|
||||||
|
class MetaJSONResponse(Response):
|
||||||
|
"""A JSONResponse that returns a meta, data, error on response"""
|
||||||
|
media_type = 'application/json'
|
||||||
|
charset = None
|
||||||
|
options = {
|
||||||
|
'ensure_ascii': False,
|
||||||
|
'allow_nan': False,
|
||||||
|
'indent': None,
|
||||||
|
'separators': (',', ':'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
content: typing.Any,
|
||||||
|
status_code: int = 200,
|
||||||
|
headers: typing.Union[StrMapping, StrPairs] = None,
|
||||||
|
exc_info=None,
|
||||||
|
meta: typing.Any = None) -> None:
|
||||||
|
self.status_code = status_code
|
||||||
|
self.meta = self._build_meta(meta)
|
||||||
|
self._content = content
|
||||||
|
self.content = self.render(content)
|
||||||
|
self.headers = MutableHeaders(headers)
|
||||||
|
self.set_default_headers()
|
||||||
|
self.exc_info = exc_info
|
||||||
|
|
||||||
|
def render(self, content: typing.Any) -> bytes:
|
||||||
|
"""Builds a JSON response containing meta data. If the content is an `apistar.execption.HTTPException`
|
||||||
|
then it will return the execption reason code"""
|
||||||
|
error = {}
|
||||||
|
if isinstance(content, exceptions.HTTPException):
|
||||||
|
error["reason"] = content.detail
|
||||||
|
|
||||||
|
options = {'default': self.default}
|
||||||
|
options.update(self.options)
|
||||||
|
response = dict(meta=self.meta)
|
||||||
|
|
||||||
|
if error:
|
||||||
|
response.update(dict(error=error))
|
||||||
|
return json.dumps(response, **options).encode('utf-8')
|
||||||
|
|
||||||
|
response.update(dict(data=content))
|
||||||
|
return json.dumps(response, **options).encode('utf-8')
|
||||||
|
|
||||||
|
def default(self, obj: typing.Any) -> typing.Any:
|
||||||
|
if isinstance(obj, types.Type):
|
||||||
|
return dict(obj)
|
||||||
|
if isinstance(obj, dt.datetime):
|
||||||
|
return obj.isoformat()
|
||||||
|
elif isinstance(obj, decimal.Decimal):
|
||||||
|
return float(obj)
|
||||||
|
error = "Object of type '%s' is not JSON serializable."
|
||||||
|
return TypeError(error % type(obj).__name_)
|
||||||
|
|
||||||
|
def _build_meta(self, meta):
|
||||||
|
_meta = {
|
||||||
|
"status": self.status_code,
|
||||||
|
"message": HTTP_STATUS_CODES.get(self.status_code)
|
||||||
|
}
|
||||||
|
if meta is None:
|
||||||
|
return _meta
|
||||||
|
return {**_meta, **meta}
|
@ -0,0 +1,59 @@
|
|||||||
|
import decimal
|
||||||
|
from math import isfinite
|
||||||
|
|
||||||
|
from apistar import validators
|
||||||
|
|
||||||
|
|
||||||
|
class Decimal(validators.NumericType):
|
||||||
|
numeric_type = decimal.Decimal
|
||||||
|
|
||||||
|
def validate(self, value, definitions=None, allow_coerce=False):
|
||||||
|
if value is None and self.allow_null:
|
||||||
|
return None
|
||||||
|
elif value is None:
|
||||||
|
self.error('null')
|
||||||
|
elif isinstance(value, bool):
|
||||||
|
self.error('type')
|
||||||
|
elif self.numeric_type is int and isinstance(value, float) and not value.is_integer():
|
||||||
|
self.error('integer')
|
||||||
|
elif not isinstance(value, (int, float, decimal.Decimal)) and not allow_coerce:
|
||||||
|
self.error('type')
|
||||||
|
elif isinstance(value, float) and not isfinite(value):
|
||||||
|
self.error('finite')
|
||||||
|
|
||||||
|
try:
|
||||||
|
value = self.numeric_type(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
self.error('type')
|
||||||
|
|
||||||
|
if self.enum is not None:
|
||||||
|
if value not in self.enum:
|
||||||
|
if len(self.enum) == 1:
|
||||||
|
self.error('exact')
|
||||||
|
self.error('enum')
|
||||||
|
|
||||||
|
if self.minimum is not None:
|
||||||
|
if self.exclusive_minimum:
|
||||||
|
if value <= self.minimum:
|
||||||
|
self.error('exclusive_minimum')
|
||||||
|
else:
|
||||||
|
if value < self.minimum:
|
||||||
|
self.error('minimum')
|
||||||
|
|
||||||
|
if self.maximum is not None:
|
||||||
|
if self.exclusive_maximum:
|
||||||
|
if value >= self.maximum:
|
||||||
|
self.error('exclusive_maximum')
|
||||||
|
else:
|
||||||
|
if value > self.maximum:
|
||||||
|
self.error('maximum')
|
||||||
|
|
||||||
|
if self.multiple_of is not None:
|
||||||
|
if isinstance(self.multiple_of, float):
|
||||||
|
if not (value * (1 / self.multiple_of)).is_integer():
|
||||||
|
self.error('multiple_of')
|
||||||
|
else:
|
||||||
|
if value % self.multiple_of:
|
||||||
|
self.error('multiple_of')
|
||||||
|
|
||||||
|
return value
|
@ -0,0 +1,4 @@
|
|||||||
|
alembic
|
||||||
|
pytest
|
||||||
|
pytest-cov
|
||||||
|
tox
|
@ -0,0 +1,23 @@
|
|||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile
|
||||||
|
# To update, run:
|
||||||
|
#
|
||||||
|
# pip-compile --output-file dev_requirements.txt dev_requirements.in
|
||||||
|
#
|
||||||
|
alembic==0.9.9
|
||||||
|
atomicwrites==1.1.5 # via pytest
|
||||||
|
attrs==18.1.0 # via pytest
|
||||||
|
coverage==4.5.1 # via pytest-cov
|
||||||
|
mako==1.0.7 # via alembic
|
||||||
|
markupsafe==1.0 # via mako
|
||||||
|
more-itertools==4.2.0 # via pytest
|
||||||
|
pluggy==0.6.0 # via pytest, tox
|
||||||
|
py==1.5.3 # via pytest, tox
|
||||||
|
pytest-cov==2.5.1
|
||||||
|
pytest==3.6.1
|
||||||
|
python-dateutil==2.7.3 # via alembic
|
||||||
|
python-editor==1.0.3 # via alembic
|
||||||
|
six==1.11.0 # via more-itertools, pytest, python-dateutil, tox
|
||||||
|
sqlalchemy==1.2.8 # via alembic
|
||||||
|
tox==3.0.0
|
||||||
|
virtualenv==16.0.0 # via tox
|
@ -1,28 +0,0 @@
|
|||||||
"""initial models
|
|
||||||
|
|
||||||
Revision ID: 227892845cde
|
|
||||||
Revises:
|
|
||||||
Create Date: 2018-01-15 14:58:17.931063
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '227892845cde'
|
|
||||||
down_revision = None
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
pass
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
pass
|
|
||||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,75 @@
|
|||||||
|
"""Cookie Domain Model
|
||||||
|
|
||||||
|
Revision ID: 374c36260db7
|
||||||
|
Revises: 710505cf5d4c
|
||||||
|
Create Date: 2018-06-10 17:39:18.891568
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '374c36260db7'
|
||||||
|
down_revision = '710505cf5d4c'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('cookies',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('created_date', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', CURRENT_TIMESTAMP)"), nullable=True),
|
||||||
|
sa.Column('modified_date', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', CURRENT_TIMESTAMP)"), nullable=True),
|
||||||
|
sa.Column('name', sa.String(length=50), nullable=True),
|
||||||
|
sa.Column('recipe_url', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('sku', sa.String(length=55), nullable=True),
|
||||||
|
sa.Column('qoh', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('unit_cost', sa.Numeric(precision=12, scale=2), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_cookies_name'), 'cookies', ['name'], unique=False)
|
||||||
|
op.create_table('users',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('created_date', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', CURRENT_TIMESTAMP)"), nullable=True),
|
||||||
|
sa.Column('modified_date', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', CURRENT_TIMESTAMP)"), nullable=True),
|
||||||
|
sa.Column('email', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('password', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('admin', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('confirmed', sa.Boolean(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('email')
|
||||||
|
)
|
||||||
|
op.create_table('orders',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('created_date', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', CURRENT_TIMESTAMP)"), nullable=True),
|
||||||
|
sa.Column('modified_date', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', CURRENT_TIMESTAMP)"), nullable=True),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('shipped', sa.Boolean(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('line_items',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('created_date', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', CURRENT_TIMESTAMP)"), nullable=True),
|
||||||
|
sa.Column('modified_date', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', CURRENT_TIMESTAMP)"), nullable=True),
|
||||||
|
sa.Column('order_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('cookie_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('quantity', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('extended_cost', sa.Numeric(precision=12, scale=2), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['cookie_id'], ['cookies.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['order_id'], ['orders.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('line_items')
|
||||||
|
op.drop_table('orders')
|
||||||
|
op.drop_table('users')
|
||||||
|
op.drop_index(op.f('ix_cookies_name'), table_name='cookies')
|
||||||
|
op.drop_table('cookies')
|
||||||
|
# ### end Alembic commands ###
|
@ -0,0 +1,24 @@
|
|||||||
|
"""Empty Init
|
||||||
|
|
||||||
|
Revision ID: 710505cf5d4c
|
||||||
|
Revises:
|
||||||
|
Create Date: 2018-06-10 17:31:37.014032
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '710505cf5d4c'
|
||||||
|
down_revision = None
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
pass
|
@ -0,0 +1,30 @@
|
|||||||
|
"""Adding user active attribute
|
||||||
|
|
||||||
|
Revision ID: 7c2d43ec9c84
|
||||||
|
Revises: 374c36260db7
|
||||||
|
Create Date: 2018-06-10 17:57:32.570036
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '7c2d43ec9c84'
|
||||||
|
down_revision = '374c36260db7'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('users', sa.Column('active', sa.Boolean()))
|
||||||
|
op.execute('UPDATE users SET active = True')
|
||||||
|
op.alter_column('users', 'active', nullable=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('users', 'active')
|
||||||
|
# ### end Alembic commands ###
|
@ -1,30 +0,0 @@
|
|||||||
"""Added confirmed to user
|
|
||||||
|
|
||||||
Revision ID: 82595a1e5193
|
|
||||||
Revises: 227892845cde
|
|
||||||
Create Date: 2018-01-15 15:34:46.028181
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '82595a1e5193'
|
|
||||||
down_revision = '227892845cde'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('users', sa.Column('confirmed', sa.Boolean()))
|
|
||||||
op.execute('UPDATE users SET confirmed=FALSE')
|
|
||||||
op.alter_column('users', 'confirmed', nullable=False)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('users', 'confirmed')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,32 +1,29 @@
|
|||||||
alembic==0.9.6
|
#
|
||||||
apistar==0.3.9
|
# This file is autogenerated by pip-compile
|
||||||
apistar-alembic-migrations==0.0.6
|
# To update, run:
|
||||||
apistar-jwt==0.2.1
|
#
|
||||||
|
# pip-compile --output-file requirements.txt setup.py
|
||||||
|
#
|
||||||
|
apistar-jwt==0.4.2
|
||||||
|
apistar-mail==0.3.0
|
||||||
|
apistar==0.5.18
|
||||||
bcrypt==3.1.4
|
bcrypt==3.1.4
|
||||||
certifi==2017.7.27.1
|
certifi==2018.4.16 # via requests
|
||||||
cffi==1.11.2
|
cffi==1.11.5 # via bcrypt
|
||||||
chardet==3.0.4
|
chardet==3.0.4 # via requests
|
||||||
colorama==0.3.9
|
click==6.7 # via apistar
|
||||||
coreapi==2.3.3
|
idna==2.6 # via requests
|
||||||
coreschema==0.0.4
|
itsdangerous==0.24
|
||||||
idna==2.6
|
jinja2==2.10 # via apistar
|
||||||
itypes==1.1.0
|
logbook==1.3.3
|
||||||
Jinja2==2.9.6
|
markupsafe==1.0 # via jinja2
|
||||||
Logbook==1.1.0
|
psycopg2-binary==2.7.4
|
||||||
Mako==1.0.7
|
pycparser==2.18 # via cffi
|
||||||
MarkupSafe==1.0
|
pyjwt==1.6.4 # via apistar-jwt
|
||||||
marshmallow==2.15.0
|
pyyaml==3.12 # via apistar
|
||||||
psycopg2==2.7.3.1
|
requests==2.18.4 # via apistar
|
||||||
py==1.4.34
|
six==1.11.0 # via bcrypt
|
||||||
pycparser==2.18
|
sqlalchemy==1.2.8
|
||||||
PyJWT==1.5.3
|
urllib3==1.22 # via requests
|
||||||
pytest==3.2.3
|
werkzeug==0.14.1 # via apistar
|
||||||
python-dateutil==2.6.1
|
whitenoise==3.3.1 # via apistar
|
||||||
python-editor==1.0.3
|
|
||||||
requests==2.18.4
|
|
||||||
six==1.11.0
|
|
||||||
SQLAlchemy==1.1.14
|
|
||||||
uritemplate==3.0.0
|
|
||||||
urllib3==1.22
|
|
||||||
Werkzeug==0.12.2
|
|
||||||
whitenoise==3.3.1
|
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
with open('README.md') as readme_file:
|
||||||
|
readme = readme_file.read()
|
||||||
|
|
||||||
|
requirements = [
|
||||||
|
'apistar-jwt==0.4.2',
|
||||||
|
'apistar-mail==0.3.0',
|
||||||
|
'apistar==0.5.18',
|
||||||
|
'bcrypt==3.1.4',
|
||||||
|
'itsdangerous==0.24',
|
||||||
|
'logbook==1.3.3',
|
||||||
|
'psycopg2-binary==2.7.4',
|
||||||
|
'sqlalchemy==1.2.8',
|
||||||
|
]
|
||||||
|
|
||||||
|
test_requirements = [
|
||||||
|
'pytest',
|
||||||
|
'pytest-cov',
|
||||||
|
'tox'
|
||||||
|
]
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='cookie-api',
|
||||||
|
version='0.1.0',
|
||||||
|
description='The Cookie API serves as a simple experimentation application for testing concepts in the APIStar '
|
||||||
|
'platform.',
|
||||||
|
long_description=readme,
|
||||||
|
author='Drew Bednar',
|
||||||
|
author_email='drew@androiddrew.com',
|
||||||
|
maintainer='Drew Bednar',
|
||||||
|
maintainer_email='drew@androiddrew.com',
|
||||||
|
url='https://git.androiddrew.com/androiddrew/cookie-api',
|
||||||
|
packages=find_packages(exclude=['tests']),
|
||||||
|
include_package_data=True,
|
||||||
|
install_requires=requirements,
|
||||||
|
license='MIT',
|
||||||
|
zip_safe=False,
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 2 - Pre-Alpha',
|
||||||
|
'Environment :: Web Environment',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: BSD License',
|
||||||
|
'Natural Language :: English',
|
||||||
|
'Programming Language :: Python :: 3.5',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
|
],
|
||||||
|
extras_require={
|
||||||
|
'testing': test_requirements,
|
||||||
|
},
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'wsgi_runner=wsgi:main'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
@ -1,23 +0,0 @@
|
|||||||
import datetime as dt
|
|
||||||
from decimal import Decimal
|
|
||||||
import json
|
|
||||||
|
|
||||||
from cookie_api.renders import extended_encoder, JSONRenderer
|
|
||||||
|
|
||||||
|
|
||||||
def test_extended_encoder_date_parsing():
|
|
||||||
test_date = dt.datetime(2017, 5, 10)
|
|
||||||
assert test_date.isoformat() == extended_encoder(test_date)
|
|
||||||
|
|
||||||
|
|
||||||
def test_extended_encoder_decimal_casting():
|
|
||||||
test_decimal = Decimal('1.0')
|
|
||||||
assert 1.0 == extended_encoder(test_decimal)
|
|
||||||
|
|
||||||
|
|
||||||
def test_render_with_extended_encoder():
|
|
||||||
test_date = dt.datetime(2017, 5, 10)
|
|
||||||
test_decimal = Decimal('0.1')
|
|
||||||
expected = dict(my_date="2017-05-10T00:00:00", my_float=0.1)
|
|
||||||
test_response = dict(my_date=test_date, my_float=test_decimal)
|
|
||||||
assert json.dumps(expected).encode('utf-8') == JSONRenderer().render(test_response)
|
|
Loading…
Reference in New Issue