Skip to content

Commit

Permalink
add magnum opus cql example
Browse files Browse the repository at this point in the history
remove storage.db from git
  • Loading branch information
philtweir committed Jul 1, 2020
1 parent 9c484b0 commit f0b2620
Show file tree
Hide file tree
Showing 85 changed files with 1,608 additions and 0 deletions.
3 changes: 3 additions & 0 deletions 021-conical-domain/magnum_opus/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.egg-info
.tox
Pipfile
23 changes: 23 additions & 0 deletions 021-conical-domain/magnum_opus/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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 .

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

USER user

EXPOSE 5000

ENTRYPOINT []

CMD gunicorn --config ./gunicorn_config.py magnumopus.index:app

COPY magnumopus magnumopus
1 change: 1 addition & 0 deletions 021-conical-domain/magnum_opus/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

5 changes: 5 additions & 0 deletions 021-conical-domain/magnum_opus/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Magnum Opus
===========

This recipe maker
and averaged over repeated attempts, returning an average round trip time.
9 changes: 9 additions & 0 deletions 021-conical-domain/magnum_opus/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)
6 changes: 6 additions & 0 deletions 021-conical-domain/magnum_opus/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: "3"
services:
web:
build: .
ports:
- 5000:5000
5 changes: 5 additions & 0 deletions 021-conical-domain/magnum_opus/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
Empty file.
51 changes: 51 additions & 0 deletions 021-conical-domain/magnum_opus/magnumopus/alembic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from .substance import Substance

class NotEnoughSubstancesToMixException(Exception):
pass

class UnknownProcessException(Exception):
pass


MIXTURES = {
('Mercury', 'Salt', 'Sulphur'): 'Gloop'
}


class Alembic:
_nature_of_unknown_mixture = 'Sludge'

@staticmethod
def _produce(nature):
return Substance(nature=nature)

def mix(self, *substances):
if len(substances) < 2:
raise NotEnoughSubstancesToMixException()

constituents = [substance.nature for substance in substances]

# This gives us a canonical, ordered way of expressing our
# constituents that we can use as a recipe look-up
ingredient_list = tuple(sorted(constituents))

try:
nature = MIXTURES[ingredient_list]
except KeyError:
nature = self._nature_of_unknown_mixture

return self._produce(nature)

def process(self, process_name, substance):
if process_name == 'ferment':
result = substance.ferment()
elif process_name == 'cook':
result = substance.cook()
elif process_name == 'wash':
result = substance.wash()
elif process_name == 'pickle':
result = substance.pickle()
else:
raise UnknownProcessException()

return result
3 changes: 3 additions & 0 deletions 021-conical-domain/magnum_opus/magnumopus/index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import create_app

app = create_app()
5 changes: 5 additions & 0 deletions 021-conical-domain/magnum_opus/magnumopus/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import logging

def init_app(app):
app.logger.addHandler(logging.StreamHandler())
app.logger.setLevel(logging.INFO)
5 changes: 5 additions & 0 deletions 021-conical-domain/magnum_opus/magnumopus/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .base import db

def init_app(app):
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db.init_app(app)
3 changes: 3 additions & 0 deletions 021-conical-domain/magnum_opus/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()
23 changes: 23 additions & 0 deletions 021-conical-domain/magnum_opus/magnumopus/models/substance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class SubstanceMustBeFreshToProcessException(Exception):
pass

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

def _process(self, process_name):
self.state.append(process_name)
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')
12 changes: 12 additions & 0 deletions 021-conical-domain/magnum_opus/magnumopus/pantry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Pantry:
def __init__(self):
self._cupboard = []

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

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

def count_all_substances(self):
return len(self._cupboard)
Empty file.
14 changes: 14 additions & 0 deletions 021-conical-domain/magnum_opus/magnumopus/repositories/pantry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class Pantry:
_cupboard = []

def __init__(self):
pass

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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from flask_restful import Api
from . import substance
from . import alembic_instruction

def init_app(app):
api = Api(app)
substance.init_app(app, api)
alembic_instruction.init_app(app, api)
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from flask_restful import Resource, reqparse
from ..repositories.pantry import Pantry
from ..models.substance import Substance
from ..schemas.substance_schema import SubstanceSchema
from ..services.alembic_instruction_handler import AlembicInstructionHandler, AlembicInstruction

parser = reqparse.RequestParser()
parser.add_argument('instruction_type')
parser.add_argument('action')
parser.add_argument('natures')

