Skip to content

Commit

Permalink
Setup repo, start schema creation
Browse files Browse the repository at this point in the history
  • Loading branch information
rafalp committed Oct 4, 2024
1 parent e2a2c0d commit eabaf40
Show file tree
Hide file tree
Showing 18 changed files with 350 additions and 1 deletion.
36 changes: 36 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Tests

on:
push:
branches:
- main
pull_request:

jobs:
build:

runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.12"]

steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install wheel
pip install -e .[test,dev]
- name: Pytest
run: |
pytest
- name: Linters
run: |
pylint example tests
mypy example --ignore-missing-imports --check-untyped-defs
black --check .
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# ariadne-graphql-modules-v2-example
An example GraphQL API implemented with Ariadne GraphQL Modules v2

An example GraphQL API implemented with Ariadne GraphQL Modules v2.

This API aims to use ALL features from GraphQL Modules v2. It can also be used as a reference for other developers.
6 changes: 6 additions & 0 deletions example/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from ariadne.asgi import GraphQL

from .schema import schema


app = GraphQL(schema, debug=True)
55 changes: 55 additions & 0 deletions example/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from typing import Any

from .fixture import get_data


class DataBase:
_data: dict[str, dict[int, Any]]
_id: int

def __init__(self, data: dict[str, dict[int, Any]], counter: int = 0):
self._data = data
self._id = counter

async def get_row(self, table: str, **kwargs) -> Any:
assert kwargs, "use kwargs to filter"

for row in self._data[table].values():
for attr, value in kwargs.items():
if getattr(row, attr) != value:
continue

return row

return None

async def get_all(self, table: str, **kwargs) -> list[Any]:
if not kwargs:
return list(self._data[table].values())

results: list[Any] = []
for row in self._data[table].values():
for attr, value in kwargs.items():
if getattr(row, attr) != value:
continue

results.append(row)

return results

async def insert(self, table: str, obj: Any):
assert obj.id is None, "obj.id attr must be None"

self._id += 1
obj.id = self._id

self._data[table][obj.id] = obj

async def update(self, table: str, obj: Any):
self._data[table][obj.id] = obj

async def delete(self, table: str, id: int):
self._data[table].pop(id, None)


db = DataBase(get_data(), 1000)
Empty file added example/enums/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions example/enums/groupfilter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from enum import StrEnum


class GroupFilter(StrEnum):
ALL = "all"
ADMIN = "admin"
MEMBER = "member"
47 changes: 47 additions & 0 deletions example/fixture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from typing import Any

from .models.group import Group
from .models.user import User


def get_data() -> dict[str, dict[int, Any]]:
return {
"groups": {
1: Group(
id=1,
name="Admins",
is_admin=True,
),
2: Group(
id=2,
name="Members",
is_admin=False,
),
},
"users": {
1: User(
id=1,
username="JohnDoe",
email="[email protected]",
group_id=1,
),
2: User(
id=2,
username="Alice",
email="[email protected]",
group_id=1,
),
3: User(
id=3,
username="Bob",
email="[email protected]",
group_id=2,
),
4: User(
id=4,
username="Mia",
email="[email protected]",
group_id=2,
),
},
}
Empty file added example/models/__init__.py
Empty file.
8 changes: 8 additions & 0 deletions example/models/group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from dataclasses import dataclass


@dataclass
class Group:
id: int
name: str
is_admin: bool
9 changes: 9 additions & 0 deletions example/models/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from dataclasses import dataclass


@dataclass
class User:
id: int
username: str
email: str
group_id: int
42 changes: 42 additions & 0 deletions example/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from ariadne_graphql_modules import GraphQLObject, make_executable_schema
from graphql import GraphQLResolveInfo

from .database import db
from .enums.groupfilter import GroupFilter
from .types.group import GroupType
from .types.user import UserType


class Query(GraphQLObject):
hello: str

@GraphQLObject.resolver("hello")
@staticmethod
def resolve_hello(obj, info: GraphQLResolveInfo) -> str:
return "Hello world!"

