diff --git a/.gitignore b/.gitignore index 5abd0da..bcb4bd8 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ htmlcov/ nosetests.xml coverage.xml *,cover +.pytest_cache/ # Translations *.mo diff --git a/README.md b/README.md index 964504d..7efe3ca 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,27 @@ 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. \ No newline at end of file +This project uses pip-tools for developement. Reference the [project page](https://github.com/jazzband/pip-tools) for usage instructions. + +## DB Connection + +The database connection string must be set using the `NEWS_DB` environmental variable. + +``` +$ export NEWS_DB=postgresql://apistar:local@localhost/news +``` + +## Console Script +A console script os provided for testing via the Werkzueg development server. + + +``` +$(env)wsgi_serve + +``` + +## Deployment + +``` +$(env) gunicorn -w 2 -b 0.0.0.0:8080 --chdir scripts/ wsgi_app:app +``` \ No newline at end of file diff --git a/news/app.py b/news/app.py index 2bda7aa..5545dd5 100644 --- a/news/app.py +++ b/news/app.py @@ -1,3 +1,4 @@ +import os from apistar import Route, http, App from sqlalchemy import create_engine from .models import NewsArticle, NewsSource @@ -9,11 +10,13 @@ news_article_schema = NewsArticleSchema() def get_sources(session: Session): + """Retrieves all News Sources""" sources = session.query(NewsSource).all() return http.JSONResponse(news_source_schema.dump(sources, many=True).data, status_code=200) def add_source(session: Session, request_data: http.RequestData, app: App): + """Adds a single source to the News Source collection""" news_source_data, errors = news_source_schema.load(request_data) if errors: msg = {"message": "400 Bad Request", "error": errors} @@ -23,23 +26,26 @@ def add_source(session: Session, request_data: http.RequestData, app: App): session.add(news_source) session.flush() - headers = {"Location": app.reverse_url('get_source', {"id": news_source.id})} - + headers = {"Location": app.reverse_url('get_source', id=news_source.id)} + session.commit() return http.JSONResponse(news_source_schema.dump(news_source).data, status_code=201, headers=headers) def delete_source(session: Session, id: int): - """Delete a single News Sources from the collection by id""" + """Delete a single News Source from the collection by id""" news_source = session.query(NewsSource).filter_by(id=id).one_or_none() if news_source is None: msg = {"message": "404 Not Found"} return http.JSONResponse(msg, status_code=404) + session.delete(news_source) + session.commit() msg = {"message": "200 OK"} return http.JSONResponse(msg, status_code=200) def get_source(session: Session, id: int): + """Retrieves a single News Source by id""" news_source = session.query(NewsSource).filter_by(id=id).one_or_none() if news_source is None: msg = {"message": "404 Not Found"} @@ -49,11 +55,22 @@ def get_source(session: Session, id: int): def get_articles(session: Session): + """Retrieves all articles""" articles = session.query(NewsArticle).all() return http.JSONResponse(news_article_schema.dump(articles, many=True).data, status_code=200) +def get_article(session: Session, id: int): + """Retrieves a single article by 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.JSONResponse(msg, status_code=404) + return http.JSONResponse(news_article_schema.dump(news_article).data, status_code=200) + + def add_article(session: Session, request_data: http.RequestData, app: App): + """Adds a single article""" news_article_data, errors = news_article_schema.load(request_data) if errors: msg = {"message": "400 Bad Request", "error": errors} @@ -63,17 +80,9 @@ def add_article(session: Session, request_data: http.RequestData, app: App): session.add(news_article) session.flush() - headers = {"Location": app.reverse_url('get_source', {"id": news_article.id})} - - 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.JSONResponse(msg, status_code=404) - return http.JSONResponse(news_article_schema.dump(news_article), status_code=200) + headers = {"Location": app.reverse_url('get_article', id=news_article.id)} + session.commit() + return http.JSONResponse(news_article_schema.dump(news_article).data, status_code=201, headers=headers) def delete_article(session: Session, id): @@ -83,6 +92,8 @@ def delete_article(session: Session, id): msg = {"message": "404 Not Found"} return http.JSONResponse(msg, status_code=404) + session.delete(news_article) + session.commit() msg = {"message": "200 OK"} return http.JSONResponse(msg, status_code=200) @@ -100,7 +111,9 @@ routes = [ routes = routes -components = [SQLAlchemySession(engine=create_engine('postgresql://apistar:local@localhost/news'))] +components = [ + SQLAlchemySession(engine=create_engine(os.getenv('NEWS_DB', 'postgresql://apistar:local@localhost/news'))) +] hooks = [SQLAlchemyHook()] diff --git a/news/models.py b/news/models.py index 0cc35b4..c2daa29 100644 --- a/news/models.py +++ b/news/models.py @@ -77,6 +77,9 @@ class NewsSource(DBMixin, Base): source_type = Column(Text) categories = relationship('Category', secondary=categories, backref=backref('news_sources', lazy='dynamic')) + articles = relationship('NewsArticle', + backref=backref('news_source'), + cascade="all, delete, delete-orphan") class NewsArticle(DBMixin, Base): @@ -87,5 +90,4 @@ class NewsArticle(DBMixin, Base): authors = Column(ARRAY(Text)) published_date = Column(DateTime) news_blob = Column(Text) - news_source = relationship('NewsSource', backref=backref('articles', lazy='dynamic')) tags = relationship('Tag', secondary=tags, backref=backref('articles', lazy='dynamic')) diff --git a/news/schema.py b/news/schema.py index f9c8da3..8ee8580 100644 --- a/news/schema.py +++ b/news/schema.py @@ -64,7 +64,8 @@ class NewsArticleSchema(Schema): title = fields.Str() authors = fields.List(fields.Str()) published_date = UnixTimestamp(dump_only=True) - news_blob = fields.Str(required=True, error_messages={'required': 'NewsArticle must include news content'}) + news_blob = fields.Str(required=True, + error_messages={'required': 'NewsArticle must include news content'}) # TODO add support for Tags