Skip to content

Commit

Permalink
asynchronous quart server
Browse files Browse the repository at this point in the history
  • Loading branch information
philtweir committed Jun 28, 2020
1 parent 1c0b991 commit 12df011
Show file tree
Hide file tree
Showing 50 changed files with 5,675 additions and 0 deletions.
Binary file added 022-burette/magnum_opus_ii/.coverage
Binary file not shown.
4 changes: 4 additions & 0 deletions 022-burette/magnum_opus_ii/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.egg-info
.tox
Pipfile
.env
24 changes: 24 additions & 0 deletions 022-burette/magnum_opus_ii/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM python:3-alpine

RUN addgroup -S user && adduser user -S -G user

WORKDIR /home/user/

COPY requirements.txt .
COPY gunicorn_config.py .
COPY setup.py .
COPY setup.cfg .
COPY init_entrypoint.sh /

RUN pip install hypercorn
RUN pip install -r requirements.txt

USER user

EXPOSE 5000

ENTRYPOINT []

CMD hypercorn --config python:./gunicorn_config.py magnumopus.index:app

COPY magnumopus magnumopus
1 change: 1 addition & 0 deletions 022-burette/magnum_opus_ii/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

46 changes: 46 additions & 0 deletions 022-burette/magnum_opus_ii/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Magnum Opus
===========

Recipe maker for the philosopher's stone.


python3 -m pytest --cov magnumopus >> README.md

============================= test session starts ==============================
platform linux -- Python 3.8.2, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: /home/philtweir/Work/Training/PythonCourse/python-course/022-burette/magnum_opus
plugins: pep8-1.0.6, cov-2.10.0
collected 15 items

tests/domain/test_alembic.py ....... [ 46%]
tests/domain/test_pantry.py .. [ 60%]
tests/domain/test_substance.py ...... [100%]

----------- coverage: platform linux, python 3.8.2-final-0 -----------
Name Stmts Miss Cover
------------------------------------------------------------------------
magnumopus/__init__.py 10 8 20%
magnumopus/config.py 4 4 0%
magnumopus/index.py 2 2 0%
magnumopus/initialize.py 6 6 0%
magnumopus/logger.py 4 4 0%
magnumopus/models/__init__.py 3 1 67%
magnumopus/models/base.py 2 0 100%
magnumopus/models/substance.py 24 0 100%
magnumopus/repositories/__init__.py 0 0 100%
magnumopus/repositories/pantry.py 12 1 92%
magnumopus/repositories/sqlalchemy_pantry.py 15 15 0%
magnumopus/resources/__init__.py 7 7 0%
magnumopus/resources/alembic_instruction.py 26 26 0%
magnumopus/resources/substance.py 28 28 0%
magnumopus/schemas/__init__.py 4 4 0%
magnumopus/schemas/substance_schema.py 11 11 0%
magnumopus/services/__init__.py 0 0 100%
magnumopus/services/alembic.py 32 2 94%
magnumopus/services/alembic_instruction_handler.py 20 20 0%
magnumopus/services/assessor.py 2 2 0%
------------------------------------------------------------------------
TOTAL 212 141 33%


============================== 15 passed in 0.38s ==============================
9 changes: 9 additions & 0 deletions 022-burette/magnum_opus_ii/RULES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
* One unit of each of the substances Mercury, Salt and Sulphur are mixed, using my "Alembic" (mixing pot), giving one unit of another substance, Gloop
* Any attempt to mix anything other than those three substances, gives Sludge, another substance
* Substances can undergo several Processes in my Alembic - they can be Cooked, Washed, Pickled or Fermented
* If Gloop is Cooked, Washed, Pickled and Fermented, in that order, it is the Philosopher's Stone (panacea and cure of all ills)
[* To process a Substance, at least one unit must be in my Pantry, including Gloop - even when freshly processed/created, it must be stored there before re-use (to cool)]

Final rule:
GROUP 1: When I process a substance, using any process, it becomes a different substance
GROUP 2: When I process a substance, its state changes but is essentially the same substance (NB: mixing is not a process)
11 changes: 11 additions & 0 deletions 022-burette/magnum_opus_ii/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: "3"
services:
web_async:
build: .
environment:
DATABASE_URI: 'sqlite:////docker/storage/storage.db'
volumes:
- ./docker:/docker
- ./magnumopus:/home/user/magnumopus
ports:
- 5000:5000
Binary file not shown.
5 changes: 5 additions & 0 deletions 022-burette/magnum_opus_ii/gunicorn_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
port = '5000'
bind = "0.0.0.0:%s" % port
workers = 1
timeout = 600
reload = False
6 changes: 6 additions & 0 deletions 022-burette/magnum_opus_ii/init_containers.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh

mkdir -p docker/storage

docker-compose run --user root web /init_entrypoint.sh
docker-compose run web python3 -m magnumopus.initialize
3 changes: 3 additions & 0 deletions 022-burette/magnum_opus_ii/init_entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

chown -R user:user /docker/storage
22 changes: 22 additions & 0 deletions 022-burette/magnum_opus_ii/magnumopus/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from quart_openapi import Pint

def create_app():
from . import models, resources, schemas, logger, repositories, injection

app = Pint(__name__, title='Magnum Opus')

# This could be used to separate by environment
app.config.from_object('magnumopus.config.Config')

# This helps avoid cyclic dependencies
modules = []

modules += logger.init_app(app)
modules += models.init_app(app)
modules += resources.init_app(app)
modules += repositories.init_app(app)
modules += schemas.init_app(app)

injection.init_app(app, modules)

return app
7 changes: 7 additions & 0 deletions 022-burette/magnum_opus_ii/magnumopus/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import os

