<jupyter_start><jupyter_text>GraphQL: The Devil's API 👹Or: How I Learned to Stop Worrying and Love the **DataLoader***Thank you [Tatari](https://www.tatari.tv/) for your support!*- <NAME>- <EMAIL>- github.com/lame- linkedin.com/in/kuhl- https://www.meetup.com/homebrew-learning-club/  What the Heck is GraphQL?GraphQL is two things: a **query language** and an **API**...   Query Language- A descriptive structure of our API - Replaces all those swagger docs you swear are 99% up to date but are really a closer to a complete disaster.```graphqlquery DanBrownNovels{  author(name: "<NAME>"){    books{      edges{        node{          title          isDemonic        }      }    }  }}```  The APIGives our users a way of traversing our connected data```pythonclass LamborghiniQL(SQLAlchemyObjectType):    class Meta:        model = LamborghiniModel        interfaces = (relay.Node,)        is_diablo = graphene.Boolean()        def resolve_is_diablo(self, info):        return self.model == 'diablo'```  Pit Stop: The GQL Package LandscapeWe need three things:1. Database2. ORM3. GQL Lib  Pit Stop: The GQL Package Landscape Database- SQLite3 for this example- In production, use something that scales!!  Pit Stop: The GQL Package Landscape ORMSQLAlchemy- Industry standard from my experience- Peewee sounds cool, but I haven't used it. Maybe next time!  Pit Stop: The GQL Package Landscape Python/GraphQL libs[Ariadne](https://pypi.org/project/ariadne/)- Pronounced "R-E-ad-knee", from greek mythology 🤷- 1.7k stars, 45 contributors on [GitHub](https://github.com/mirumee/ariadne),  Pit Stop: The GQL Package Landscape Python/GraphQL libs[Graphene](https://pypi.org/project/graphene/)- Easy to pronounce, named after super rad game-changing material- 7.1k stars, 162 contributors on [GitHub](https://github.com/graphql-python/graphene)  Let's Make an API Models<jupyter_code># Set up Root dir
import os
import sys
root = os.path.abspath(os.path.join(os.path.curdir, './flask_sqlalchemy/'))
sys.path.append(root)

# Import db stuff
from database import Base, init_db
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, func, Table
from sqlalchemy.orm import backref, relationship

# Initialize the db schema and stuff
init_db()
class CompanyModel(Base):
    __tablename__ = "companies"
    id = Column(Integer, primary_key=True)
    name = Column(String)
    
class DepartmentModel(Base):
    __tablename__ = "departments"
    id = Column(Integer, primary_key=True)
    name = Column(String)
    company_id = Column(Integer, ForeignKey("companies.id"))
    company = relationship(
        Company, 
        backref="departments", 
        uselist=False, 
        cascade="delete,all"
    )
class EmployeeModel(Base):
    __tablename__ = "employees"
    id = Column(Integer, primary_key=True)
    name = Column(String)
    department_id = Column(
        Integer, 
        ForeignKey("departments.id")
    )
    department = relationship(
        Department, 
        backref=backref(
            "employees", 
            uselist=True, 
            cascade="delete,all"
        )
    )<jupyter_output><empty_output><jupyter_text>Let's Make and API GraphQL SchemaFinally right?! He's been talking for like half an hour<jupyter_code>import graphene
from graphene import relay
from graphene_sqlalchemy import SQLAlchemyConnectionField, SQLAlchemyObjectType
from flask_graphql import GraphQLView
class CompanyQL(SQLAlchemyObjectType):
    class Meta:
        model = CompanyModel        # The model SQLA maps to node
        interfaces = (relay.Node,)  # Object state
        

class DepartmentQL(SQLAlchemyObjectType):
    class Meta:
        model = DepartmentModel
        interfaces = (relay.Node,)
        
        
class EmployeeQL(SQLAlchemyObjectType):
    class Meta:
        model = EmployeeModel
        interfaces = (relay.Node,)
# Create the root node

class Query(graphene.ObjectType):
    node = relay.Node.Field()

    companies = (
        SQLAlchemyConnectionField(
            CompanyQL.connection
        )
    )
    departments = (
        SQLAlchemyConnectionField(
            DepartmentQL.connection
        )
    )
    employees = (
        SQLAlchemyConnectionField(
            EmployeeQL.connection
        )
    )
# Make the root node discoverable
schema = graphene.Schema(query=Query)

app = Flask(__name__)
app.debug = True


app.add_url_rule(
    "/graphql", 
    view_func=GraphQLView.as_view(
        "graphql", 
        schema=schema, 
        graphiql=True
    )
)<jupyter_output><empty_output><jupyter_text>Live Demo Time!!!What could go wrong?!![Really Funny Gif](https://c.tenor.com/Xw-PgnWOUBYAAAAC/eating-popcorn-im-watching.gif)  Geez, That Latency ThoInherent in GraphQL's ladder structure and context:```||--- Employees|-- Departments|- Company|``` ```pythoncompanies = db_session.query(Company).all()for company in companies:    departments = (        db_session.query(Departments)        .filter_by(company_id=company.id)        .all()    )    for department in departments:        employees = (            db_session.query(Employees)            .filter_by(department_id=department.id)            .all()        )``` So if we have 1 company, with 10 departments and 100 employees:**not12 DB queries**If we have 100 companies, each with 10 departments, each department with 100 employees:1 + 100 + 100 * 10 = **1101 queries** 😨  Dataloaders to the rescue!!!- We don't need to do independent queries at each level- we can batch them!  What the heck is a promise- Introduce some async to our process- Sort of like generators1. Resolver requests an object2. Resolver queries DataLoader for obj3. DataLoader returns Promise()4. Promises get resolved at access time<jupyter_code># TODO: Pretend you know more about this and it's just really intuitive
from promise import Promise

def _resolve(call):
    pass
    
def _reject():
    pass

# Taken from Promise docs https://github.com/syrusakbary/promise#usage
promise = Promise(
    lambda resolver, rejector: _resolve(resolver)
)
promise = promise.resolve('')
print(f'Value {promise.get()}')
print(f'Fulfilled {promise.is_fulfilled}')
# See... super simple

promise = Promise(executor=baz_resolve, scheduler=baz_reject)
promise.reject(ValueError("Wasn't feeling it"))<jupyter_output><empty_output><jupyter_text>Let's make a DataLoader<jupyter_code>import promise
from promise.dataloader import DataLoader

class DepartmentDataLoader(DataLoader):
    def batch_load_fn(self, company_ids) -> Promise:
        # company_ids is an unordered list
        
        departments_by_company_id = defaultdict(list)
        departments = (
            db_session.query(DepartmentModel)
            .filter(DepartmentModel.company_id.in_(company_ids))
            .all()
        )
        for department in departments:
            departments_by_company_id[department.company_id].append(department)

        # Need to return in same order as company_ids    
        return Promise.resolve(
            [departments_by_company_id[company_id] for company_id in company_ids]
        )<jupyter_output><empty_output><jupyter_text>Let's See Our DataLoader Gainz 💪<jupyter_code>from base64 import b64decode
b64decode(b'RW1wbG95ZWVRTDox')

# TODO LIST:
#
# Remake in G-Slides
# Making a GraphQL server, not a client!
# Requests package to query GraphQL
# Get to the query **earlier**
# Intro fragments
# More time talking about plus/minus, nodes/edges, add pictures!
# Optimization - tell em what you're gonna tell em, tell em, **tell em what you told em**<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09521922873745378, 'fraction_numerical': 0.007527733755942948, 'mean_word_length': 3.4704840613931522, 'pattern_counts': {'":': 0, '<': 24, '<?xml version=': 0, '>': 25, 'https://': 8, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '613928', 'license': 'MIT', 'n_tokens_mistral': 2398, 'n_tokens_neox': 2236, 'n_words': 662, 'path': 'Presentation.ipynb', 'repo_name': 'lame/pycon-graphql', 'size': '7584'}