Level up to Apistar 0.5.4 without tests

master
androiddrew 7 years ago
parent ded199e618
commit ea35decd36

@ -0,0 +1,3 @@
[flake8]
max-line-length = 120
exclude = .git,__pycache__,htmlcov,.cache,.tox,env,dist,doc,*egg,build

@ -0,0 +1,5 @@
#History
0.2.0 Level up to Apistar 0.5.x
0.1.0 Initial app

@ -1,3 +1,6 @@
# news
A ReSTful api for storing and retrieving news articles
A ReSTful api for storing and retrieving news articles
## Pip tools
This project uses pip-tools for developement. Reference the [project page](https://github.com/jazzband/pip-tools) for usage instructions.

@ -0,0 +1,6 @@
pip-tools
pytest
tox
flake8
setuptools >= 38.6.0
wheel >= 0.31.0

@ -0,0 +1,29 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --output-file dev_requirements.txt dev_requirements.in
#
--trusted-host pypi.python.org
--trusted-host git.aigalatic.com
atomicwrites==1.1.5 # via pytest
attrs==18.1.0 # via pytest
click==6.7 # via pip-tools
first==2.0.1 # via pip-tools
flake8==3.5.0
mccabe==0.6.1 # via flake8
more-itertools==4.2.0 # via pytest
pip-tools==2.0.2
pluggy==0.6.0 # via pytest, tox
py==1.5.4 # via pytest, tox
pycodestyle==2.3.1 # via flake8
pyflakes==1.6.0 # via flake8
pytest==3.6.2
six==1.11.0 # via more-itertools, pip-tools, pytest, tox
tox==3.0.0
virtualenv==16.0.0 # via tox
wheel==0.31.1
# The following packages are considered to be unsafe in a requirements file:
# setuptools

@ -1,2 +1 @@
from .app import application_factory
from .app import application_factory # noqa: F401

@ -1,13 +1,8 @@
from apistar import Include, Route, http
from apistar.interfaces import Router
from apistar.frameworks.wsgi import WSGIApp as App
from apistar.backends import sqlalchemy_backend
from apistar.backends.sqlalchemy_backend import Session
from apistar.handlers import docs_urls, static_urls
from .models import Base, NewsArticle, NewsSource
from .renders import JSONRenderer
from apistar import Route, http, App
from sqlalchemy import create_engine
from .models import NewsArticle, NewsSource
from .schema import NewsSourceSchema, NewsArticleSchema
from .util import Session, SQLAlchemySession, SQLAlchemyHook
news_source_schema = NewsSourceSchema()
news_article_schema = NewsArticleSchema()
@ -15,22 +10,22 @@ news_article_schema = NewsArticleSchema()
def get_sources(session: Session):
sources = session.query(NewsSource).all()
return http.Response(news_source_schema.dump(sources, many=True).data, status=200)
return http.JSONResponse(news_source_schema.dump(sources, many=True).data, status_code=200)
def add_source(session: Session, request_data: http.RequestData, router: Router):
def add_source(session: Session, request_data: http.RequestData, app: App):
news_source_data, errors = news_source_schema.load(request_data)
if errors:
msg = {"message": "400 Bad Request", "error": errors}
return http.Response(msg, status=400)
return http.JSONResponse(msg, status_code=400)
news_source = NewsSource(**news_source_data)
session.add(news_source)
session.flush()
headers = {"Location": router.reverse_url('get_source', {"id": news_source.id})}
headers = {"Location": app.reverse_url('get_source', {"id": news_source.id})}
return http.Response(news_source_schema.dump(news_source).data, status=201, headers=headers)
return http.JSONResponse(news_source_schema.dump(news_source).data, status_code=201, headers=headers)
def delete_source(session: Session, id: int):
@ -38,47 +33,47 @@ def delete_source(session: Session, id: int):
news_source = session.query(NewsSource).filter_by(id=id).one_or_none()
if news_source is None:
msg = {"message": "404 Not Found"}
return http.Response(msg, status=404)
return http.JSONResponse(msg, status_code=404)
msg = {"message": "200 OK"}
return http.Response(msg, status=200)
return http.JSONResponse(msg, status_code=200)
def get_source(session: Session, id: int):
news_source = session.query(NewsSource).filter_by(id=id).one_or_none()
if news_source is None:
msg = {"message": "404 Not Found"}
return http.Response(msg, status=404)
return http.JSONResponse(msg, status_code=404)
return http.Response(news_source_schema.dump(news_source).data, status=200)
return http.JSONResponse(news_source_schema.dump(news_source).data, status_code=200)
def get_articles(session: Session):
articles = session.query(NewsArticle).all()
return http.Response(news_article_schema.dump(articles, many=True).data, status=200)
return http.JSONResponse(news_article_schema.dump(articles, many=True).data, status_code=200)
def add_article(session: Session, request_data: http.RequestData, router: Router):
def add_article(session: Session, request_data: http.RequestData, app: App):
news_article_data, errors = news_article_schema.load(request_data)
if errors:
msg = {"message": "400 Bad Request", "error": errors}
return http.Response(msg, status=400)
return http.JSONResponse(msg, status_code=400)
news_article = NewsArticle(**news_article_data)
session.add(news_article)
session.flush()
headers = {"Location": router.reverse_url('get_source', {"id": news_article.id})}
headers = {"Location": app.reverse_url('get_source', {"id": news_article.id})}
return http.Response(news_article_schema.dump(news_article), status=201, headers=headers)
return http.JSONResponse(news_article_schema.dump(news_article), status_code=201, headers=headers)
def get_article(session: Session, id: int):
news_article = session.query(NewsArticle).filter_by(id=id).one_or_none()
if news_article is None:
msg = {"message": "404 Not Found"}
return http.Response(msg, status=404)
return http.Response(news_article_schema.dump(news_article), status=200)
return http.JSONResponse(msg, status_code=404)
return http.JSONResponse(news_article_schema.dump(news_article), status_code=200)
def delete_article(session: Session, id):
@ -86,10 +81,10 @@ def delete_article(session: Session, id):
news_article = session.query(NewsArticle).filter_by(id=id).one_or_none()
if news_article is None:
msg = {"message": "404 Not Found"}
return http.Response(msg, status=404)
return http.JSONResponse(msg, status_code=404)
msg = {"message": "200 OK"}
return http.Response(msg, status=200)
return http.JSONResponse(msg, status_code=200)
routes = [
@ -101,29 +96,22 @@ routes = [
Route('/articles', 'POST', add_article),
Route('/articles/{id}', 'GET', get_article),
Route('/articles/{id}', 'DELETE', delete_article),
Include('/docs', docs_urls),
Include('/static', static_urls)
]
_settings = {
'DATABASE': {
'URL': 'postgresql://apistar:local@localhost/news',
'METADATA': Base.metadata
},
'RENDERERS': [JSONRenderer()]
}
routes = routes
commands = sqlalchemy_backend.commands
components = [SQLAlchemySession(engine=create_engine('postgresql://apistar:local@localhost/news'))]
components = sqlalchemy_backend.components
hooks = [SQLAlchemyHook()]
def application_factory(settings={}):
def application_factory():
"""Returns an instance of Cookie API"""
app_settings = {**_settings, **settings}
return App(settings=app_settings,
commands=commands,
components=components,
return App(components=components,
event_hooks=hooks,
routes=routes)
if __name__ == "__main__":
app = application_factory()
app.serve('0.0.0.0', 8080, debug=True)

@ -1,7 +1,7 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Boolean, Numeric, BigInteger, Text, Table, ARRAY
from sqlalchemy import Column, ForeignKey, DateTime, BigInteger, Text, Table, ARRAY
from sqlalchemy.orm import relationship, backref
from sqlalchemy.sql import expression, and_
from sqlalchemy.sql import expression
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.types import DateTime as DateTimeType
from sqlalchemy.orm.exc import NoResultFound

@ -1,23 +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.timestamp()
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')

@ -5,6 +5,7 @@ from marshmallow import Schema, fields
class PassDateTime(fields.DateTime):
"""Used to Pass Datetime deserialization """
def _deserialize(self, value, attr, data):
if isinstance(value, dt.datetime):
return value
@ -40,14 +41,17 @@ class NewsSourceSchema(Schema):
created_date = UnixTimestamp(dump_only=True)
modified_date = UnixTimestamp(dump_only=True)
url = fields.URL()
source_name = fields.Str(required=True, error_messages={'required': 'NewsSource name is a required field'})
source_type = fields.Str(required=True, error_messages={'required': 'NewsSource type is a required field'})
source_name = fields.Str(required=True,
error_messages={'required': 'NewsSource name is a required field'})
source_type = fields.Str(required=True,
error_messages={'required': 'NewsSource type is a required field'})
# TODO add support for Categories
class Meta:
ordered = True
# TODO deserialization of timestamp to
class NewsArticleSchema(Schema):
id = fields.Int(dump_only=True)

@ -0,0 +1,38 @@
import inspect
import typing
from apistar import Component
from apistar.http import Response
from sqlalchemy.engine import Engine
from sqlalchemy.orm import sessionmaker, scoped_session
DBSession = scoped_session(sessionmaker())
Session = typing.TypeVar("Session")
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):
return DBSession()
def can_handle_parameter(self, parameter: inspect.Parameter) -> bool:
return parameter.annotation is Session
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,5 @@
apistar
psycopg2
marshmallow
SQLAlchemy

@ -1,21 +1,25 @@
apistar==0.3.9
certifi==2017.11.5
chardet==3.0.4
colorama==0.3.9
coreapi==2.3.3
coreschema==0.0.4
idna==2.6
itypes==1.1.0
Jinja2==2.9.6
MarkupSafe==1.0
marshmallow==2.15.0
psycopg2==2.7.3.2
py==1.4.34
pytest==3.2.3
pytz==2017.3
requests==2.18.4
SQLAlchemy==1.1.15
uritemplate==3.0.0
urllib3==1.22
Werkzeug==0.12.2
whitenoise==3.3.1
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --output-file requirements.txt requirements.in
#
--trusted-host pypi.python.org
--trusted-host git.aigalatic.com
apistar==0.5.40
certifi==2018.4.16 # via requests
chardet==3.0.4 # via requests
click==6.7 # via apistar
idna==2.7 # via requests
jinja2==2.10 # via apistar
markupsafe==1.0 # via jinja2
marshmallow==2.15.3
psycopg2==2.7.5
pyyaml==3.12 # via apistar
requests==2.19.1 # via apistar
sqlalchemy==1.2.9
urllib3==1.23 # via requests
werkzeug==0.14.1 # via apistar
whitenoise==3.3.1 # via apistar
>>>>>>> Level up to Apistar 0.5.4 without tests

@ -0,0 +1,11 @@
from news import application_factory
app = application_factory()
def main():
app.serve('0.0.0.0', 8080, debug=True)
if __name__ == "__main__":
main()

@ -0,0 +1,79 @@
from setuptools import setup, find_packages
with open('README.md') as readme_file:
readme = readme_file.read()
with open('HISTORY.md') as history_file:
history = history_file.read()
requirements = [
'apistar==0.5.40',
'certifi==2018.4.16', # via requests
'chardet==3.0.4', # via requests
'click==6.7', # via apistar
'idna==2.7', # via requests
'jinja2==2.10', # via apistar
'markupsafe==1.0', # via jinja2
'marshmallow==2.15.3',
'psycopg2==2.7.5',
'pyyaml==3.12', # via apistar
'requests==2.19.1', # via apistar
'sqlalchemy==1.2.9',
'urllib3==1.23', # via requests
'werkzeug==0.14.1', # via apistar
'whitenoise==3.3.1', # via apistar
]
dev_requirements = [
'atomicwrites==1.1.5', # via pytest
'attrs==18.1.0', # via pytest
'click==6.7', # via pip-tools
'first==2.0.1', # via pip-tools
'flake8==3.5.0',
'mccabe==0.6.1', # via flake8
'more-itertools==4.2.0', # via pytest
'pip-tools==2.0.2',
'pluggy==0.6.0', # via pytest, tox
'py==1.5.4', # via pytest, tox
'pycodestyle==2.3.1', # via flake8
'pyflakes==1.6.0', # via flake8
'pytest==3.6.2',
'six==1.11.0', # via more-itertools, pip-tools, pytest, tox
'tox==3.0.0',
'virtualenv==16.0.0', # via tox
'wheel==0.31.1',
]
setup(
name='news',
version='0.2.0',
description="A ReSTful api for storing and retrieving news articles",
long_description=readme + '\n\n' + history,
long_description_content_type='text/markdown',
author="Drew Bednar",
author_email='drew@androiddrew.com',
url='https://git.aigalactic.com/Spyglass/news',
packages=find_packages(include=['news', 'scripts']),
include_package_data=True,
install_requires=requirements,
license='BSD',
keywords='news spyglass',
classifiers=[
'Development Status :: 2 - Pre-Alpha',
'Environment :: Backend',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Natural Language :: English',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],
extras_require={
'dev': dev_requirements,
},
entry_points={
'console_scripts': [
'wsgi_serve=scripts.wsgi_app:main'
]
},
)

@ -1,24 +0,0 @@
import datetime as dt
import json
from pytz import timezone
from decimal import Decimal
from news.renders import extended_encoder, JSONRenderer
def test_extended_encoder_date_parsing():
test_date = dt.datetime(2017, 5, 10, tzinfo=timezone('UTC'))
assert test_date.timestamp() == 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=1494374400.0, 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)

@ -1,9 +0,0 @@
from news import application_factory
# Override base application settings here
settings = {}
app = application_factory(settings)
if __name__ == "__main__":
app.main()
Loading…
Cancel
Save