class Config:
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI', 'sqlite:///:memory:')
SQLALCHEMY_TRACK_MODIFICATIONS = False

PANTRY_STORE = os.environ.get('MAGNUMOPUS_PANTRY_STORE', 'list')
3 changes: 3 additions & 0 deletions 022-burette/magnum_opus_ii/magnumopus/index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import create_app

app = create_app()
9 changes: 9 additions & 0 deletions 022-burette/magnum_opus_ii/magnumopus/initialize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from . import create_app
from .models import db

if __name__ == "__main__":
# There are nicer ways around this, but this keeps it clear for an example
app = create_app()

with app.app_context():
db.create_all()
26 changes: 26 additions & 0 deletions 022-burette/magnum_opus_ii/magnumopus/injection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from injector import Injector
from quart.views import MethodViewType

class ViewInjectorMeta(MethodViewType):
def get_injector(self):
return self.get_app().extensions['injector']

def __call__(cls, *args, **kwargs):
return cls.get_injector().create_object(
cls.__bases__[0],
additional_kwargs=kwargs
)

def init_app(app, modules):
modules.append(configure_injector)

injctr = Injector()
for module in modules:
injctr.binder.install(module)
app.extensions['injector'] = injctr

def configure_injector(binder):
"""
Add any general purpose injectables, not included in a submodule
"""
pass
6 changes: 6 additions & 0 deletions 022-burette/magnum_opus_ii/magnumopus/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import logging

def init_app(app):
app.logger.addHandler(logging.StreamHandler())
app.logger.setLevel(logging.INFO)
return []
11 changes: 11 additions & 0 deletions 022-burette/magnum_opus_ii/magnumopus/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from .base import db
from flask_sqlalchemy import SQLAlchemy

def init_app(app):
# db.init_app(app)
return [configure_injector]

def configure_injector(binding):
binding.bind(
SQLAlchemy, to=db
)
3 changes: 3 additions & 0 deletions 022-burette/magnum_opus_ii/magnumopus/models/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
40 changes: 40 additions & 0 deletions 022-burette/magnum_opus_ii/magnumopus/models/substance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from . import db
from sqlalchemy_utils import ScalarListType

class SubstanceMustBeFreshToProcessException(Exception):
pass

class Substance(db.Model):
__tablename__ = 'substances'

id = db.Column(db.Integer, primary_key=True)
nature = db.Column(db.String(32), default='Unknown')
state = db.Column(ScalarListType())

def __init__(self, nature='Unknown'):
self.state = []
self.nature = nature

super(Substance, self).__init__()

def _process(self, process_name):
# Example of leakage of persistence behaviour into
# domain, due to db.Model -- we must copy the state list to
# ensure it is seen to change...
state = self.state[:]
state.append(process_name)
self.state = state

return self

def cook(self):
return self._process('cooked')

def pickle(self):
return self._process('pickled')

def ferment(self):
return self._process('fermented')

def wash(self):
return self._process('washed')
18 changes: 18 additions & 0 deletions 022-burette/magnum_opus_ii/magnumopus/repositories/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from .pantry import Pantry
from .list_pantry import ListPantry
from .sqlalchemy_pantry import SQLAlchemyPantry

PANTRY_STORES = {
'sqlalchemy': SQLAlchemyPantry,
'list': ListPantry() # we instantiate this, as we want a singleton cupboard
}

def init_app(app):
return [lambda binder: configure_injector(binder, app)]

def configure_injector(binder, app):
pantry_cls = PANTRY_STORES[app.config['PANTRY_STORE']]

binder.bind(
Pantry, to=pantry_cls
)
26 changes: 26 additions & 0 deletions 022-burette/magnum_opus_ii/magnumopus/repositories/list_pantry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class ListPantry:
def __init__(self):
self._cupboard = []

def empty_pantry(self):
self._cupboard = []

def add_substance(self, substance):
self._cupboard.append(substance)

def find_substances_by_nature(self, nature):
return [substance for substance in self._cupboard if substance.nature == nature]

def count_all_substances(self):
return len(self._cupboard)

def commit(self):
# Give each substance an ID, if it does not have one
free_ids = iter(
set(range(1, len(self._cupboard) + 1)) -
set([substance.id for substance in self._cupboard if substance])
)

for substance in self._cupboard:
if not substance.id:
substance.id = next(free_ids)
25 changes: 25 additions & 0 deletions 022-burette/magnum_opus_ii/magnumopus/repositories/pantry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from abc import ABC, abstractmethod

class Pantry(ABC):
def __init__(self):
pass

@abstractmethod
def do_empty(self):
pass

@abstractmethod
def add_substance(self, substance):
pass

@abstractmethod
def find_substances_by_nature(self, nature):
pass

@abstractmethod
def count_all_substances(self):
pass

@abstractmethod
def commit(self):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from flask_sqlalchemy import SQLAlchemy
from injector import inject

from ..models.substance import Substance
from ..models import db

class SQLAlchemyPantry:
@inject
def __init__(self, db: SQLAlchemy):
self._db = db

def empty_pantry(self):
Substance.query.delete()

# A more involved solution would open and close the pantry... like
# a cupboard door, or a Unit of Work

# Note how we're committing too frequently?
def add_substance(self, substance):
self._db.session.add(substance)
return substance

def find_substances_by_nature(self, nature):
substances = Substance.query.filter_by(nature=nature).all()
return substances

def count_all_substances(self):
return Substance.query.count()

def commit(self):
self._db.session.commit()
7 changes: 7 additions & 0 deletions 022-burette/magnum_opus_ii/magnumopus/resources/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from . import substance
from . import alembic_instruction

def init_app(app):
substance.init_app(app)
alembic_instruction.init_app(app)
return []
Loading

0 comments on commit 12df011

Please sign in to comment.