commit 3e2cf37921e4fe2a42597859de524a711fd94ba7 Author: androiddrew Date: Wed Oct 4 15:02:02 2017 -0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5ce60e --- /dev/null +++ b/.gitignore @@ -0,0 +1,62 @@ +# Pycharm +.idea + +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app.py b/app.py new file mode 100644 index 0000000..ecd8329 --- /dev/null +++ b/app.py @@ -0,0 +1,52 @@ +import json + +from apistar import Include, Route, http, Response, annotate +from apistar.frameworks.wsgi import WSGIApp as App +from apistar.handlers import docs_urls, static_urls +from apistar.backends import sqlalchemy_backend +from apistar.backends.sqlalchemy_backend import Session + +from models import Base, Cookie +from model.util import alchemyencoder +from render import JSONRenderer + + +def welcome(name=None): + if name is None: + return {'message': 'Welcome to API Star!'} + return {'message': 'Welcome to API Star, %s!' % name} + + +@annotate(renderers=[JSONRenderer()]) +def get_cookies(session: Session): + cookies = session.query(Cookie).all() + result = [{"id": cookie.id, + "created_date": cookie.created_date, + "modified_date": cookie.modified_date, + "name": cookie.name, + "recipe_url": cookie.recipe_url, + "sku": cookie.sku, + "qoh": cookie.unit_cost} + for cookie in cookies] + + return result + +routes = [ + Route('/', 'GET', welcome), + Route('/cookies', 'GET', get_cookies), + Include('/docs', docs_urls), + Include('/static', static_urls) +] + +settings = { + 'DATABASE': { + 'URL': 'postgresql://:@localhost/apistar', + 'METADATA': Base.metadata + } +} + +app = App(routes=routes, + settings=settings, + commands=sqlalchemy_backend.commands, + components=sqlalchemy_backend.components + ) diff --git a/model/__init__.py b/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/util.py b/model/util.py new file mode 100644 index 0000000..be07f85 --- /dev/null +++ b/model/util.py @@ -0,0 +1,9 @@ +import datetime as dt +import decimal + +def alchemyencoder(obj): + """JSON encoder function for SQLAlchemy special classes.""" + if isinstance(obj, dt.datetime): + return obj.isoformat() + elif isinstance(obj, decimal.Decimal): + return float(obj) \ No newline at end of file diff --git a/models.py b/models.py new file mode 100644 index 0000000..8f7c5de --- /dev/null +++ b/models.py @@ -0,0 +1,70 @@ +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Boolean, Numeric +from sqlalchemy.orm import relationship, backref +from sqlalchemy.sql import expression +from sqlalchemy.ext.compiler import compiles +from sqlalchemy.types import DateTime as DateTimeType + +# can be moved to models util +class utcnow(expression.FunctionElement): + type = DateTimeType() + +# Can be moved to the models util dirp +@compiles(utcnow, 'postgresql') +def pg_utcnow(element, compiler, **kw): + return "TIMEZONE('utc', CURRENT_TIMESTAMP)" + + +Base = declarative_base() + + + +class DBMixin: + id = Column(Integer, primary_key=True) + created_date = Column(DateTime, server_default=utcnow()) + modified_date = Column(DateTime, server_default=utcnow(), onupdate=utcnow()) + + +def ReferenceCol(tablename, nullable=False, **kw): + return Column(ForeignKey('{}.id'.format(tablename)), nullable=nullable, **kw) + + +class Cookie(Base, DBMixin): + __tablename__ = 'cookies' + + name = Column(String(50), index=True) + recipe_url = Column(String(255)) + sku = Column(String(55)) + qoh = Column(Integer) + unit_cost = Column(Numeric(12, 2)) + + +class User(DBMixin, Base): + __tablename__ = 'users' + + username = Column(String(255), nullable=False, unique=True) + email_address = Column(String(255), nullable=False) + phone = Column(String(20), nullable=False) + password = Column(String(255)) + + +class Order(DBMixin, Base): + __tablename__ = 'orders' + + user_id = ReferenceCol('users') + shipped = Column(Boolean, default=False) + + user = relationship('User', backref=backref('orders')) + + +class LineItem(DBMixin, Base): + __tablename__ = 'line_items' + + order_id = ReferenceCol('orders') + cookie_id = ReferenceCol('cookies') + quantity = Column(Integer) + extended_cost = Column(Numeric(12, 2)) + + order = relationship('Order', backref=backref('line_items')) + cookie = relationship('Cookie', uselist=False) + diff --git a/render.py b/render.py new file mode 100644 index 0000000..77c029b --- /dev/null +++ b/render.py @@ -0,0 +1,13 @@ +import json +from apistar import http +from apistar.renderers import Renderer + +from model.util import alchemyencoder + + +class JSONRenderer(Renderer): + media_type = 'application/json' + charset = None + + def render(self, data: http.ResponseData) -> bytes: + return json.dumps(data, default=alchemyencoder).encode('utf-8') \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6aef029 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,17 @@ +apistar==0.3.6 +certifi==2017.7.27.1 +chardet==3.0.4 +coreapi==2.3.1 +coreschema==0.0.4 +idna==2.6 +itypes==1.1.0 +Jinja2==2.9.6 +MarkupSafe==1.0 +py==1.4.34 +pytest==3.2.2 +requests==2.18.4 +SQLAlchemy==1.1.14 +uritemplate==3.0.0 +urllib3==1.22 +Werkzeug==0.12.2 +whitenoise==3.3.1 diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..5c73bcc --- /dev/null +++ b/tests.py @@ -0,0 +1,20 @@ +from apistar.test import TestClient +from app import welcome + + +def test_welcome(): + """ + Testing a view directly. + """ + data = welcome() + assert data == {'message': 'Welcome to API Star!'} + + +def test_http_request(): + """ + Testing a view, using the test client. + """ + client = TestClient() + response = client.get('http://localhost/') + assert response.status_code == 200 + assert response.json() == {'message': 'Welcome to API Star!'}