substance_schema = SubstanceSchema()

class AlembicInstructionResource(Resource):
def get(self):
"""This should return past requests/commands."""
pass

def post(self):
"""
Add an instruction for the alembic.
Note that POST is _not_ assumed to be idempotent, unlike PUT
"""

args = parser.parse_args()
instruction_type = args['instruction_type']

pantry = Pantry()

instruction_handler = AlembicInstructionHandler()

# This could do with deserialization...
instruction = AlembicInstruction(
instruction_type=args.instruction_type,
natures=args.natures.split(','),
action=args.action
)

# Crude start at DI... see flask-injector
result = instruction_handler.handle(instruction, pantry)

pantry.add_substance(result)

return substance_schema.dump(result)


def init_app(app, api):
api.add_resource(AlembicInstructionResource, '/alembic_instruction')
37 changes: 37 additions & 0 deletions 021-conical-domain/magnum_opus/magnumopus/resources/substance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from flask_restful import Resource, reqparse
from ..repositories.pantry import Pantry
from ..models.substance import Substance
from ..schemas.substance_schema import SubstanceSchema

parser = reqparse.RequestParser()
parser.add_argument('nature')

substance_schema = SubstanceSchema()
substances_schema = SubstanceSchema(many=True)


class SubstanceResource(Resource):
def get(self):
args = parser.parse_args()
nature = args['nature']
pantry = Pantry()

substances = pantry.find_substances_by_nature(nature)

return substances_schema.dump(substances)

def post(self):
args = parser.parse_args()
nature = args['nature']

pantry = Pantry()

substance = Substance(nature=nature)

pantry.add_substance(substance)

return substance_schema.dump(substance)


def init_app(app, api):
api.add_resource(SubstanceResource, '/substance')
6 changes: 6 additions & 0 deletions 021-conical-domain/magnum_opus/magnumopus/schemas/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from flask_marshmallow import Marshmallow

ma = Marshmallow()

def init_app(app):
ma.init_app(app)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from marshmallow import fields

from . import ma

from ..services.assessor import assess_whether_substance_is_philosophers_stone

class SubstanceSchema(ma.Schema):
is_philosophers_stone = fields.Function(
assess_whether_substance_is_philosophers_stone
)

class Meta:
fields = ("nature", "is_philosophers_stone")
Empty file.
51 changes: 51 additions & 0 deletions 021-conical-domain/magnum_opus/magnumopus/services/alembic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from ..models.substance import Substance

class NotEnoughSubstancesToMixException(Exception):
pass

class UnknownProcessException(Exception):
pass


MIXTURES = {
('Mercury', 'Salt', 'Sulphur'): 'Gloop'
}


class Alembic:
_nature_of_unknown_mixture = 'Sludge'

@staticmethod
def _produce(nature):
return Substance(nature=nature)

def mix(self, *substances):
if len(substances) < 2:
raise NotEnoughSubstancesToMixException()

constituents = [substance.nature for substance in substances]

# This gives us a canonical, ordered way of expressing our
# constituents that we can use as a recipe look-up
ingredient_list = tuple(sorted(constituents))

try:
nature = MIXTURES[ingredient_list]
except KeyError:
nature = self._nature_of_unknown_mixture

return self._produce(nature)

def process(self, process_name, substance):
if process_name == 'ferment':
result = substance.ferment()
elif process_name == 'cook':
result = substance.cook()
elif process_name == 'wash':
result = substance.wash()
elif process_name == 'pickle':
result = substance.pickle()
else:
raise UnknownProcessException()

return result
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from dataclasses import dataclass

from .alembic import Alembic
from ..repositories.pantry import Pantry

@dataclass
class AlembicInstruction:
instruction_type: str
natures: list
action: str = ''

class AlembicInstructionHandler:
def handle(self, instruction: AlembicInstruction, pantry: Pantry):
natures = instruction.natures
action = instruction.action
instruction_type = instruction.instruction_type

# Clearly need some validation here!
substances = [pantry.find_substances_by_nature(nature)[0] for nature in natures]

alembic = Alembic()

if instruction_type == 'mix':
result = alembic.mix(*substances)
elif instruction_type == 'process':
result = alembic.process(action, substances[0])
else:
pass
# a sensible error

return result
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def assess_whether_substance_is_philosophers_stone(substance):
return substance.nature == 'Gloop' and substance.state == ['cooked', 'washed', 'pickled', 'fermented']
Loading

0 comments on commit f0b2620

Please sign in to comment.