Skip to content

Commit

Permalink
[GraphQL] Init Strawberry gql. (#33)
Browse files Browse the repository at this point in the history
* start configure strawberry

* feat: fix errors.

* feat:  init strawberry logic and architecture

* fix: CI errors
  • Loading branch information
Angel-Dijoux authored Jan 21, 2024
1 parent c6b128e commit 70ede88
Show file tree
Hide file tree
Showing 16 changed files with 345 additions and 118 deletions.
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ marshmallow-sqlalchemy = "*"
apispec = "*"
chardet = "*"
charset-normalizer = "*"
strawberry-graphql = {extras = ["debug-server"], version = "*"}

[dev-packages]

Expand Down
303 changes: 224 additions & 79 deletions Pipfile.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec_webframeworks.flask import FlaskPlugin


db: SQLAlchemy = SQLAlchemy()
plugins = [
FlaskPlugin(),
Expand Down Expand Up @@ -63,12 +64,14 @@ def register_blueprints(app: Flask):
from src.blueprints.formations import formations
from src.blueprints.utils import utils
from src.blueprints.legal.views import legal
from src.blueprints.graphql import graphql

app.register_blueprint(utils)
app.register_blueprint(auth)
app.register_blueprint(legal)
app.register_blueprint(favoris)
app.register_blueprint(formations)
app.register_blueprint(graphql)


def _set_log_levels():
Expand Down
13 changes: 13 additions & 0 deletions src/api/favoris/favori_resolver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import strawberry

from src.business_logic.favoris.get_favoris import get_favoris_by_user_id
from src.business_logic.formation.scrap.types import FormationsWithTotal

# https://strawberry.rocks/docs/integrations/flask#options


@strawberry.type
class FavorisResolver:
get_favoris_by_user_id: FormationsWithTotal = strawberry.field(
resolver=get_favoris_by_user_id
)
12 changes: 12 additions & 0 deletions src/api/formation/formation_resolver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from src.business_logic.formation.get_formation_details import (
FormationDetails,
get_formation_by_id,
)
import strawberry


@strawberry.type
class FormationResolver:
get_formation_by_id: FormationDetails = strawberry.field(
resolver=get_formation_by_id
)
22 changes: 22 additions & 0 deletions src/api/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from src.api.favoris.favori_resolver import FavorisResolver
from src.api.formation.formation_resolver import FormationResolver
import strawberry

from strawberry.tools import merge_types
from strawberry.extensions import QueryDepthLimiter
from strawberry.extensions import MaxTokensLimiter
from strawberry.extensions import MaxAliasesLimiter

types: tuple = (FormationResolver, FavorisResolver)

Queries = merge_types("Queries", types)

schema = strawberry.Schema(
query=Queries,
extensions=[
QueryDepthLimiter(max_depth=10),
MaxTokensLimiter(max_token_count=1000),
MaxAliasesLimiter(max_alias_count=15),
],
)
# https://strawberry.rocks/docs/guides/tools
12 changes: 12 additions & 0 deletions src/blueprints/graphql.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from flask import Blueprint, Response
from strawberry.flask.views import GraphQLView

from src.api.schema import schema

graphql = Blueprint("graphql", __name__, url_prefix="/api/graphql")


@graphql.route("/", methods=["POST", "GET"])
def gql() -> tuple[Response, int]:
view = GraphQLView.as_view("graphql_view", schema=schema)
return view()
3 changes: 1 addition & 2 deletions src/business_logic/favoris/get_favoris.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from src import db
from src.business_logic.formation.scrap.types import (
FormationIsFavorite,
FormationsWithTotal,
)

from src import db
from src.models.formation import Formation
from src.models.user_favori import UserFavori

Expand Down
23 changes: 14 additions & 9 deletions src/business_logic/formation/get_formation_details.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from datetime import date
from datetime import date, datetime
import json
from typing import Any, Optional
from src.business_logic.formation.exceptions import ProcessFormationException
Expand All @@ -15,10 +15,14 @@
ContinuationOfStudies,
process_continuation_studies,
)
import strawberry

DATE_FORMAT = "%d/%m/%Y"


@dataclass
class Formation:
@strawberry.type
class FormationDetails:
id: str
exceptions: Optional[ParcourSupExpectations]
duree: str
Expand All @@ -29,7 +33,7 @@ class Formation:
type: str
jobs: Optional[list[Job]]
continuation_studies: Optional[ContinuationOfStudies]
updated_at: date
updated_at: Optional[date]


def _filter_by_link(formations: list[dict[str, Any]], for_id: str) -> dict[str, Any]:
Expand All @@ -45,7 +49,7 @@ def _read_json_formation(for_id: str) -> Optional[dict[str, Any]]:
return result if len(result) > 0 else None


def _process_formation(for_id: str) -> Formation:
def _process_formation(for_id: str) -> FormationDetails:
formation = _read_json_formation(for_id)

if formation:
Expand All @@ -60,16 +64,17 @@ def _process_formation(for_id: str) -> Formation:
certificat = formation["nature_certificat"]["libelle_nature_certificat"]
sigle = formation["sigle"] if formation["sigle"] else None
type_sigle = formation["type_Formation"]["type_formation_sigle"]

metier = formation["metiers_formation"]["metier"]
jobs = process_jobs(metier if metier else None)

poursuite_etudes = formation["poursuites_etudes"]
continuation_studies = process_continuation_studies(
poursuite_etudes if poursuite_etudes else None
)
updated_at = formation["modification_date"]
return Formation(
updated_at = datetime.strptime(
formation["modification_date"], DATE_FORMAT
).date()
return FormationDetails(
id=identifiant,
exceptions=exceptions,
duree=duree,
Expand All @@ -84,10 +89,10 @@ def _process_formation(for_id: str) -> Formation:
)


def get_formation_by_id(for_id: str) -> Formation:
def get_formation_by_id(for_id: str) -> FormationDetails:
try:
return _process_formation(for_id)
except Exception as e:
raise ProcessFormationException(
"Error during formation processing : " + e
"Error during formation processing : " + str(e)
) from e
2 changes: 2 additions & 0 deletions src/business_logic/formation/job/get_job_by_formation.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from dataclasses import dataclass
import strawberry


@dataclass
@strawberry.type
class Job:
id: str
libelle: str
Expand Down
7 changes: 5 additions & 2 deletions src/business_logic/formation/parcoursup/helper/html_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
from typing import Optional
from bs4 import BeautifulSoup

import strawberry


@dataclass
@strawberry.type
class Expectation:
title: str
sub_expectations: Optional[list[str]] = field(default_factory=list)


@dataclass
@strawberry.type
class ParcourSupExpectations:
title: str
expectations: list[Expectation]
Expand Down Expand Up @@ -57,7 +61,6 @@ def _extract_expectations(self) -> list[Expectation]:

if current_expectation:
self.__expectations.append(current_expectation)

return self.__expectations

def _extract_title(self) -> str:
Expand All @@ -67,4 +70,4 @@ def parse_html(self) -> ParcourSupExpectations:
title = self._extract_title()
expectations = self._extract_expectations()

return ParcourSupExpectations(title, expectations)
return ParcourSupExpectations(title=title, expectations=expectations)
5 changes: 5 additions & 0 deletions src/business_logic/formation/scrap/types.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
from dataclasses import dataclass

import strawberry

from src.models.formation import Formation


@dataclass
@strawberry.type
class Facet:
key: str
doc_count: int


@dataclass
@strawberry.type
class FormationIsFavorite:
formation: Formation
is_favorite: bool


@dataclass
@strawberry.type
class FormationsWithTotal:
total: int
formations: list[Formation | FormationIsFavorite]
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from dataclasses import dataclass, field
from typing import Optional

import strawberry


@strawberry.type
@dataclass
class ContinuationOfStudies:
title: str = ""
Expand Down
11 changes: 7 additions & 4 deletions src/models/base_model.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from datetime import date

import strawberry
from sqlalchemy import Column, DateTime, func
from sqlalchemy.ext.declarative import declarative_base


Model = declarative_base(name="Model")


@strawberry.type
class BaseModel(Model):
__abstract__: bool = True
created_at = Column(DateTime, nullable=False, default=func.now())
updated_at = Column(
__abstract__ = True
created_at: date = Column(DateTime, nullable=False, default=func.now())
updated_at: date = Column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)

Expand Down
21 changes: 12 additions & 9 deletions src/models/formation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import uuid

import strawberry
from sqlalchemy import Column, Integer, String, Text

from src.models.base_model import BaseModel
from src.models.helpers.UUIDType import UUIDType

Expand All @@ -11,19 +13,20 @@ def default_uuid5():
return uuid.uuid5(namespace, name)


@strawberry.type
class Formation(BaseModel):
__tablename__ = "formation"

id = Column(
id: uuid.UUID = Column(
UUIDType,
default=default_uuid5,
primary_key=True,
)
code_nsf = Column(Integer, nullable=False)
type = Column(String(255), nullable=False)
libelle = Column(String(255), nullable=False)
tutelle = Column(String(255), nullable=False)
url = Column(String(255), nullable=False, unique=True)
domain = Column(Text, nullable=False)
niveau_de_sortie = Column(String(255), nullable=False)
duree = Column(String(255), nullable=False)
code_nsf: int = Column(Integer, nullable=False)
type: str = Column(String(255), nullable=False)
libelle: str = Column(String(255), nullable=False)
tutelle: str = Column(String(255), nullable=False)
url: str = Column(String(255), nullable=False, unique=True)
domain: str = Column(Text, nullable=False)
niveau_de_sortie: str = Column(String(255), nullable=False)
duree: str = Column(String(255), nullable=False)
22 changes: 9 additions & 13 deletions src/models/user.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
from dataclasses import dataclass
from typing import Optional

import strawberry
from sqlalchemy import Column, Integer, String, Text
from src.models.base_model import BaseModel
from src.models.user_favori import UserFavori
from sqlalchemy.orm import relationship

from src.models.base_model import BaseModel
from src.models.user_favori import UserFavori

# Create User row


@dataclass
@strawberry.type
class User(BaseModel):
__tablename__ = "user"

id: int
username: str
email: str
profile_pic_url: str

id = Column(Integer, primary_key=True)
username = Column(String(80), unique=True, nullable=False)
email = Column(String(200), unique=True, nullable=False)
id: int = Column(Integer, primary_key=True)
username: str = Column(String(80), unique=True, nullable=False)
email: str = Column(String(200), unique=True, nullable=False)
password = Column(Text(), nullable=False)
profile_pic_url = Column(Text)
profile_pic_url: Optional[str] = Column(Text)

favoris = relationship(
"UserFavori",
Expand Down

0 comments on commit 70ede88

Please sign in to comment.