Skip to content

Commit

Permalink
[GraphQL] re-init Strawberry + co-habitation with REST + Bruno setup. (
Browse files Browse the repository at this point in the history
…#37)

* feat: setup bruno + strawberry gql

* feat: rename `Formation` to `FormationDetails`

* add bruno for search.

* feat: add strawberry before all class/dataclass

* fix: handle basics errors.
  • Loading branch information
Angel-Dijoux authored Jan 23, 2024
1 parent bcb358d commit 7791f11
Show file tree
Hide file tree
Showing 18 changed files with 374 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
309 changes: 227 additions & 82 deletions Pipfile.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions bruno_onisep_explorer/bruno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"version": "1",
"name": "bruno_onisep_explorer",
"type": "collection"
}
22 changes: 22 additions & 0 deletions bruno_onisep_explorer/geMainFormations.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
meta {
name: geMainFormations
type: http
seq: 1
}

post {
url: http://127.0.0.1:5005/api/v1/formations/
body: json
auth: none
}

headers {
Content-Type: application/json
}

body:json {
{
"offset": 0,
"limit": 10
}
}
23 changes: 23 additions & 0 deletions bruno_onisep_explorer/searchFormation.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
meta {
name: searchFormation
type: http
seq: 2
}

post {
url: http://localhost:5005/api/v1/formations/search
body: json
auth: none
}

headers {
Content-Type: application/json
}

body:json {
{
"limit": 10,
"offset": 0,
"query": "BTS SIO"
}
}
10 changes: 5 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ click==8.1.7
coverage==7.4.0
decorator==5.1.1
factory-boy==3.3.0
Faker==22.4.0
Faker==22.5.0
flasgger==0.9.5
Flask==2.2.2
Flask-Cors==3.0.10
Expand All @@ -34,7 +34,7 @@ jsonschema==4.21.1
jsonschema-specifications==2023.12.1
libcst==1.1.0
loguru==0.6.0
Mako==1.3.0
Mako==1.3.1
markdown-it-py==3.0.0
MarkupSafe==2.1.4
marshmallow==3.20.2
Expand Down Expand Up @@ -70,14 +70,14 @@ sniffio==1.3.0
soupsieve==2.5
SQLAlchemy==2.0.25
SQLAlchemy-serializer==1.4.1
starlette==0.35.1
strawberry-graphql==0.217.1
starlette==0.36.1
strawberry-graphql==0.218.0
termcolor==2.4.0
typer==0.9.0
typing-inspect==0.9.0
typing_extensions==4.9.0
urllib3==2.1.0
uvicorn==0.26.0
uvicorn==0.27.0
validators==0.20.0
Werkzeug==2.2.2
xmltodict==0.13.0
Expand Down
2 changes: 2 additions & 0 deletions src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,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
24 changes: 24 additions & 0 deletions src/api/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import strawberry
from strawberry.extensions import MaxAliasesLimiter, MaxTokensLimiter, QueryDepthLimiter


@strawberry.type
class Book:
title: str
author: str


@strawberry.type
class Query:
books: list[Book]


schema = strawberry.Schema(
query=Query,
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()
23 changes: 15 additions & 8 deletions src/business_logic/formation/get_formation_details.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from dataclasses import dataclass
from datetime import date
from datetime import date, datetime
import json
from typing import Any, Optional

import strawberry
from src.business_logic.formation.exceptions import ProcessFormationException
from src.business_logic.formation.job.get_job_by_formation import Job, process_jobs
from src.business_logic.formation.parcoursup.get_parcoursup_expectations import (
Expand All @@ -16,9 +18,12 @@
process_continuation_studies,
)

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


@dataclass
class Formation:
@strawberry.type
class FormationDetail:
id: str
exceptions: Optional[ParcourSupExpectations]
duree: str
Expand All @@ -29,7 +34,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 +50,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) -> FormationDetail:
formation = _read_json_formation(for_id)

if formation:
Expand All @@ -68,8 +73,10 @@ def _process_formation(for_id: str) -> Formation:
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 FormationDetail(
id=identifiant,
exceptions=exceptions,
duree=duree,
Expand All @@ -84,10 +91,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) -> FormationDetail:
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
3 changes: 3 additions & 0 deletions src/business_logic/formation/job/get_job_by_formation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from dataclasses import dataclass

import strawberry


@strawberry.type
@dataclass
class Job:
id: str
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from dataclasses import dataclass, field
from typing import Optional
from bs4 import BeautifulSoup
import strawberry


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


@strawberry.type
@dataclass
class ParcourSupExpectations:
title: str
Expand Down Expand Up @@ -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,20 +1,25 @@
from dataclasses import dataclass

import strawberry

from src.models.formation import Formation


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


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


@strawberry.type
@dataclass
class FormationsWithTotal:
total: int
Expand Down
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
2 changes: 2 additions & 0 deletions src/models/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

class BaseModel(Model):
__abstract__: bool = True
__allow_unmapped__: bool = True

created_at = Column(DateTime, nullable=False, default=func.now())
updated_at = Column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
Expand Down
20 changes: 11 additions & 9 deletions src/models/formation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import uuid

from sqlalchemy import Column, Integer, String, Text
import strawberry
from src.models.base_model import BaseModel
from src.models.helpers.UUIDType import UUIDType

Expand All @@ -11,19 +12,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)
20 changes: 8 additions & 12 deletions src/models/user.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from dataclasses import dataclass
from typing import Optional

from sqlalchemy import Column, Integer, String, Text
import strawberry
from src.models.base_model import BaseModel
from src.models.user_favori import UserFavori
from sqlalchemy.orm import relationship
Expand All @@ -9,22 +10,17 @@
# 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(
favoris: list[UserFavori] = relationship(
"UserFavori",
secondary=UserFavori.__tablename__,
primaryjoin="User.id == UserFavori.user_id",
Expand Down
3 changes: 2 additions & 1 deletion src/models/user_favori.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from sqlalchemy import Column, ForeignKey, Integer
from src.models.base_model import BaseModel
from src.models import Formation
from src.models.helpers.UUIDType import UUIDType
from sqlalchemy_serializer import SerializerMixin
from sqlalchemy.orm import relationship
Expand All @@ -25,4 +26,4 @@ class UserFavori(BaseModel, SerializerMixin):
)

users = relationship("User", back_populates="favoris")
formation = relationship("Formation")
formation: list[Formation] = relationship("Formation")

0 comments on commit 7791f11

Please sign in to comment.