Commit 2207540d authored by Patrick van der Leer's avatar Patrick van der Leer
Browse files

Refs #2

parent 360b5c0a
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="dev" uuid="8729ac1d-61c3-4822-9fec-29a292d76b77">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/dev.db</jdbc-url>
<driver-properties>
<property name="enable_load_extension" value="true" />
</driver-properties>
<libraries>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.20.1.1/sqlite-jdbc-3.20.1.1.jar</url>
</library>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.20.1.1/xerial-sqlite-license.txt</url>
</library>
</libraries>
</data-source>
</component>
</project>
\ No newline at end of file
import logging
from logging.handlers import RotatingFileHandler
# import connexion
from flask import Flask
from flask_cors import CORS
......@@ -39,12 +38,15 @@ def configure_blueprints(app):
def create_api(app, host='localhost', port=5000, api_prefix=''):
from safrs import SAFRSAPI
from .models import Author, Book, BookEdition
from .models import Author, Book, BookEdition, BookSerie, Genre, Publisher
api = SAFRSAPI(app, host=host, port=port, prefix=api_prefix)
api = SAFRSAPI(app, host='{}:{}'.format(host, port), prefix=api_prefix)
api.expose_object(Author)
api.expose_object(Book)
api.expose_object(BookEdition)
api.expose_object(BookSerie)
api.expose_object(Genre)
api.expose_object(Publisher)
def configure_extensions(app):
......@@ -64,30 +66,14 @@ def configure_extensions(app):
return None
def post_configure_extensions(app):
def json_extract(item, key):
import json
j = json.loads(item)
value = j.get(key)
return value
import sqlalchemy
engine = ext.db.get_engine(app=app)
@sqlalchemy.event.listens_for(engine, "begin")
def do_begin(conn):
if conn.dialect.name == 'sqlite':
conn.connection.create_function('jsonExtract', 2, json_extract)
def configure_shellcontext(app):
"""Configure shell context objects."""
def shell_context():
"""Shell context objects."""
return {
'db': db
'db': db,
'session': db.session,
}
app.shell_context_processor(shell_context)
......
from .core import Author, Book, BookEdition
from .core import Author, Book, BookEdition, BookSerie, Genre, Publisher
__all__ = [
Author,
Book,
BookEdition
BookEdition,
BookSerie,
Genre,
Publisher
]
from sqlalchemy import UniqueConstraint
from sqlalchemy.orm import validates
from eBookHub.database import Model, db
from eBookHub.validators.isbn import validate_isbn10, validate_isbn13
Column = db.Column
author_book_table = db.Table(
'author_book',
db.Column('author_id', db.Integer, db.ForeignKey('author.id')),
db.Column('book_id', db.Integer, db.ForeignKey('book.id')),
UniqueConstraint('author_id', 'book_id'),
)
book_genre_table = db.Table(
'book_genre',
db.Column('book_id', db.Integer, db.ForeignKey('book.id')),
db.Column('genre_id', db.Integer, db.ForeignKey('genre.id')),
UniqueConstraint('book_id', 'genre_id'),
)
class Author(Model):
class Genre(Model):
"""Genre"""
id = db.Column(db.Integer, primary_key=True)
name = Column(db.String(255))
sortable_name = Column(db.String(255))
name = db.Column(db.String(255))
fiction = db.Column(db.Boolean)
books = db.relationship(
"Book",
secondary=book_genre_table,
back_populates="genres"
)
class Publisher(Model):
"""Publisher"""
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
books = db.relationship("BookEdition", back_populates="publisher")
def set_name(self, name):
self.name = name
# todo
self.sortable_name = name
class Author(Model):
"""Author"""
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
sortable_name = db.Column(db.String(255))
goodreads_id = db.Column(db.String(10), comment="Goodreads author ID")
books = db.relationship(
"Book",
secondary=author_book_table,
back_populates="authors"
)
class Book(Model):
"""Book"""
id = db.Column(db.Integer, primary_key=True)
title = Column(db.String(255))
sortable_title = Column(db.String(255))
language = None
serie = None
# authors = []
title = db.Column(db.String(255))
sortable_title = db.Column(db.String(255))
language = db.Column(db.String(3), comment="ISO-639-2")
editions = db.relationship("BookEdition", uselist=False, back_populates="book")
authors = db.relationship(
"Author",
secondary=author_book_table,
back_populates="books"
)
genres = db.relationship(
"Genre",
secondary=book_genre_table,
back_populates="books"
)
serie_id = db.Column(db.Integer, db.ForeignKey('book_serie.id'))
serie = db.relationship("BookSerie", back_populates="books")
class BookEdition(Model):
"""Book Edition"""
id = db.Column(db.Integer, primary_key=True)
tags = []
edition = None
isbn_10 = None
isbn_13 = None
ean_13 = None
sbn = None
amazon = None
google = None
goodreads = None
published_year = None
published_month = None
page_count = None
cover = None
rating = None
publisher = None
book_id = db.Column(db.Integer, db.ForeignKey('book.id'))
book = db.relationship("Book", back_populates="editions")
edition = db.Column(db.String(100))
isbn_10 = db.Column(db.String(10), comment="International Standard Book Number - v10")
isbn_13 = db.Column(db.String(13), comment="International Standard Book Number - v13")
ean_13 = db.Column(db.String(13), comment="European Article Number")
sbn = db.Column(db.String(13), comment="Standard Book Numbering")
asin = db.Column(db.String(10), comment="Amazon Standard Identification Numbers")
google_id = db.Column(db.String(14), comment="Google Books ID")
goodreads_id = db.Column(db.String(10), comment="Goodreads book ID")
published_year = db.Column(db.Integer)
published_month = db.Column(db.Integer)
page_count = db.Column(db.Integer)
publisher_id = db.Column(db.Integer, db.ForeignKey('publisher.id'))
publisher = db.relationship("Publisher", back_populates="books")
@validates('isbn_10')
def validate_isbn_10(self, key, value):
......@@ -53,3 +99,10 @@ class BookEdition(Model):
def validate_isbn_13(self, key, value):
validate_isbn13(value)
return value
class BookSerie(Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
language = db.Column(db.String(3), comment="ISO-639-2")
books = db.relationship("Book", back_populates="serie")
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
from __future__ import with_statement
import logging
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option('sqlalchemy.url',
current_app.config.get('SQLALCHEMY_DATABASE_URI'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}
"""Init
Revision ID: A00000000001_init
Revises:
Create Date: 2019-03-12 12:47:07.686608
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'A00000000001'
down_revision = None
branch_labels = None
depends_on = None
genre_data = [
{"fiction": 0, "name": "Art"},
{"fiction": 0, "name": "Autobiography"},
{"fiction": 0, "name": "Biography"},
{"fiction": 0, "name": "Book review"},
{"fiction": 0, "name": "Cookbook"},
{"fiction": 0, "name": "Diary"},
{"fiction": 0, "name": "Dictionary"},
{"fiction": 0, "name": "Encyclopedia"},
{"fiction": 0, "name": "Guide"},
{"fiction": 0, "name": "Health"},
{"fiction": 0, "name": "History"},
{"fiction": 0, "name": "Journal"},
{"fiction": 0, "name": "Math"},
{"fiction": 0, "name": "Memoir"},
{"fiction": 0, "name": "Prayer"},
{"fiction": 0, "name": "Religion, spirituality, and new age"},
{"fiction": 0, "name": "Textbook"},
{"fiction": 0, "name": "Review"},
{"fiction": 0, "name": "Science"},
{"fiction": 0, "name": "Self help"},
{"fiction": 0, "name": "Travel"},
{"fiction": 0, "name": "True crime"},
{"fiction": 1, "name": "Action and adventure"},
{"fiction": 1, "name": "Alternate history"},
{"fiction": 1, "name": "Anthology"},
{"fiction": 1, "name": "Chick lit"},
{"fiction": 1, "name": "Children's literature"},
{"fiction": 1, "name": "Comic book"},
{"fiction": 1, "name": "Coming-of-age"},
{"fiction": 1, "name": "Crime"},
{"fiction": 1, "name": "Drama"},
{"fiction": 1, "name": "Fairytale"},
{"fiction": 1, "name": "Fantasy"},
{"fiction": 1, "name": "Graphic novel"},
{"fiction": 1, "name": "Historical fiction"},
{"fiction": 1, "name": "Horror"},
{"fiction": 1, "name": "Mystery"},
{"fiction": 1, "name": "Paranormal romance"},
{"fiction": 1, "name": "Picture book"},
{"fiction": 1, "name": "Poetry"},
{"fiction": 1, "name": "Political thriller"},
{"fiction": 1, "name": "Romance"},
{"fiction": 1, "name": "Satire"},
{"fiction": 1, "name": "Science fiction"},
{"fiction": 1, "name": "Short story"},
{"fiction": 1, "name": "Suspense"},
{"fiction": 1, "name": "Thriller"},
{"fiction": 1, "name": "Young adult"},
]
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
'author',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=255), nullable=True),
sa.Column('sortable_name', sa.String(length=255), nullable=True),
sa.Column('goodreads_id', sa.String(length=10), nullable=True, comment='Goodreads author ID'),
sa.PrimaryKeyConstraint('id')
)
op.create_table(
'book_serie',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=255), nullable=True),
sa.Column('language', sa.String(length=3), nullable=True, comment='ISO-639-2'),
sa.PrimaryKeyConstraint('id')
)
genre_table = op.create_table(
'genre',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=255), nullable=True),
sa.Column('fiction', sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table(
'publisher',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table(
'book',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=255), nullable=True),
sa.Column('sortable_title', sa.String(length=255), nullable=True),
sa.Column('language', sa.String(length=3), nullable=True, comment='ISO-639-2'),
sa.Column('serie_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['serie_id'], ['book_serie.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table(
'author_book',
sa.Column('author_id', sa.Integer(), nullable=True),
sa.Column('book_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['author_id'], ['author.id'], ),
sa.ForeignKeyConstraint(['book_id'], ['book.id'], ),
sa.UniqueConstraint('author_id', 'book_id')
)
op.create_table(
'book_edition',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('book_id', sa.Integer(), nullable=True),
sa.Column('edition', sa.String(length=100), nullable=True),
sa.Column('isbn_10', sa.String(length=10), nullable=True, comment='International Standard Book Number - v10'),
sa.Column('isbn_13', sa.String(length=13), nullable=True, comment='International Standard Book Number - v13'),
sa.Column('ean_13', sa.String(length=13), nullable=True, comment='European Article Number'),
sa.Column('sbn', sa.String(length=13), nullable=True, comment='Standard Book Numbering'),
sa.Column('asin', sa.String(length=10), nullable=True, comment='Amazon Standard Identification Numbers'),
sa.Column('google_id', sa.String(length=14), nullable=True, comment='Google Books ID'),
sa.Column('goodreads_id', sa.String(length=10), nullable=True, comment='Goodreads book ID'),
sa.Column('published_year', sa.Integer(), nullable=True),
sa.Column('published_month', sa.Integer(), nullable=True),
sa.Column('page_count', sa.Integer(), nullable=True),
sa.Column('publisher_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['book_id'], ['book.id'], ),
sa.ForeignKeyConstraint(['publisher_id'], ['publisher.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table(
'book_genre',
sa.Column('book_id', sa.Integer(), nullable=True),
sa.Column('genre_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['book_id'], ['book.id'], ),
sa.ForeignKeyConstraint(['genre_id'], ['genre.id'], ),
sa.UniqueConstraint('book_id', 'genre_id')
)
op.bulk_insert(
genre_table,
genre_data
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('book_genre')
op.drop_table('book_edition')
op.drop_table('author_book')
op.drop_table('book')
op.drop_table('publisher')
op.drop_table('genre')
op.drop_table('book_serie')
op.drop_table('author')
# ### end Alembic commands ###
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment