Skip to content

Commit

Permalink
add graphql intro
Browse files Browse the repository at this point in the history
  • Loading branch information
philtweir committed Jun 28, 2020
1 parent 12df011 commit 9f719b6
Show file tree
Hide file tree
Showing 51 changed files with 5,736 additions and 0 deletions.
Binary file added 022-burette/magnum_opus_iii/.coverage
Binary file not shown.
5 changes: 5 additions & 0 deletions 022-burette/magnum_opus_iii/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.egg-info
.tox
Pipfile
.env
.nenv
24 changes: 24 additions & 0 deletions 022-burette/magnum_opus_iii/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_iii/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_iii/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_iii/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_iii/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: "3"
services:
web_async_gql:
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_iii/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_iii/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_iii/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
23 changes: 23 additions & 0 deletions 022-burette/magnum_opus_iii/magnumopus/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from quart_openapi import Pint

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

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)
modules += graph.init_app(app)

injection.init_app(app, modules)

return app
7 changes: 7 additions & 0 deletions 022-burette/magnum_opus_iii/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')
57 changes: 57 additions & 0 deletions 022-burette/magnum_opus_iii/magnumopus/graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from quart import request, jsonify
from ariadne import ObjectType, make_executable_schema, graphql
from ariadne.constants import PLAYGROUND_HTML

from .repositories import PANTRY_STORES

PANTRY = PANTRY_STORES['list']

type_defs = """
type Query {
substances(nature: String!): [Substance]
}
type Substance {
nature: String!,
state: [String]!
}
"""

query = ObjectType('Query')
substance = ObjectType('Substance')

@query.field('substances')
def resolve_substances(obj, *_, nature='Unknown'):
return PANTRY.find_substances_by_nature(nature)


@substance.field('nature')
def resolve_nature(obj, *_):
return obj.nature

@substance.field('state')
def resolve_state(obj, *_):
return obj.state

schema = make_executable_schema(type_defs, query, substance)

async def graphql_server():
data = await request.get_json()
success, result = await graphql(
schema,
data,
context_value=request
)

return jsonify(result), (200 if success else 400)

def init_app(app):
app.route('/graphql', methods=['GET'], endpoint='graphql_playground')(
lambda: (PLAYGROUND_HTML, 200)
)

app.route('/graphql', methods=['POST'], endpoint='graphql_server')(
graphql_server
)

return []
3 changes: 3 additions & 0 deletions 022-burette/magnum_opus_iii/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_iii/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_iii/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_iii/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_iii/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_iii/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_iii/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_iii/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_iii/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_iii/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
Loading

0 comments on commit 9f719b6

Please sign in to comment.