@GraphQLObject.field(args={"filter_": GraphQLObject.argument("filter")})
async def groups(obj, info: GraphQLResolveInfo, filter_: GroupFilter) -> list[GroupType]:
if filter_ == GroupFilter.ADMIN:
return await db.get_all("groups", is_admin=True)

if filter_ == GroupFilter.MEMBER:
return await db.get_all("groups", is_admin=False)

return await db.get_all("groups")

@GraphQLObject.field()
async def group(obj, info: GraphQLResolveInfo, id: str) -> GroupType | None:
try:
id_int = int(id)
except (TypeError, ValueError):
return None

return await db.get_row("groups", id=id_int)

@GraphQLObject.field()
async def users(obj, info: GraphQLResolveInfo) -> list[UserType]:
return await db.get_all("users")


schema = make_executable_schema(Query, convert_names_case=True)
Empty file added example/types/__init__.py
Empty file.
21 changes: 21 additions & 0 deletions example/types/group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from typing import TYPE_CHECKING, Annotated

from ariadne_graphql_modules import GraphQLID, GraphQLObject, deferred
from graphql import GraphQLResolveInfo

from ..database import db
from ..models.group import Group

if TYPE_CHECKING:
from .user import UserType


class GroupType(GraphQLObject):
id: GraphQLID
name: str
is_admin: bool

@GraphQLObject.field(graphql_type=list[Annotated["UserType", deferred(".user")]])
@staticmethod
async def users(obj: Group, info: GraphQLResolveInfo):
return await db.get_all("users", group_id=obj.id)
23 changes: 23 additions & 0 deletions example/types/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from typing import TYPE_CHECKING, Annotated

from ariadne_graphql_modules import GraphQLID, GraphQLObject, deferred
from graphql import GraphQLResolveInfo

from ..database import db
from ..models.group import Group
from ..models.user import User

if TYPE_CHECKING:
from .group import GroupType


class UserType(GraphQLObject):
id: GraphQLID
username: str
email: str
group: Annotated["GroupType", deferred(".group")]

@GraphQLObject.resolver("group")
@staticmethod
async def resolve_group(user: User, info: GraphQLResolveInfo) -> Group:
return await db.get_row("groups", id=user.group_id)
64 changes: 64 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
[project]
name = "ariadne-graphql-modules-v2-example"
version = "0.1.0"
description = "An example GraphQL API implemented using the Ariadne GraphQL Modules v2."
authors = [{ name = "Rafał Pitoń", email = "[email protected]" }]
readme = "README.md"
license = { file = "LICENSE" }
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Environment :: Web Environment",
"Programming Language :: Python",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"ariadne>=0.23.0",
"ariadne-graphql-modules@git+https://github.com/mirumee/ariadne-graphql-modules@next-api",
]

[project.optional-dependencies]
dev = ["black", "mypy", "pylint"]
test = [
"pytest",
"pytest-asyncio",
"pytest-benchmark",
"pytest-cov",
"pytest-mock",
"freezegun",
"syrupy",
"werkzeug",
"httpx",
"opentracing",
"opentelemetry-api",
"python-multipart>=0.0.5",
"aiodataloader",
"graphql-sync-dataloaders;python_version>\"3.7\"",
]

[tool.black]
line-length = 88
target-version = ['py36', 'py37', 'py38']
include = '\.pyi?$'
exclude = '''
/(
\.eggs
| \.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
| snapshots
)/
'''

[tool.pytest.ini_options]
asyncio_mode = "strict"
testpaths = ["tests"]
Empty file added tests/__init__.py
Empty file.
21 changes: 21 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import pytest
from graphql import graphql

from example.schema import schema


@pytest.fixture
def exec_query():
async def exec_query_(
document: str,
variables: dict | None = None,
operation: str | None = None,
):
return await graphql(
schema,
document,
variable_values=variables,
operation_name=operation,
)

return exec_query_
7 changes: 7 additions & 0 deletions tests/test_query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import pytest


@pytest.mark.asyncio
async def test_query_hello_field(exec_query):
result = await exec_query("{ hello }")
assert result.data == {"hello": "Hello world!"}

0 comments on commit eabaf40

Please sign in to comment.