From 935b404338561adeeb5e981853207b4bea3ebee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Gramain?= Date: Wed, 27 Mar 2024 14:17:05 +0100 Subject: [PATCH] duplicate apis --- PROJET.md | 54 +++++++ athlete/Dockerfile | 14 +- athlete/__pycache__/app.cpython-311.pyc | Bin 0 -> 8825 bytes athlete/__pycache__/athlete.cpython-311.pyc | Bin 0 -> 3465 bytes .../conftest.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 1852 bytes athlete/app.py | 119 +++++++++++++-- athlete/athlete.py | 48 ++++++- athlete/conftest.py | 14 +- athlete/genData.py | 0 athlete/static/swagger.yaml | 2 + .../conftest.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 1250 bytes ...st_delAthlete.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 2132 bytes ...st_getAthlete.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 2916 bytes .../test_gettest.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 1462 bytes ..._listeAthlete.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 11730 bytes .../test_ping.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 1435 bytes ..._ping_athlete.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 1451 bytes ...t_postAthlete.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 3084 bytes athlete/tests/athletes.json | 38 ----- athlete/tests/test_delAthlete.py | 6 + athlete/tests/test_getAthlete.py | 23 ++- athlete/tests/test_listeAthlete.py | 22 +++ athlete/tests/test_ping.py | 5 - athlete/tests/test_ping_athlete.py | 3 + athlete/tests/test_postAthlete.py | 19 +++ discipline/Dockerfile | 19 +++ .../conftest.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 1881 bytes .../__pycache__/discipline.cpython-311.pyc | Bin 0 -> 3191 bytes discipline/app.py | 121 ++++++++++++++++ discipline/conftest.py | 26 ++++ discipline/discipline.py | 50 +++++++ discipline/static/swagger.yaml | 133 +++++++++++++++++ discipline/swagger.yaml | 0 .../conftest.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 1250 bytes ...st_delAthlete.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 2132 bytes ...delDiscipline.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 2141 bytes ...st_getAthlete.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 2916 bytes ...getDiscipline.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 3065 bytes .../test_gettest.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 1462 bytes ..._listeAthlete.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 11730 bytes ...steDiscipline.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 4072 bytes .../test_ping.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 1435 bytes ...ng_discipline.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 1460 bytes ...t_postAthlete.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 3084 bytes ...ostDiscipline.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 3248 bytes discipline/tests/test_delDiscipline.py | 6 + discipline/tests/test_getDiscipline.py | 13 ++ discipline/tests/test_listeDiscipline.py | 9 ++ discipline/tests/test_ping_discipline.py | 3 + discipline/tests/test_postDiscipline.py | 17 +++ docker-compose.yaml | 46 ++++++ medaille/Dockerfile | 19 +++ .../conftest.cpython-311-pytest-8.1.1.pyc | Bin 0 -> 1858 bytes medaille/app.py | 117 +++++++++++++++ medaille/conftest.py | 26 ++++ medaille/static/swagger.yaml | 136 ++++++++++++++++++ medaille/swagger.yaml | 0 requirements.txt | 3 +- sample/athletes.json | 4 +- sample/disciplines.json | 30 ++++ 60 files changed, 1065 insertions(+), 80 deletions(-) create mode 100644 athlete/__pycache__/app.cpython-311.pyc create mode 100644 athlete/__pycache__/athlete.cpython-311.pyc create mode 100644 athlete/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc delete mode 100644 athlete/genData.py create mode 100644 athlete/tests/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc create mode 100644 athlete/tests/__pycache__/test_delAthlete.cpython-311-pytest-8.1.1.pyc create mode 100644 athlete/tests/__pycache__/test_getAthlete.cpython-311-pytest-8.1.1.pyc create mode 100644 athlete/tests/__pycache__/test_gettest.cpython-311-pytest-8.1.1.pyc create mode 100644 athlete/tests/__pycache__/test_listeAthlete.cpython-311-pytest-8.1.1.pyc create mode 100644 athlete/tests/__pycache__/test_ping.cpython-311-pytest-8.1.1.pyc create mode 100644 athlete/tests/__pycache__/test_ping_athlete.cpython-311-pytest-8.1.1.pyc create mode 100644 athlete/tests/__pycache__/test_postAthlete.cpython-311-pytest-8.1.1.pyc delete mode 100644 athlete/tests/athletes.json create mode 100644 athlete/tests/test_delAthlete.py delete mode 100644 athlete/tests/test_ping.py create mode 100644 athlete/tests/test_ping_athlete.py create mode 100644 athlete/tests/test_postAthlete.py create mode 100644 discipline/Dockerfile create mode 100644 discipline/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc create mode 100644 discipline/__pycache__/discipline.cpython-311.pyc create mode 100644 discipline/app.py create mode 100644 discipline/conftest.py create mode 100644 discipline/discipline.py create mode 100644 discipline/static/swagger.yaml delete mode 100644 discipline/swagger.yaml create mode 100644 discipline/tests/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc create mode 100644 discipline/tests/__pycache__/test_delAthlete.cpython-311-pytest-8.1.1.pyc create mode 100644 discipline/tests/__pycache__/test_delDiscipline.cpython-311-pytest-8.1.1.pyc create mode 100644 discipline/tests/__pycache__/test_getAthlete.cpython-311-pytest-8.1.1.pyc create mode 100644 discipline/tests/__pycache__/test_getDiscipline.cpython-311-pytest-8.1.1.pyc create mode 100644 discipline/tests/__pycache__/test_gettest.cpython-311-pytest-8.1.1.pyc create mode 100644 discipline/tests/__pycache__/test_listeAthlete.cpython-311-pytest-8.1.1.pyc create mode 100644 discipline/tests/__pycache__/test_listeDiscipline.cpython-311-pytest-8.1.1.pyc create mode 100644 discipline/tests/__pycache__/test_ping.cpython-311-pytest-8.1.1.pyc create mode 100644 discipline/tests/__pycache__/test_ping_discipline.cpython-311-pytest-8.1.1.pyc create mode 100644 discipline/tests/__pycache__/test_postAthlete.cpython-311-pytest-8.1.1.pyc create mode 100644 discipline/tests/__pycache__/test_postDiscipline.cpython-311-pytest-8.1.1.pyc create mode 100644 discipline/tests/test_delDiscipline.py create mode 100644 discipline/tests/test_getDiscipline.py create mode 100644 discipline/tests/test_listeDiscipline.py create mode 100644 discipline/tests/test_ping_discipline.py create mode 100644 discipline/tests/test_postDiscipline.py create mode 100644 medaille/Dockerfile create mode 100644 medaille/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc create mode 100644 medaille/app.py create mode 100644 medaille/conftest.py create mode 100644 medaille/static/swagger.yaml delete mode 100644 medaille/swagger.yaml create mode 100644 sample/disciplines.json diff --git a/PROJET.md b/PROJET.md index e69de29..12fe38c 100644 --- a/PROJET.md +++ b/PROJET.md @@ -0,0 +1,54 @@ +# API JO 2024 + +Todo : +- API Athletes (in progress), API Medaille, API Discipline +- Simple FrontEnd +- Dockerize +- Data generation script for testing purpose + +Done : +- Swagger definitions +- Base model (on Athletes) with unitests (pytest) + +## Architecture +3 uServices in their respective subfolders: +- ``/athlete``: Athletes API +- ``/medaille``: Medals API +- ``/discipline``: Disciplines API + +A ``swagger.yaml`` definition is available in each uService definition in the api folder. + +The ``data`` folder contains the data used by the API in dev. It is a shared folder in the dev environement. +The ``sample`` folder for default values inside the containers. It should be mounted in the docker container in seperated volumes as they shouldn't talk to each other. + +## How to run (dev) +Go in the right API folder. Start the flask application + +```bash +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +cd athlete/ +python app.py +``` + +## How to test +```bash +pytest -vvv . +``` + +## How to build the docker images + +You can build using the rootfolder context and the Dockerfile in the subfolder. + +```bash +docker build . -f athlete/Dockerfile +docker build . -f discipline/Dockerfile +docker build . -f medaille/Dockerfile +``` + +However, the compose file is already set up to build the images for you. + +```bash +docker compose up -d +``` diff --git a/athlete/Dockerfile b/athlete/Dockerfile index bafe792..611747e 100644 --- a/athlete/Dockerfile +++ b/athlete/Dockerfile @@ -3,9 +3,17 @@ FROM python:alpine3.19 WORKDIR /app COPY /requirements.txt requirements.txt - RUN pip install -r requirements.txt -COPY . . +COPY athlete/ . -CMD ["python", "app.py"] \ No newline at end of file +RUN apk update && apk add --no-cache curl + +HEALTHCHECK --interval=5s --timeout=3s --start-period=5s --retries=3 CMD curl --fail http://localhost:8000/ping || exit 1 + +COPY sample/athletes.json ./data/ + +EXPOSE 8000 +ENV ATHLETE_FILE=data/athletes.json + +CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"] diff --git a/athlete/__pycache__/app.cpython-311.pyc b/athlete/__pycache__/app.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99dcc32be7f70e133113744ac97a9509f7f3a2e0 GIT binary patch literal 8825 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 literal 0 HcmV?d00001 diff --git a/athlete/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc b/athlete/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a5c712bd9fee807cc7d5d8dee75065eabcad9a05 GIT binary patch literal 1852 zcmb_dU1%d!6h3!;(@c}5``1nFhA#eKq%iMoW@#7@7Gy!+vUGjfC(oU9lE&bItoP2Gd;adZ z-#zCH^KB#&1{^;=uRA^(|Y)jk??&Q--GW7Kn4ir#6~U{gZJ40n-44ir68AJ z%YCgK>hs$eX}1@xBnFl~S0pzSz|1oGU!xTH{@!iGvfIN$U>!>AX8zbZf-cbEnZlEt zR}^n?gSTU6z73m%JkdS2j%2Tob+8Jo|FFi=k|$Y(a?R9lM6kxDZtK=;$*NX75joq+ zB{@&dEiC62mtOZqFXrC3ypUT^Ut7GqkQ|``2FLBIb=xpKL95o*D66a&Qq>j1)K%4! zY6YrSY){gvl@;SXPpa37*g&{eOr!Gt4)J*GC5u&e`q72dTb53(RJov=sqC7zTA<}D zt(J7nwoaeUAd` zOqSn+7m#dXr)=RmJO=B$3_FB|VFdIu~1MVr& z&*wgwbCfApnQ94Bl-d3dNc0p2LQrD?D3#VL6`c+PJJdgg{tTSO5ct6MmBgNsaFl6R znQjTwKD6YR7rItAOxvhf-spU7P3=i>wYKJWOToe(vyEzHmg*l+1AB3#r}E@L7sky# zwgSZ-_F2k)2cBGG{byYB8@=0i{#PKc7}xDO)hR', methods=["GET"]) +def getAthlete(id: int): + """ + Renvoie un athlète par son id + """ + listeAthletes = ListeAthlete() + listeAthletes.loadFromJson(app.config['ATHLETE_FILE']) + for athlete in listeAthletes.root: + if athlete.id == id: + return jsonify(athlete.model_dump()), 200 + return jsonify({"message": "Athlete introuvable"}), 404 +@app.route('/', methods=["DELETE"]) +def deleteAthlete(id: int): + """ + Supprime un athlète par son id + """ + listeAthletes = ListeAthlete() + listeAthletes.loadFromJson(app.config['ATHLETE_FILE']) + for athlete in listeAthletes.root: + if athlete.id == id: + listeAthletes.root.remove(athlete) + with open(app.config['ATHLETE_FILE'], 'w') as f: + json.dump(listeAthletes.model_dump(), f, indent=4) + return jsonify({"message": "Athlete supprimé"}), 200 + return jsonify({"message": "Athlete introuvable"}), 404 + +@app.route('/', methods=["PUT"]) +def updateAthlete(id: int): + """ + Met à jour un athlète par son id + """ + listeAthletes = ListeAthlete() + listeAthletes.loadFromJson(app.config['ATHLETE_FILE']) + for athlete in listeAthletes.root: + if athlete.id == id: + data = json.loads(request.data) + for key, value in data.items(): + setattr(athlete, key, value) + with open(app.config['ATHLETE_FILE'], 'w') as f: + json.dump(listeAthletes.model_dump(), f, indent=4) + return jsonify({"message": "Athlete mis à jour"}), 200 + return jsonify({"message": "Athlete introuvable"}), 404 + +@app.route('/', methods=["PATCH"]) +def patchAthlete(id: int): + """ + Met à jour un athlète par son id + """ + listeAthletes = ListeAthlete() + listeAthletes.loadFromJson(app.config['ATHLETE_FILE']) + for athlete in listeAthletes.root: + if athlete.id == id: + data = json.loads(request.data) + data["id"] = athlete.id # On ne peut pas changer l'id + for key, value in data.items(): + if hasattr(athlete, key): + setattr(athlete, key, value) + with open(app.config['ATHLETE_FILE'], 'w') as f: + json.dump(listeAthletes.model_dump(), f, indent=4) + return jsonify({"message": "Athlete mis à jour"}), 200 + return jsonify({"message": "Athlete introuvable"}), 404 + +@app.route('/', methods=["POST"]) +def addAthlete(): + """ + Ajoute un athlète + """ + listeAthletes = ListeAthlete() + listeAthletes.loadFromJson(app.config['ATHLETE_FILE']) + athlete = Athlete(**json.loads(request.data)) + athlete.id = max([athlete.id for athlete in listeAthletes.root]) + 1 + listeAthletes.root.append(athlete) + with open(app.config['ATHLETE_FILE'], 'w') as f: + json.dump(listeAthletes.model_dump(), f, indent=4) + return jsonify(athlete.model_dump()), 200 + +swaggerui_blueprint = get_swaggerui_blueprint( + "/swagger", + "/static/swagger.yaml" +) +app.register_blueprint(swaggerui_blueprint) def create_app(): return app diff --git a/athlete/athlete.py b/athlete/athlete.py index d826389..7f216be 100644 --- a/athlete/athlete.py +++ b/athlete/athlete.py @@ -1,15 +1,53 @@ -from pydantic import BaseModel -from typing import Optional +from pydantic import BaseModel, RootModel +from typing import Optional, List +import json class Athlete(BaseModel): """ Modèle Athlète """ - id: Optional[int] + id: Optional[int] = 0 prenom: str nom: str pays: str sexe: str = "N/A" image: str = "https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_640.png" - disciplines: Optional[list[int]] = None - records: Optional[list[int]] = None + disciplines: Optional[List[int]] = None + records: Optional[List[int]] = None + + def loadFromJsonData(self, data: str): + """ + Charge les données depuis une chaine json + + :param data: Données json + :return: None + """ + data = json.loads(data) + for key, value in data.items(): + setattr(self, key, value) + +class ListeAthlete(RootModel): + root: List[Athlete] = [] + def loadFromJson(self, path: str): + """ + Charge les données depuis un fichier json + + :param path: Chemin du fichier json + :return: None + """ + try: + with open(path) as f: + data = json.load(f) + for athlete in data: + self.root.append(Athlete(**athlete)) + except FileNotFoundError: + print("Fichier introuvable") + def loadFromJsonData(self, data: str): + """ + Charge les données depuis une chaine json + :param data: Données json + :return: None + """ + data = json.loads(data) + for athlete in data: + self.root.append(Athlete(**athlete)) \ No newline at end of file diff --git a/athlete/conftest.py b/athlete/conftest.py index 882eddb..6ccefe3 100644 --- a/athlete/conftest.py +++ b/athlete/conftest.py @@ -1,24 +1,26 @@ import pytest -from ..app.app import create_app +import shutil +from pathlib import Path +from app import create_app @pytest.fixture() def app(): app = create_app() + # Copy the sample to the test folder using python + shutil.copy(Path(__file__).parent.parent / 'sample' / 'athletes.json', Path(__file__).parent / 'tests' / 'athletes.json') app.config.update({ "TESTING": True, + "ATHLETE_FILE": Path(__file__).parent / 'tests' / 'athletes.json' }) - - # other setup can go here - yield app - # clean up / reset resources here + # Remove the file after the test + (Path(__file__).parent / 'tests' / 'athletes.json').unlink() @pytest.fixture() def client(app): return app.test_client() - @pytest.fixture() def runner(app): return app.test_cli_runner() \ No newline at end of file diff --git a/athlete/genData.py b/athlete/genData.py deleted file mode 100644 index e69de29..0000000 diff --git a/athlete/static/swagger.yaml b/athlete/static/swagger.yaml index 540ddfe..80998d4 100644 --- a/athlete/static/swagger.yaml +++ b/athlete/static/swagger.yaml @@ -19,11 +19,13 @@ paths: name: offset schema: type: integer + default: 0 description: Le nombre d'éléments à ignorer avant de commencer à collecter l'ensemble de résultats - in: query name: limit schema: type: integer + default: 10 description: Le nombre d'éléments à ignorer summary: Liste l'ensemble des athlètes description: Affiche la liste des athlètes enregistrés sur les J.O. 2024. diff --git a/athlete/tests/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc b/athlete/tests/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a8c86dc2b42349753e13818caedc6bfacdb0104 GIT binary patch literal 1250 zcmb_a&ubGw6n?Y2-Q8xBw5e@Xs3FBuq%;nlh0*(t#unIM6Bo)v>{3;>Bmz~XJVe41wXE8bExTG(FY5#(pz8W?bkza^gz<&;Z}KlCKW%lQvm_ymrbp6i<0(c@iWnMophq%tphC#i60#x{a&1 zitrQ&Chuc$qR}t?9lVA0_;foeT0|DRO>D^_JILCLnFPOk0b7KZ52eY)Kr203DJ>Nr zm+~^>5(3q#*H)cZf!b`?sB@PDGD`LM6}j_;!gG%@uTV9qTUc&cYbL8Mv-&!w#nS8_sHJ{9OMn~v){H7`is zZ?r10ceB!HiEf!5cG`F9wOK|tnB&u6I1U9`6ieX8LYv6~H8cYa-^(nn@I7g(&KtkU zXxMvRIL1Zpf>0vLdamEA*6pTC7g!dLcq_ec2wj;F(&2ghW2V|-b|AOk|2rWg?a9sT w0m&SZOjpuLx(f(rGDu@_H@-F7LVuytZB}gzRU_EA}q4{0}u!W1poj5 literal 0 HcmV?d00001 diff --git a/athlete/tests/__pycache__/test_delAthlete.cpython-311-pytest-8.1.1.pyc b/athlete/tests/__pycache__/test_delAthlete.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6967234b76525c3d6a23b6c0bdee97bb236385c GIT binary patch literal 2132 zcmeHH%}*0S6rb7MwzLQhQ4>v68iMFvTFQrLVvQU~JQ1RJ;oxTJOrdqR+srHqb(;|5 ziHjE#IC$WMQ4;?Sk76((b29P7jfBJ)PQKY~x7~vA=0&HSH}B*3-psstZ{PLxr4S6` zhg$jw>vt)%hSFq?M}XNx1QBeZ1?;bitriuGY;7Ti5m8>Dg*d^Fkggs|r`aNI`pP8% zQ-yi_1n4I6&^8FC2f;hwwHtaiu!k#cNg+x@^%PHi>YuBjc^d!V#@cZzau0tH)zeCt zXcApgb~K0zvuTUD$rg`LCG_6`u{<@j3h{p7b|05in{}?6Cf0c6yDm4M*LpN*4D;!o_f(_GKGxfMs=R$ zuIa9Dqv#NtYGew5VN&O!hf!X0s+{VoPys8+m?V?+ev@;`TtCJXKtVZGegmDV_lh{7 z7ImrU?FcMV_iJk|<*s2eF<=x;%Q6_Pv7%$wOh&~yFdK_2Wy>vBxrkd1Ah@_>@MUMk zB0khuvYbWJGODIcMbhAAiOL9~f7(Av*{RMj<}h*IC^^hFU4yRHEVF95U=ivPb@haZ z7p*d_x=gmR=#_g0_sDk&6P?0%AOsRt*!ks3z)v$CcwurP)V}AwJ z42p)Ol*iRj+m>)mptt3HsNo*G$-woXneqq?%PC)s`#ECE1>afDp*BSgHgo z>6#-{@KjIrVWsG)D18ZSen*NFcvW~c=n84rNNpnppNOiV5>{t@qJ}E_xezDcCo1&G zqCg+%lmFlPzFePFi>rlC-c{mtui+)U9M)#LP=ASCs24pIrTi{LRgByRvY$aDLuTa8#D2nJMuK44QAZvp31lbVe!lY#}MraVH z(S@K;)VSMu@++(=Bz*24vSWc2>@u?tNj^_)U1z#N#gU_@#Ku63^~pjm#(4+<%JX`Y zD+3Zxaoh6R^GdlY%HXy4{c%88U|N(fntjW0OiFwT+vHo6@CHnqH+O9(ut$t*jt2r4JzGQBD)U^VW-0>Z2WCbw8 z>$&3e2G{zIO-2D-7IiI&jO!wSq^xdUIF}dB=Y{n|NP@kSRFDYWf`?unw~-evFmdgy z=Ek$j#;34*%y2E@7}v)A9gDixs5d12fEhQgbkvqM_gDyY4^3Mu-%P%qeE;Ty*6ByB(|cE*YN&N;T5n8&HkPK%m1*GMGCWMam^?86iQ{No%ZGfbGwJi%mQ z-YQdOelpEb*ip=XIr%A=a2uoq&$!(4ia2EcRpEY=)86Az&YkzVDM^sv)6K*3)%lk1_K96>m;ZAxxa zJAk8}sl@#o>*(uX6QA4ox{15{Cqy@dwg=={tB2>L2k$=u z44Tw;&MM&SArqO{Ms*y&B}Z;Z7&%HkgOd@puA0(ERL`3D4btSlB6+QW`x@evg)T!s zI|OzQh3FfAi_OIQFn*9`M%a`hDa7p|BZpEbZ=*;F74|d83^m$9KW?TRDot!EBC;v% zE1(EHX=52F8%pEznwel~kbzKJ9r*j;91H;3M(paCp4esF!ijy*@wnl<6LBtvnMe0D zjFcxDe-B;b9{pmp4XYtB!c175UFo$S$1H#jH|W>!Hi$m*`~K;n{k%jvb_z%)?LFP z{K{%Px8=DnDfK9y)0-Z3j6f$lzHPWh03%wKs}0*CZa_t~_>`Ca0_*O&S5KzFl9?UoS1pSB+rPCIP96LtwGh z;dBJ5_#K{)&qfrD-U4ayX)pvIQ4gIIuYK-*>VCO*R4gAC%e_}8O6^ztJ03U*Ozm%_ zcG=KTJd_3`5E2t6g((A0N?y$RZ&;E7cJv1#N$`Y7Dn1%K>Yu?uAT7spg9|~6K=2!S za+@ki4WYsnO5Rf|fSZ(3Jlp5!w8&ADl=ysqAbJ}(dK1if?up}>TQ+${?*J74Tx=E0 asf;l`5hb|fnd!yPsUqQ(Q*_b9Q~U$<^Kl>m literal 0 HcmV?d00001 diff --git a/athlete/tests/__pycache__/test_listeAthlete.cpython-311-pytest-8.1.1.pyc b/athlete/tests/__pycache__/test_listeAthlete.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a21fcd789c0da6fc0d6554082581678e52d4a95 GIT binary patch literal 11730 zcmeHN&2JmW72hR?6h-RGv7N+8+ei%@h_NGzQfxUzBh)~fek2Hteq4MZHr$m>WyvLG z*R7>4VWVh)wkXgPD9|2q@S$*#`tQh{6iAd*R(w2$-%q4t^m1YDksP~fWkzW3tE?oSX@t3bBEh}9 zZS+W(56L~VR|pIK--3#a8ODB5+IB81?3s3+A@_2RA^(Zuut~5iagf92$&ch%e`@#_ z65R6=7v_>9q)8lKHHOu39TR%Sqdi9ElZ+EkB#E*MpXLu?#=W9lwCBQN%jDi%0P7`> z1k-|S4d;?fN$(f1kM5hjJp2?uYpi}LcvW)1{7T8Z9zFJ5!CB1lGm0({H_$k9)L3_(CT~dqITMQTXm!#V( z!8yMamck`C=SfKX2Z?zc@2DJ{>!!WTkot~FWE|_Wm>l5*R5q=bl3eyme0DC|=j<24 z1iZ9JnL@Hi5qlz!jR75IXJfn)2W-3llu_T@saN+Kok6ZmPBd~K#AQW8IfpH|7$ znUY9J{3L!_CD&$3A|=t&_?`G$&&OHsIA8yNQ_?oz3;ydLJ~wMs^Ler5uXenkY94N0 zUhf`kRsHhMV`KH*Rc38g{SE4QLAkdou)OtJ)sGfj^zFZzkMaO!)fTI+9WGhE@6dq5 zcK{BJJCuFBhi=VX=<)lE9*9JbU>x_j&O+NV1V$XHtHD%ZJiDCx2IF>}PPIVUw|Lf1 zomJbEeW+vBjl*R$mO9KRA^_TiF>Fogs7sxW*G|s+deyJyDSN`9?B>kauP0^-qoM3( z1KhPK+$R#pi!fo{UF*P44`zCf@7#Az%BfEoTB~i{IWcKxN|N33o2{NqEkaeGQC zb($WvPfXjGlGd_MnppKeWr#fk%NVhJm|J@n7HV2IY7?x9(}8UO^YF@)Zp6|i;8&-> z<7Lf^kC!xoe`^ZAT+T$owDm|XB}#iS%HMGU$}X(YitAbSd(>xfzR@;f@8@{#YFotO9Ccmh(yv}zh^=NdyvFFjY6*QdtUDxVZ0YvB`8(~qb zT$G3PfYTjLo`^8*q5@YWs-SsBbMYpOaxJ^M5*3?n%jpDBi8_AQ>%e7fj!{~YRB{M)_wvr@oR0% zacj2@ns+VQzD2!#ry2OQAK$7se)x8Mvu26^%dN5X^LgFC*ZGa^L3EC;1`PG#N0EA%RgYu&FH7m~KhKPQ(C%HayV+U=6SO((vGp$>kxr+$Wbu zydFpwT2wibHbem?@G7bdl`AmH1%POQS~aFy1ka>gh-$gGMRlPX1velP0Ho#7o5khLw0S_ra7FRF;GED_7K&7z>P7RFRF{uqntuJeA zfSoQQgw&z~9#ViTu3!LUnhIKgN@Ek88W_7{c~Xm08_sTJfSoR*$pCSEP=GA1;0L7E zk!S%bjZJWBVC;VBrc)o1dY{BKHo#7o5khLw0S_ra7FRF;GED_7K&7z>P7RFRG1(cC zoxZHG0d~5K5K@Z{ct`=VxPk$YX)0&|DveEWYGCY+$@Y+J_hpR@u+wFPkXm%WLkf_^ z6%2q(Q$Y(*X>5X117mkgHil%QFKcXooh~DU)S?3(Qh+S3U;t#A3R-|lV-uVj7`tQg z#*nC!Ev;dXHCO9=PcE{xEkX-G{8XI7XawH9K zM+m6^!Wi(70%UPjVwwtCfJ$Q%oEjLrV^STGYG2ma09%wJX@EOINCgnafQJ+yi>ngT zRL}xc8k^wMz}Ov=H;3fSzO1nUwkSu^0C$9t3LuOD4=F$vS0$#YparNjHo>WZvHPW) z&Xpm#(kF3^4X{Nyk_NaVgj4`w40uQZvbZWSO$9ALrLhT44UFBfyvvJc1GvK~L)~qU z&|O>Ird3oKSsnvaI=;XE7oQ5mV4lI-s8OAdO7cSkWq*4bU22F=6C2_^9zGXrQ0Hf~ z6~Jc()x^FB@HWc+s4$v~---FB5B{qAnL)n+5qblv=cRA9J^P;P?9!XyvcF{Z--qgH c-ZafIdmBFaU*A~O%G2BhbL***u6Sqv2H9%GO8@`> literal 0 HcmV?d00001 diff --git a/athlete/tests/__pycache__/test_ping.cpython-311-pytest-8.1.1.pyc b/athlete/tests/__pycache__/test_ping.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4258ec0511168f63924bf30f70fe89bc7515d74f GIT binary patch literal 1435 zcmZ`&J8u&~5Z=8zKl8AnfW)h?Eb%xA*$GK-Ks*a1swm>oq&YUewR6_Jw0jA$b026jVVFiT}VqAX1Q`ZK>!YrAx)^oqe&LFut9gZ+7O}-I?{*iHRZtdGqsxGMN8J z&=;j=9Nz$A2bsvkHd?{)tT<{z!N}28G8mc4d$f`@@k^wue`V5A1NU;}l!KwdIQ{|N z9TcK30M3eucVT@m&5W?AL`sO;ryey_LUjX0TBz~wLFSaBE%{?H)lh3NvJq~1^&-q*|oWBm+-+S$In3&vg_pbf+?4DrcZ#uAR)gAK=>?mH6aV%T}`PN$LT z$O*Hb)h?VX%9uGbA7(5ZqiitMKLbKTOohHb%nmaRqj%8TO&lpOv`94(^s}(k&D4C$ zZSiT~*s3^`bJC)f`xW1FTa~D&W1(2Cj=Q(7-|{=NIxkypS(*zvFK+dW^269Qr}L^w z0#b}bo>2}Aoa(AjAy29~sTR7EgmcP*IHRs}>8$HTp|)rsG?^}nPy9|md0-GGri=!$ zZG%ytH9W^BjEaK6Ns~&C7>7t>b=|T9%jF_#dl1aUC4;Yd>$VwZ87ZwO7lt#*6vcnHmY!Y#Pv-ECP?UA$wHy zJE9O5Mk00oIAF&F+xa9Z1I8rgjhqCnN|BjG? J7td2M^*`!BXnp_y literal 0 HcmV?d00001 diff --git a/athlete/tests/__pycache__/test_ping_athlete.cpython-311-pytest-8.1.1.pyc b/athlete/tests/__pycache__/test_ping_athlete.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d524402053184a62d0c0387573cc866a3061721 GIT binary patch literal 1451 zcmZ`&OK;Oa5Z<+&N7F}C6(lGeqDVX}p`@iPDF_f85S*%t$E6p`jW?+yzqIR?Cbgvs zapJctH*h2;~u!YueG)uPJlrXZDwH!u<^d7C{4g3nJ@?R0O(!~8(F%@9QFiw8} zyN3ew1;BYR@II^`q>&*uq)-ZQXTZpT6v$gBR04&4_j7|B9g#m0Lk^S{HWcC6lJ*tI z2qTuEh?EVa;eCyqKb}M&)X69IJ{SiHKwF4i8pi(L3nvHV!2iN+=r$`Z>1J%hf&8 zX|ox~u~W4PV|trZA5=ZpX;;INin(MuD(>C6aog+8tE^%=6=5!^thCcNDvu&+L1i^V z_w`c9ig!!6I`Lx#5 zElZ=sqfOWLbV~S`#`G2u44(i`ZGF?UeA8h(Z@J*i_!W(9xSN&{MQLrzUDqwm(QU$~ zo<@6{uJepim+~2{_Fspu9T7kk8Kp38#8EVvtkINQA} zSZ3noRAM=MUVj6ZXLVaAR(-YG+|a4LO5F}=`mFwXbzXb9I6qg{{SAxwq%MlUBCUxY zX??!tb@_Bupe#$T!9L=@#Dv~O$K}f(dmnnAZXK4ZN9F47i({qnEBcNSBZjH|tu+3( zbQmqA4l#tlm`P#EfRU2tqb_udLqKOgND@1bBuSHJowN2S6aqmji^=PtWgz%fEq+?H zxS`PQ8YOS3>BF;1DPHJvbOK_cx_(3Sy0B+KofRM2uCZy6$FvGCD*1gdPGpSnu_*n; NzY`?k<%?95`XA$QZR`L5 literal 0 HcmV?d00001 diff --git a/athlete/tests/__pycache__/test_postAthlete.cpython-311-pytest-8.1.1.pyc b/athlete/tests/__pycache__/test_postAthlete.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8678aaee30a49610156135297400e5f8cd02c2dd GIT binary patch literal 3084 zcmdT_&1>976rYi_-?rBq$4=5vFr*II2E5+Hv7JzdhJGZZ5JJ*W@I@#)V|%yKO3cWu zz1D&$Ih0(IL!p7XkdsU69D3^C(4!q3x(pNwJ>}+*(t~^H8)+p=YbOMH>a6tkef;LV zH*emIe(3MdBN*#{tMe}~LVrpj97>mYc@CIw5kUl-Xb$`Tilx>Rj4W*~g{4I?(^h6K z1H5_<&1DI`jmp|{>EK2gi_8^w(WEY|L5OI`z$&~if7YaXH)bQ4C`2V1Ns%JcJ24?hl*-&X^-0qI3ivQ&1LOVi@RLGM)FEx)zzeLX~8?c^Dc!NO(-2RYh*O?rFcYzY;sT9IoQu!Z1?|3tgmI znj-z-l4Cbrq20N4`?kQ>T0@Jj>s+c-2(|2Sx@=gENtx`na#kpHBGL|{P1_QxB${Ka zaG`O!Ohu}084FYtNS)W}j#+O~E;5YPY)1GVuE1rgHMre;{;Twb_mtV$GH#t8JL9aJ zEA#wnml>TMmE1&`k8{^>m$+WD3C*`hX9L59G8b7!IV8<#SrsZ^B{?U_h1P(I0;ZeLRHy=`tD1xyElAGP$&%&d+h@x8w#XR42cw)~ z#~>^_4n$@#Dan*1Gp%AQ$ttWA%D4jmMJBIs5Nz&>{Jv*Vh*d)5-*;AA%3a-HqNLXh z)6^MtSk1N^gHh23X8rC`-E`|sF4CqA2riE6e9>Mqi4WBmO#7~3>P^Fo=Oj5 z;EG?ny50PUF`J1Yecoo4;p%kRF^#6-f`tvqMahB+b%DC&K9R1Ob=q{U$1T#FYOdBtzGMAr+2i|Tgm5XOni12Ai!^jrT`O(V2x%)%7dDN zutPE6p(njS1K=Kf6srdhVyATCo3|giJEeEluHL&26@1XT-`aZX-A4=S4fum+yLfh| zcy{gDAKJu@HnHVD5d-3OOb_DI5CiR!Iw9ee=dGRY)0}^K=!il!7a9A(#z~Ji1Mq$KLN6 0 + assert listeAthlete.root[0].prenom is not None + assert listeAthlete.root[0].nom is not None + assert listeAthlete.root[0].pays is not None + assert listeAthlete.root[0].sexe is not None + assert listeAthlete.root[0].image is not None + assert listeAthlete.root[0].disciplines is not None + assert listeAthlete.root[0].records is not None + assert listeAthlete.root[0].id is not None + assert listeAthlete.root[0].id > 0 + assert listeAthlete.root[0].prenom != "" + assert listeAthlete.root[0].nom != "" + assert listeAthlete.root[0].pays != "" + assert listeAthlete.root[0].sexe != "" + assert listeAthlete.root[0].image != "" + assert listeAthlete.root[0].disciplines != [] diff --git a/athlete/tests/test_ping.py b/athlete/tests/test_ping.py deleted file mode 100644 index 29b234a..0000000 --- a/athlete/tests/test_ping.py +++ /dev/null @@ -1,5 +0,0 @@ -import pytest - -def test_ping(client): - response = client.get("/ping") - assert b"{\"message\":\"pong\"}\n" in response.data \ No newline at end of file diff --git a/athlete/tests/test_ping_athlete.py b/athlete/tests/test_ping_athlete.py new file mode 100644 index 0000000..827718d --- /dev/null +++ b/athlete/tests/test_ping_athlete.py @@ -0,0 +1,3 @@ +def test_ping_athlete(client): + response = client.get("/ping") + assert b"{\"message\":\"pong\"}\n" in response.data diff --git a/athlete/tests/test_postAthlete.py b/athlete/tests/test_postAthlete.py new file mode 100644 index 0000000..5524968 --- /dev/null +++ b/athlete/tests/test_postAthlete.py @@ -0,0 +1,19 @@ +from athlete import Athlete +def test_postAthlete(client): + athlete = Athlete( + id=1, + prenom="Jean", + nom="Dupont", + pays="URSS", + sexe="H", + image="http://demo.example", + disciplines=[0], + records=[1], + ) + + response = client.post("/", json=athlete.model_dump()) + assert response.status_code == 200 + athlete.id = response.json["id"] + response = client.get(f"/{athlete.id}") + + assert response.json == athlete.model_dump() \ No newline at end of file diff --git a/discipline/Dockerfile b/discipline/Dockerfile new file mode 100644 index 0000000..c8ddb39 --- /dev/null +++ b/discipline/Dockerfile @@ -0,0 +1,19 @@ +FROM python:alpine3.19 + +WORKDIR /app + +COPY /requirements.txt requirements.txt +RUN pip install -r requirements.txt + +COPY discipline/ . + +RUN apk update && apk add --no-cache curl + +HEALTHCHECK --interval=5s --timeout=3s --start-period=5s --retries=3 CMD curl --fail http://localhost:8000/ping || exit 1 + +COPY sample/disciplines.json ./data/ + +EXPOSE 8000 +ENV DISCIPLINE_FILE=data/disciplines.json + +CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"] diff --git a/discipline/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc b/discipline/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..407abd7ca21ff5339073ad7c18535fdb67dbed61 GIT binary patch literal 1881 zcmb_d-D@LN6hC)9)66za_p{bmviKP(jRis0m0g##E^$i?rhX7%hwRM6PMl2Qoe6Fi zg$Q|36cJpiun&E)TIz%Ugb#v3rbHVCgauj9x0J3gee&GNPSO}$koDe~bI;eD`@83y znfYdNG732M5Bc&B9Ki2P2nWi+IOyWC0Xpc!f+7i4uF7j1fvQjxN#7zBrNAN=Wn4Kc zQk^JH1UBA^R+XYcfCEFS9J?14wvgEg1<-}oKQzVYef`BT|H=FMOJn{qofIG?|H&}q zQp6YXC3g+is78&FYpA7qT^ry-M#B41ybr!309SxuPF&=|F?ya4z~w^=Kqbs2xbjfz zMuz+rM%o!ftB9dx$m!&U0+?Aw|7#STA3nQ9zhrCa8Kbu_S3~% z-011pp0C3OArEwp%_G?xVjZml>p!fql=CI0RIOXajks<)npw9@+i(^tPR;g3*Kk~C zHO2Yz>T+Qg$x%*N>=b*|ZE* z^`&}=8n)|8TFow-Z~IcCu45bFYU6J2Y!jcyj&fLylZWTiuQ>*F($$h-rSt3BT8UQk zv{o@R*GaGD7u8pvU3?}z#F}P78PEmX_&A0L2&feA;`85L_u|jBpI`CfD?jVM6yEyL{>kp-w061b<*Ge^cS(lZ2Y98w zh)djE0HkO^+7;y1%X`A9UEx&s#F^g`$**U=%H7I!5{uto_7dmY7xG>r-xK*WQV)>q z124*V89@U-XnjM@h7W1ZB4W0}?%rfe_SG2uh_5+cxMpuzUK4(Vu~H7=i%U zo-((q%y~+(qa@oxGJuwv@*~$8rsbNp;~#smzOD|Wht&Fd&_N{!ThBFX_5wBDqb9cK z#6acCp)QIK2iQs!TRUJW`&amK9Y2$nc`fMwelG?uhg>$VyA5hkhLSS4+Yn$15_}kW zp<2@$mT`eTfhJZ|XBSD2CxqY+rulDkvKh=S449cVj9guqZI52J$yD=nYtAFn9WveH s6tYBm!00{?-0`7q7P$CPM?xOy0g|B`(@0}pXPMX8qh5VYpbpObHz_WJ`~Uy| literal 0 HcmV?d00001 diff --git a/discipline/__pycache__/discipline.cpython-311.pyc b/discipline/__pycache__/discipline.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02e6bcb399aaafd322ab8915979ed7126f2cab6a GIT binary patch literal 3191 zcmbVOQEwAR5Z=4nv(Ju0lEyS7gvL=4f+I=_q%R>w0#Z;=lOiY&SgowXw;`9FeP-_r zByvSK2ntyMWI-l3$TUN_MB>`txHK+%q0_pIz_H|_v=KwPOR zAY|6(^U@>WpZenkDK9P8Tk(}10r%7&jgzn5{}R?M$P0=a^ruOlbIZwTzTRG-Xm_WO4J{i93FzyVeb!H7;m;?WK`U1CSS;}>WzqoKTd2!_EN5h8y zf$P{K**UKj_dUz)^m*`N91bwQzxKh8Gzwc_S7#DPfxO;BTBCR8ANH@b^sKh@EYecf z2fqycIP}BepAMIa6gnu#cjp(+EbU#3v@eZ6?0cA7i43ep28yDWmFl7mFi582VNXjY zGkTgOlU_KPOxp&;7Vwc|@_J6Uwj{x1(y&v>BoBcSM|67%1ftiHOzN484c7+1azIUS zT+a7_{0ZTR7T$qGs}j_7ZUHIOJ!M%|wK72xAe~)>(~H_t``zXu?S#HUJ4<`o3X_G& z+moemb77(|aeJcF7A;cjD>Uj)mT5>mxO}Nh;I%8MIEDV~jQ-OgsYm}cl2906zYh5U zLlTIVSr8Dd0HQU63SJfH4nTMZy%tO{toBts${`hbG^@Ka+pvi@DzF&^7>Fa*gE zpGUCkQ-5bj-+<^oa+EmqrhH4eLT*YDxk4m(DiSwRv)@m6yF^@!>8)loc#gWLcav0X z#cA#vSn9*~ow*`5ZKh^S#&^yYc=@A{Y*^aN#Ejhb#RzO{yCjYI_On~2A@I4KyP;pT z81Dto;+jY8EX(j#7+?<84c?DzK+nRoA;RBlJ8N2O%68A%xs35C=Qj7$EC*Y0l}BMU zA3zmpE9(ToQ;WIGLBt5R1d_Z+eR7!ZZV0k%sRL_@vks&{%6kH#_*&cECF5S#{jM@m zn#UeqU+dU^e{Q+$UjBZ5iI#f$Aa%5j39@vq6y0}!;$^h|MYR97L~&pg`X7Up_e%7{ zYV<@=bjT*4qJlYLQ;>DE{t8KLZdYz|b8}aj5Ny8INNX=pCX4IGA)s;MCu?oEjqcPM$iTJZo(N_Spe?syFzoS3$mCLViH-L!GCC%AWxVsL1%+ z(21EsptAtXf2o`Tdop0p$H}sfLNMhw7xBN<3>mN|?~FZBHI-Zrmi{{=Y_FOkgfO`O zchRhbx+;R4w}BfX#(5k$yrsBO31cjp{6ioPo)@;PlmM&=^RaiuE7rjw)Vl-(N?oL} z)OGB~5Ev0;@mwj=dUxVwr29pr`{zXQz%l5L2P^ND$nn+4@uKJ$6BQMa`k~DrKq%xfqfoNYHO_^ty7^hjU2KlA{AD)IFsGJ%#bb{>Ay#a95G`KwqIf zWw}|6FSnNoyc$WjrjB6Ug4eF3vODe>4|!U4&d}j+V2a}va|*-@z`sE=b4}D{E>45l zsB$FXFJ`LR8Qy4Pl~R9_9|HlD9^(821g=YxN+e#~sh3D^ai?A)O$G6kX}biq_ieJ& bxsJ21YNDZi5}fvJvShB~?5iC_!ux*!Jg(u} literal 0 HcmV?d00001 diff --git a/discipline/app.py b/discipline/app.py new file mode 100644 index 0000000..b8c9635 --- /dev/null +++ b/discipline/app.py @@ -0,0 +1,121 @@ +from flask import Flask, jsonify, request +from pathlib import Path +import json +from discipline import Discipline, ListeDiscipline +# noinspection PyUnresolvedReferences +from flask_swagger_ui import get_swaggerui_blueprint +import os + +app = Flask(__name__) +app.config['DISCIPLINE_FILE'] = os.getenv('DISCIPLINE_FILE', Path(__file__).parent.parent / 'data' / 'disciplines.json') + +@app.route('/ping', methods=["GET"]) +def ping(): + return jsonify({"message": "pong"}), 200 + +@app.route('/', methods=["GET"]) +def listeDiscipline(): + """ + Renvoie la liste des disciplines + """ + # Offset / Limit + offset = request.args.get('offset', 0) + limit = request.args.get('limit', 10) + listeDisciplines = ListeDiscipline() + listeDisciplines.loadFromJson(app.config['DISCIPLINE_FILE']) + + if limit != 0: + listeDisciplines.root = listeDisciplines.root[int(offset):int(offset)+int(limit)] + else: + listeDisciplines.root = listeDisciplines.root[int(offset):] + + return jsonify(listeDisciplines.model_dump()), 200 + +@app.route('/', methods=["GET"]) +def getDiscipline(id: int): + """ + Renvoie une discipline par son id + """ + listeDisciplines = ListeDiscipline() + listeDisciplines.loadFromJson(app.config['DISCIPLINE_FILE']) + for discipline in listeDisciplines.root: + if discipline.id == id: + return jsonify(discipline.model_dump()), 200 + return jsonify({"message": "Discipline introuvable"}), 404 + +@app.route('/', methods=["DELETE"]) +def deleteDiscipline(id: int): + """ + Supprime une discipline par son id + """ + listeDisciplines = ListeDiscipline() + listeDisciplines.loadFromJson(app.config['DISCIPLINE_FILE']) + for athlete in listeDisciplines.root: + if athlete.id == id: + listeDisciplines.root.remove(athlete) + with open(app.config['DISCIPLINE_FILE'], 'w') as f: + json.dump(listeDisciplines.model_dump(), f, indent=4) + return jsonify({"message": "Discipline supprimé"}), 200 + return jsonify({"message": "Discipline introuvable"}), 404 + +@app.route('/', methods=["PUT"]) +def updateDiscipline(id: int): + """ + Met à jour une discipline par son id + """ + listeDisciplines = ListeDiscipline() + listeDisciplines.loadFromJson(app.config['DISCIPLINE_FILE']) + for discipline in listeDisciplines.root: + if discipline.id == id: + data = json.loads(request.data) + for key, value in data.items(): + setattr(discipline, key, value) + with open(app.config['DISCIPLINE_FILE'], 'w') as f: + json.dump(listeDisciplines.model_dump(), f, indent=4) + return jsonify({"message": "Discipline mise à jour"}), 200 + return jsonify({"message": "Discipline introuvable"}), 404 + +@app.route('/', methods=["PATCH"]) +def patchDiscipline(id: int): + """ + Met à jour une discipline par son id + """ + listeDisciplines = ListeDiscipline() + listeDisciplines.loadFromJson(app.config['DISCIPLINE_FILE']) + for discipline in listeDisciplines.root: + if discipline.id == id: + data = json.loads(request.data) + data["id"] = discipline.id # On ne peut pas changer l'id + for key, value in data.items(): + if hasattr(discipline, key): + setattr(discipline, key, value) + with open(app.config['DISCIPLINE_FILE'], 'w') as f: + json.dump(listeDisciplines.model_dump(), f, indent=4) + return jsonify({"message": "Discipline mis à jour"}), 200 + return jsonify({"message": "Discipline introuvable"}), 404 + +@app.route('/', methods=["POST"]) +def addDiscipline(): + """ + Ajoute une discipline + """ + listeDisciplines = ListeDiscipline() + listeDisciplines.loadFromJson(app.config['DISCIPLINE_FILE']) + discipline = Discipline(**json.loads(request.data)) + discipline.id = max([athlete.id for athlete in listeDisciplines.root]) + 1 + listeDisciplines.root.append(discipline) + with open(app.config['DISCIPLINE_FILE'], 'w') as f: + json.dump(listeDisciplines.model_dump(), f, indent=4) + return jsonify(discipline.model_dump()), 200 + +swaggerui_blueprint = get_swaggerui_blueprint( + "/swagger", + "/static/swagger.yaml" +) +app.register_blueprint(swaggerui_blueprint) + +def create_app(): + return app + +if __name__ == '__main__': + app.run() diff --git a/discipline/conftest.py b/discipline/conftest.py new file mode 100644 index 0000000..e617f65 --- /dev/null +++ b/discipline/conftest.py @@ -0,0 +1,26 @@ +import pytest +import shutil +from pathlib import Path +from app import create_app + +@pytest.fixture() +def app(): + app = create_app() + # Copy the sample to the test folder using python + shutil.copy(Path(__file__).parent.parent / 'sample' / 'disciplines.json', Path(__file__).parent / 'tests' / 'disciplines.json') + app.config.update({ + "TESTING": True, + "DISCIPLINE_FILE": Path(__file__).parent / 'tests' / 'disciplines.json' + }) + yield app + + # Remove the file after the test + (Path(__file__).parent / 'tests' / 'disciplines.json').unlink() + +@pytest.fixture() +def client(app): + return app.test_client() + +@pytest.fixture() +def runner(app): + return app.test_cli_runner() \ No newline at end of file diff --git a/discipline/discipline.py b/discipline/discipline.py new file mode 100644 index 0000000..96cc3bd --- /dev/null +++ b/discipline/discipline.py @@ -0,0 +1,50 @@ +from pydantic import BaseModel, RootModel +from typing import Optional, List +import json + +class Discipline(BaseModel): + """ + Modèle Discipline + """ + id: Optional[int] = 0 + intitule: str + type: str + description: str + logo: str + + def loadFromJsonData(self, data: str): + """ + Charge les données depuis une chaine json + + :param data: Données json + :return: None + """ + data = json.loads(data) + for key, value in data.items(): + setattr(self, key, value) + +class ListeDiscipline(RootModel): + root: List[Discipline] = [] + def loadFromJson(self, path: str): + """ + Charge les données depuis un fichier json + + :param path: Chemin du fichier json + :return: None + """ + try: + with open(path) as f: + data = json.load(f) + for athlete in data: + self.root.append(Discipline(**athlete)) + except FileNotFoundError: + print("Fichier introuvable") + def loadFromJsonData(self, data: str): + """ + Charge les données depuis une chaine json + :param data: Données json + :return: None + """ + data = json.loads(data) + for athlete in data: + self.root.append(Discipline(**athlete)) \ No newline at end of file diff --git a/discipline/static/swagger.yaml b/discipline/static/swagger.yaml new file mode 100644 index 0000000..258777d --- /dev/null +++ b/discipline/static/swagger.yaml @@ -0,0 +1,133 @@ +openapi: 3.0.1 +info: + title: API Disciplines des J.O. 2024 + description: |- + Cette API uService sert à afficher les informations sur les Disciplines des JO 2024. + version: 0.0.1 +servers: + - url: /disciplines/ +tags: + - name: discipline + description: discipline des J.O. +paths: + /: + get: + tags: + - discipline + parameters: + - in: query + name: offset + schema: + type: integer + description: Le nombre d'éléments à ignorer avant de commencer à collecter l'ensemble de résultats + - in: query + name: limit + schema: + type: integer + description: Le nombre d'éléments à ignorer + summary: Liste l'ensemble des disciplines + description: Affiche la liste des disciplines enregistrés sur les J.O. 2024. + operationId: listedisciplines + responses: + '200': + description: discipline + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/discipline' + post: + tags: + - discipline + summary: Créer une discipline + operationId: createAthelte + requestBody: + description: Objet discipline a créer + content: + application/json: + schema: + $ref: '#/components/schemas/discipline' + responses: + default: + description: Opération avec succès + content: + application/json: + schema: + $ref: '#/components/schemas/discipline' + /{id}: + parameters: + - name: id + in: path + description: ID de l'discipline à récupérer + required: true + schema: + type: integer + get: + tags: + - discipline + summary: Récupération d'une discipline selon son id + operationId: getdiscipline + responses: + '200': + description: Opération avec succès + content: + application/json: + schema: + $ref: '#/components/schemas/discipline' + '400': + description: ID donné invalide + '404': + description: discipline introuvable + patch: + tags: + - discipline + summary: Mettre à jour une discipline + operationId: updatediscipline + requestBody: + description: Mettre à jour une discipline existant + content: + application/json: + schema: + $ref: '#/components/schemas/discipline' + responses: + default: + description: Opération avec succès + delete: + tags: + - discipline + summary: Supprimer une discipline + operationId: deletediscipline + responses: + '400': + description: ID donné invalide + '404': + description: discipline introuvable +components: + schemas: + discipline: + type: object + properties: + id: + type: integer + format: uuid + example: 123456789 + intitule: + type: string + example: Judo + type: + type: string + example: terrestre + description: + type: string + example: Le judo est un sport de combat issu du pays du soleil levant + logo: + type: string + example: https://olympics.com/images/static/sports/pictograms/v2/kte.svg + requestBodies: + User: + description: Objet discipline à ajouter + content: + application/json: + schema: + $ref: '#/components/schemas/discipline' diff --git a/discipline/swagger.yaml b/discipline/swagger.yaml deleted file mode 100644 index e69de29..0000000 diff --git a/discipline/tests/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc b/discipline/tests/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a8c86dc2b42349753e13818caedc6bfacdb0104 GIT binary patch literal 1250 zcmb_a&ubGw6n?Y2-Q8xBw5e@Xs3FBuq%;nlh0*(t#unIM6Bo)v>{3;>Bmz~XJVe41wXE8bExTG(FY5#(pz8W?bkza^gz<&;Z}KlCKW%lQvm_ymrbp6i<0(c@iWnMophq%tphC#i60#x{a&1 zitrQ&Chuc$qR}t?9lVA0_;foeT0|DRO>D^_JILCLnFPOk0b7KZ52eY)Kr203DJ>Nr zm+~^>5(3q#*H)cZf!b`?sB@PDGD`LM6}j_;!gG%@uTV9qTUc&cYbL8Mv-&!w#nS8_sHJ{9OMn~v){H7`is zZ?r10ceB!HiEf!5cG`F9wOK|tnB&u6I1U9`6ieX8LYv6~H8cYa-^(nn@I7g(&KtkU zXxMvRIL1Zpf>0vLdamEA*6pTC7g!dLcq_ec2wj;F(&2ghW2V|-b|AOk|2rWg?a9sT w0m&SZOjpuLx(f(rGDu@_H@-F7LVuytZB}gzRU_EA}q4{0}u!W1poj5 literal 0 HcmV?d00001 diff --git a/discipline/tests/__pycache__/test_delAthlete.cpython-311-pytest-8.1.1.pyc b/discipline/tests/__pycache__/test_delAthlete.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6967234b76525c3d6a23b6c0bdee97bb236385c GIT binary patch literal 2132 zcmeHH%}*0S6rb7MwzLQhQ4>v68iMFvTFQrLVvQU~JQ1RJ;oxTJOrdqR+srHqb(;|5 ziHjE#IC$WMQ4;?Sk76((b29P7jfBJ)PQKY~x7~vA=0&HSH}B*3-psstZ{PLxr4S6` zhg$jw>vt)%hSFq?M}XNx1QBeZ1?;bitriuGY;7Ti5m8>Dg*d^Fkggs|r`aNI`pP8% zQ-yi_1n4I6&^8FC2f;hwwHtaiu!k#cNg+x@^%PHi>YuBjc^d!V#@cZzau0tH)zeCt zXcApgb~K0zvuTUD$rg`LCG_6`u{<@j3h{p7b|05in{}?6Cf0c6yDm4M*LpN*4D;!o_f(_GKGxfMs=R$ zuIa9Dqv#NtYGew5VN&O!hf!X0s+{VoPys8+m?V?+ev@;`TtCJXKtVZGegmDV_lh{7 z7ImrU?FcMV_iJk|<*s2eF<=x;%Q6_Pv7%$wOh&~yFdK_2Wy>vBxrkd1Ah@_>@MUMk zB0khuvYbWJGODIcMbhAAiOL9~f7(Av*{RMj<}h*IC^^hFU4yRHEVF95U=ivPb@haZ z7p*d_x=gmR=#_g0_sDk&6P?0%AOs-WF(E|p!okh5Gli|&ZZoqet=ohc zPh7m1z`+A2jB@h7@F)foGA9#H+%Q}WC*SP0+it;l^P&Uu_I>=`Z@%8^yYB8Zg7KqI z%YTILcWE?+QdbU#L0Lxx5$vFO9DJ3cYAF~MwfO`_M0thglLS9Py1FluW-MIKm7f$$ z73Sd+kn6}tn_wIt1aCpsc9dDeJ}xvZg(x-ESA6wpu&#Z^51zYf}sD>GgT!wy8j8DNykUZ^t6tvQT|vLM+m^%N37u&oG(jH7wI{3`Wb$a*Jh?QE>{w#=^4gcy@`4q~ijDi}MCwa+e(v zK#fJmT`(P^WEQFDG`N|kl0o#`3ASRprF)FIOq?^Av{+!sgs+nSW6}=&N8<^ zEsy8sW=D;O6Qd(JVspzbJ9dfYM?5vPNjin-ZESWNyB@}D4A(O%}< z>)KjvV{RuixSJVVy}zdo?`p%JCpai~h Ik_f1O0aWqq7ytkO literal 0 HcmV?d00001 diff --git a/discipline/tests/__pycache__/test_getAthlete.cpython-311-pytest-8.1.1.pyc b/discipline/tests/__pycache__/test_getAthlete.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a9eb02ed637afefa5a51656e8337b63904ccf72 GIT binary patch literal 2916 zcmd5-&2QX96d!-A_dDA(Nd@B2AfVKj^=`UJ3u+J*DpW$Ms!}ABi{Rt*!ks3z)v$CcwurP)V}AwJ z42p)Ol*iRj+m>)mptt3HsNo*G$-woXneqq?%PC)s`#ECE1>afDp*BSgHgo z>6#-{@KjIrVWsG)D18ZSen*NFcvW~c=n84rNNpnppNOiV5>{t@qJ}E_xezDcCo1&G zqCg+%lmFlPzFePFi>rlC-c{mtui+)U9M)#LP=ASCs24pIrTi{LRgByRvY$aDLuTa8#D2nJMuK44QAZvp31lbVe!lY#}MraVH z(S@K;)VSMu@++(=Bz*24vSWc2>@u?tNj^_)U1z#N#gU_@#Ku63^~pjm#(4+<%JX`Y zD+3Zxaoh6R^GdlY%HXy4{c%88U|N(fntjW0OiFwT+vHo6@CHnqH+O9(ut$t*jt2r4JzGQBD)U^VW-0>Z2WCbw8 z>$&3e2G{zIO-2D-7IiI&jO!wSq^xdUIF}dB=Y{n|NP@kSRFDYWf`?unw~-evFmdgy z=Ek$j#;34*%y2E@7}v)A9gDixs5d12fEhQgbkvqM_gDyY4^3Mu-%P%qeE;Ty*6ByB(|cE*YN&N;T5n8&HkPK%m1*GMGCWMam^?86iQ{No%ZGfbGwJi%mQ z-YQdOelpEb*ip=XIr%A=a2uoq&$!(4ia2EcRpEY=)86Az&YkzVDM^sv)6K*3)%lk1_K96>m;ZAxxa zJAk8}sl@#o>*(uX6QA4ox{15{Cqy@dwg=={n4V=i#1LYi38}(Mg6D}M^9TX@KG9E6EbM{Roy;;6d~P)v zSi};RaS4}ys_c~j2Tw9WF%So76)2QM6i9n8bteU&P!5Da<|`}(a!|S&TZHAH%>MFg z859jNzqlHddjhU;bWhxa-0#4X3|#k{DUZ;glJdp4*AP2V@Xc2u9)Vb_l@@VXKGQfy zfXLY_M9z4qxv+ZAK*>cEZv4wSruOWabxh%)j>a=GEKOxNbE`BeBNSF}6VC<}Q;4Oi zznHEiLIqEaR3B7}o{G|!(BU_PP=ZH>rwm;o3}#Z>P{MOzEs%oRq)+5PW zF~0^Pd1Hv(sQj*LZ|nZ>XHb1ilmX}z!hB`uC_42O8;Ge}%0TyAM>p9yrRO^M?rp`> zsj^}AUFs^Ex&tSRX^jLfJ!H(~H>`hf2 zdX}r>_I2~BX%lSfZI|{{E_qy1AtBp!nA%ml+bXYI^Gw5c`&75v>{`E7ibNAfRTHxG zhZc!sf7c^X0~2OY)8pwyvgP*OmT(}hueXGe@%mYBx7%X%FD8t#u5fNSRRJ?mEwxzN zb}_LuJhZ)fc(zp!mGuO^mcoIVz%KxNz+9&uMs=Ls`cah<2AjYLTg`8LZAwlLt7ck9$JP)%w-(oF^9s_GS&wB=9oFC2R zVyr?4P_DL`k<=$X84=tYg~xI+uaP_1vd+q$JWx=o^*#`GTH9-`TI;(}p+-lx>1(Q&QkQd{>m za>KK9M~`=|kt?n>6O|3igifM!yslZEaf2t2l-0=#D|ulxFPu+=B-k5C1&PopcKFJK6XU6jBhw|xr#ovRR_~>bX1b;uBN-&uS zvzb+mZYSp4@de-SHpom`5lG~x3oIp6sZAz|F z(}$CusiMk6M_&e;_}Ip`P2AJB5$$lT3gn>r7TiB@hrdl%LFC^$wh3fk5(Ht)+y3c) T_t2|TzkRtboY_Z5NgV8N39$CP literal 0 HcmV?d00001 diff --git a/discipline/tests/__pycache__/test_gettest.cpython-311-pytest-8.1.1.pyc b/discipline/tests/__pycache__/test_gettest.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e5b477a831461dbb44bb0977c23a930d77a1210 GIT binary patch literal 1462 zcmZ`(%WD%s7@yhQY#wb5r3$_dAtJgUNo!kEM8yh%r&4@ea#^<7NxETQ%xr6u?m`ex zUOd)=2Tv6(^l!0Hq0H5jw?c0{`OR*!dDPBkzWsi``M%#gzS(@AnJFM>tB2>L2k$=u z44Tw;&MM&SArqO{Ms*y&B}Z;Z7&%HkgOd@puA0(ERL`3D4btSlB6+QW`x@evg)T!s zI|OzQh3FfAi_OIQFn*9`M%a`hDa7p|BZpEbZ=*;F74|d83^m$9KW?TRDot!EBC;v% zE1(EHX=52F8%pEznwel~kbzKJ9r*j;91H;3M(paCp4esF!ijy*@wnl<6LBtvnMe0D zjFcxDe-B;b9{pmp4XYtB!c175UFo$S$1H#jH|W>!Hi$m*`~K;n{k%jvb_z%)?LFP z{K{%Px8=DnDfK9y)0-Z3j6f$lzHPWh03%wKs}0*CZa_t~_>`Ca0_*O&S5KzFl9?UoS1pSB+rPCIP96LtwGh z;dBJ5_#K{)&qfrD-U4ayX)pvIQ4gIIuYK-*>VCO*R4gAC%e_}8O6^ztJ03U*Ozm%_ zcG=KTJd_3`5E2t6g((A0N?y$RZ&;E7cJv1#N$`Y7Dn1%K>Yu?uAT7spg9|~6K=2!S za+@ki4WYsnO5Rf|fSZ(3Jlp5!w8&ADl=ysqAbJ}(dK1if?up}>TQ+${?*J74Tx=E0 asf;l`5hb|fnd!yPsUqQ(Q*_b9Q~U$<^Kl>m literal 0 HcmV?d00001 diff --git a/discipline/tests/__pycache__/test_listeAthlete.cpython-311-pytest-8.1.1.pyc b/discipline/tests/__pycache__/test_listeAthlete.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a21fcd789c0da6fc0d6554082581678e52d4a95 GIT binary patch literal 11730 zcmeHN&2JmW72hR?6h-RGv7N+8+ei%@h_NGzQfxUzBh)~fek2Hteq4MZHr$m>WyvLG z*R7>4VWVh)wkXgPD9|2q@S$*#`tQh{6iAd*R(w2$-%q4t^m1YDksP~fWkzW3tE?oSX@t3bBEh}9 zZS+W(56L~VR|pIK--3#a8ODB5+IB81?3s3+A@_2RA^(Zuut~5iagf92$&ch%e`@#_ z65R6=7v_>9q)8lKHHOu39TR%Sqdi9ElZ+EkB#E*MpXLu?#=W9lwCBQN%jDi%0P7`> z1k-|S4d;?fN$(f1kM5hjJp2?uYpi}LcvW)1{7T8Z9zFJ5!CB1lGm0({H_$k9)L3_(CT~dqITMQTXm!#V( z!8yMamck`C=SfKX2Z?zc@2DJ{>!!WTkot~FWE|_Wm>l5*R5q=bl3eyme0DC|=j<24 z1iZ9JnL@Hi5qlz!jR75IXJfn)2W-3llu_T@saN+Kok6ZmPBd~K#AQW8IfpH|7$ znUY9J{3L!_CD&$3A|=t&_?`G$&&OHsIA8yNQ_?oz3;ydLJ~wMs^Ler5uXenkY94N0 zUhf`kRsHhMV`KH*Rc38g{SE4QLAkdou)OtJ)sGfj^zFZzkMaO!)fTI+9WGhE@6dq5 zcK{BJJCuFBhi=VX=<)lE9*9JbU>x_j&O+NV1V$XHtHD%ZJiDCx2IF>}PPIVUw|Lf1 zomJbEeW+vBjl*R$mO9KRA^_TiF>Fogs7sxW*G|s+deyJyDSN`9?B>kauP0^-qoM3( z1KhPK+$R#pi!fo{UF*P44`zCf@7#Az%BfEoTB~i{IWcKxN|N33o2{NqEkaeGQC zb($WvPfXjGlGd_MnppKeWr#fk%NVhJm|J@n7HV2IY7?x9(}8UO^YF@)Zp6|i;8&-> z<7Lf^kC!xoe`^ZAT+T$owDm|XB}#iS%HMGU$}X(YitAbSd(>xfzR@;f@8@{#YFotO9Ccmh(yv}zh^=NdyvFFjY6*QdtUDxVZ0YvB`8(~qb zT$G3PfYTjLo`^8*q5@YWs-SsBbMYpOaxJ^M5*3?n%jpDBi8_AQ>%e7fj!{~YRB{M)_wvr@oR0% zacj2@ns+VQzD2!#ry2OQAK$7se)x8Mvu26^%dN5X^LgFC*ZGa^L3EC;1`PG#N0EA%RgYu&FH7m~KhKPQ(C%HayV+U=6SO((vGp$>kxr+$Wbu zydFpwT2wibHbem?@G7bdl`AmH1%POQS~aFy1ka>gh-$gGMRlPX1velP0Ho#7o5khLw0S_ra7FRF;GED_7K&7z>P7RFRF{uqntuJeA zfSoQQgw&z~9#ViTu3!LUnhIKgN@Ek88W_7{c~Xm08_sTJfSoR*$pCSEP=GA1;0L7E zk!S%bjZJWBVC;VBrc)o1dY{BKHo#7o5khLw0S_ra7FRF;GED_7K&7z>P7RFRG1(cC zoxZHG0d~5K5K@Z{ct`=VxPk$YX)0&|DveEWYGCY+$@Y+J_hpR@u+wFPkXm%WLkf_^ z6%2q(Q$Y(*X>5X117mkgHil%QFKcXooh~DU)S?3(Qh+S3U;t#A3R-|lV-uVj7`tQg z#*nC!Ev;dXHCO9=PcE{xEkX-G{8XI7XawH9K zM+m6^!Wi(70%UPjVwwtCfJ$Q%oEjLrV^STGYG2ma09%wJX@EOINCgnafQJ+yi>ngT zRL}xc8k^wMz}Ov=H;3fSzO1nUwkSu^0C$9t3LuOD4=F$vS0$#YparNjHo>WZvHPW) z&Xpm#(kF3^4X{Nyk_NaVgj4`w40uQZvbZWSO$9ALrLhT44UFBfyvvJc1GvK~L)~qU z&|O>Ird3oKSsnvaI=;XE7oQ5mV4lI-s8OAdO7cSkWq*4bU22F=6C2_^9zGXrQ0Hf~ z6~Jc()x^FB@HWc+s4$v~---FB5B{qAnL)n+5qblv=cRA9J^P;P?9!XyvcF{Z--qgH c-ZafIdmBFaU*A~O%G2BhbL***u6Sqv2H9%GO8@`> literal 0 HcmV?d00001 diff --git a/discipline/tests/__pycache__/test_listeDiscipline.cpython-311-pytest-8.1.1.pyc b/discipline/tests/__pycache__/test_listeDiscipline.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83b75ba912f5c959556c92efd1522ee2a027eff8 GIT binary patch literal 4072 zcmd^C&2QX96!+M(-`U-Sw56q&NVGI=rEE6aHU*K2N(J#*Dn29>30dxr({AcruVx%* zf~{7F3kV5raOj~2R0REJk}44^OPn~tDN=7a@y4^B^?18cjz~D(+4u2#zxU?Nc-Hx~ zS}kjET|14}e57fAaV5F*q3~=8gr78`5#(yCDE`*FMq5W(7q8|J_vmiETUafCY<#1w z776-TGx6V?;D(8!`uiQ`JJ&m`-RZlXp3{a@@+yL4z~|XNd7dWF9!XbPAm}kfJ&}o@_22GK$s7y;fuVJ^tA-~CGB)3kM{ zR9r+4a%+ef9J!`HhWICvgJ8d&;2?(2GrrVXztGq*g}apC0$r_;s~^@^CV9SLB4}xt>C|yRH|tT(^O&)3M!MTeGOab9tsWk0^;CAGIm@swP&uo_g?Yl#{bVPDmPVogYbqC+89t$P@&cI+>cdV}j?l@9%A{(;PFA6daW|nmhjmh( zcF;*pbP`U_!@8))T|D-G>mt$M=kfTB1Jp3lLB7N0dY*5ht;MtR{mmtlmA@Jab1QRP zS~S@L^*p~E&IxR6y=1be?y5hmrV$x{=fe^oE^XCp#vJO$5z7z(#%0R4H)KXvA?Z6j z18|9?AdHgEb5|>IfiQG6Wj!X8bT#I_!ns|iXXYsXq2qa)d}vYr1aRrfRy|cq6DAd5 z04+ix`pODaa{iEX9RitFL_02<%rq!}Y$@MyocppePpE}Xg9kVphj8}1uG&#fJRKRg z9iQ@-glO9JZ1NWMy6-Ztcir~w2$Oi^ixHLI=x_QC^DUc3GgjMnU5h$>+V;AAn>x`H zxUE|o9oO&lSd@1?K(Oef#qM|;E{UPmZP&YHyH?NcI#J1D_L{>PqT021%{pH1O-el) z&01?7?b^QO-0Qn`&-TGXtGq(JLm~s_0cGdgZpZ2QQIR^V@AY8O3mj45Wr)5v)5^uP z(h`cuayhMBNyC=X%5qv!VqO(<7i>)>>tZ_Sl28P0nei2y%UlN^G(LryutwK*+{Vq# z_8pscZ&GjFY5T14$<3zq(Z%LsgUC0_244{tk5=Z{Sm!b2|w4ID;0p#&}FNMr_cVd?W97PojfE=d+3s7a)1ZM_3u0DgqKr>5GF*&>`f6I?f zS>m`~5Qixo!3)&+l6HJJx}_;93~lrzk13B1ruY{nK5Tx|XbYUQ3By70^{z)YT<0o% k4OITmEqf1!eFGsh;OEiv|F*SPGWWh-K&^dke2A0%3$7TJEC2ui literal 0 HcmV?d00001 diff --git a/discipline/tests/__pycache__/test_ping.cpython-311-pytest-8.1.1.pyc b/discipline/tests/__pycache__/test_ping.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4258ec0511168f63924bf30f70fe89bc7515d74f GIT binary patch literal 1435 zcmZ`&J8u&~5Z=8zKl8AnfW)h?Eb%xA*$GK-Ks*a1swm>oq&YUewR6_Jw0jA$b026jVVFiT}VqAX1Q`ZK>!YrAx)^oqe&LFut9gZ+7O}-I?{*iHRZtdGqsxGMN8J z&=;j=9Nz$A2bsvkHd?{)tT<{z!N}28G8mc4d$f`@@k^wue`V5A1NU;}l!KwdIQ{|N z9TcK30M3eucVT@m&5W?AL`sO;ryey_LUjX0TBz~wLFSaBE%{?H)lh3NvJq~1^&-q*|oWBm+-+S$In3&vg_pbf+?4DrcZ#uAR)gAK=>?mH6aV%T}`PN$LT z$O*Hb)h?VX%9uGbA7(5ZqiitMKLbKTOohHb%nmaRqj%8TO&lpOv`94(^s}(k&D4C$ zZSiT~*s3^`bJC)f`xW1FTa~D&W1(2Cj=Q(7-|{=NIxkypS(*zvFK+dW^269Qr}L^w z0#b}bo>2}Aoa(AjAy29~sTR7EgmcP*IHRs}>8$HTp|)rsG?^}nPy9|md0-GGri=!$ zZG%ytH9W^BjEaK6Ns~&C7>7t>b=|T9%jF_#dl1aUC4;Yd>$VwZ87ZwO7lt#*6vcnHmY!Y#Pv-ECP?UA$wHy zJE9O5Mk00oIAF&F+xa9Z1I8rgjhqCnN|BjG? J7td2M^*`!BXnp_y literal 0 HcmV?d00001 diff --git a/discipline/tests/__pycache__/test_ping_discipline.cpython-311-pytest-8.1.1.pyc b/discipline/tests/__pycache__/test_ping_discipline.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b74945cb76a458c31c4d43bd9b01821f0637a62 GIT binary patch literal 1460 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 LZ3pcqfwO-BB_I`M literal 0 HcmV?d00001 diff --git a/discipline/tests/test_delDiscipline.py b/discipline/tests/test_delDiscipline.py new file mode 100644 index 0000000..02037c1 --- /dev/null +++ b/discipline/tests/test_delDiscipline.py @@ -0,0 +1,6 @@ +def test_delDiscipline(client): + response = client.delete("/1") + assert response.status_code == 200 + + response = client.get("/1") + assert response.status_code == 404 \ No newline at end of file diff --git a/discipline/tests/test_getDiscipline.py b/discipline/tests/test_getDiscipline.py new file mode 100644 index 0000000..b69ed1f --- /dev/null +++ b/discipline/tests/test_getDiscipline.py @@ -0,0 +1,13 @@ +from discipline import Discipline + +def test_getDiscipline(client): + response = client.get("/1") + discipline = Discipline( + id=1, + intitule="Judo", + type="Combat", + description="Le judo est un art martial japonais, fondé par Jigoro Kano en 1882. Il se compose pour l'essentiel de techniques de projection, de contrôle au sol, d'étranglements et de clefs.", + logo="https://upload.wikimedia.org/wikipedia/commons/4/4b/Judo_pictogram.svg" + ) + assert discipline.model_dump() == response.json + assert response.status_code == 200 diff --git a/discipline/tests/test_listeDiscipline.py b/discipline/tests/test_listeDiscipline.py new file mode 100644 index 0000000..778fb6a --- /dev/null +++ b/discipline/tests/test_listeDiscipline.py @@ -0,0 +1,9 @@ +from discipline import ListeDiscipline +def test_listeAthlete(client): + response = client.get("/") + listeDiscipline = ListeDiscipline() + listeDiscipline.loadFromJsonData(response.data) + assert listeDiscipline.root is not None + assert len(listeDiscipline.root) > 0 + assert listeDiscipline.root[0].id == 0 + assert listeDiscipline.root[0].nom is not None diff --git a/discipline/tests/test_ping_discipline.py b/discipline/tests/test_ping_discipline.py new file mode 100644 index 0000000..a2695ea --- /dev/null +++ b/discipline/tests/test_ping_discipline.py @@ -0,0 +1,3 @@ +def test_ping_discipline(client): + response = client.get("/ping") + assert b"{\"message\":\"pong\"}\n" in response.data diff --git a/discipline/tests/test_postDiscipline.py b/discipline/tests/test_postDiscipline.py new file mode 100644 index 0000000..bd288e5 --- /dev/null +++ b/discipline/tests/test_postDiscipline.py @@ -0,0 +1,17 @@ +from discipline import Discipline + +def test_postDiscipline(client): + discipline = Discipline( + id=1, + intitule="Karaté", + type="Combat", + description="Le karaté est un art martial d'origine japonaise, dont la pratique est à la fois un sport, un moyen de self-défense et un art de vivre.", + logo="https://upload.wikimedia.org/wikipedia/commons/4/4b/Karate_pictogram.svg" + ) + + response = client.post("/", json=discipline.model_dump()) + assert response.status_code == 200 + discipline.id = response.json["id"] + response = client.get(f"/{discipline.id}") + + assert response.json == discipline.model_dump() \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index e69de29..068f708 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -0,0 +1,46 @@ +version: "3.3" + +services: + + traefik: + image: "traefik:v2.11" + container_name: "proxy" + command: + - "--log.level=DEBUG" + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--entrypoints.web.address=:8082" + ports: + - "8082:8082" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + + athlete: + image: "jo2024/athlete" + container_name: "athlete-service" + build: + context: . + dockerfile: ./athlete/Dockerfile + labels: + - "traefik.enable=true" + - "traefik.http.routers.joueurs-service.entrypoints=web" + - "traefik.http.routers.joueurs.rule=PathPrefix(`/joueurs{regex:$$|/.*}`)" + - "traefik.http.routers.joueurs.middlewares=joueurs-stripprefix" + - "traefik.http.middlewares.joueurs-stripprefix.stripprefix.prefixes=/joueurs" + volumes: + - "athlete:/app/data" + + discipline: + image: "jo2024/discipline" + container_name: "discipline-service" + build: + context: . + dockerfile: ./discipline/Dockerfile + labels: + - "traefik.enable=true" + - "traefik.http.routers.disciplines-service.entrypoints=web" + - "traefik.http.routers.disciplines.rule=PathPrefix(`/disciplines{regex:$$|/.*}`)" + - "traefik.http.routers.disciplines.middlewares=disciplines-stripprefix" + - "traefik.http.middlewares.disciplines-stripprefix.stripprefix.prefixes=/disciplines" + volumes: + - "athlete:/app/data" \ No newline at end of file diff --git a/medaille/Dockerfile b/medaille/Dockerfile new file mode 100644 index 0000000..78163f1 --- /dev/null +++ b/medaille/Dockerfile @@ -0,0 +1,19 @@ +FROM python:alpine3.19 + +WORKDIR /app + +COPY /requirements.txt requirements.txt +RUN pip install -r requirements.txt + +COPY medaille/ . + +RUN apk update && apk add --no-cache curl + +HEALTHCHECK --interval=5s --timeout=3s --start-period=5s --retries=3 CMD curl --fail http://localhost:8000/ping || exit 1 + +COPY sample/medailles.json ./data/ + +EXPOSE 8000 +ENV MEDAILLE_FILE=data/medailles.json + +CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"] diff --git a/medaille/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc b/medaille/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..32d37cf0a0a3b4df6b11f1c0056a2ecbbd9a646c GIT binary patch literal 1858 zcmb_dU2NM_6u#HKw$pe?)BO`l)rp@iQ7Atw%S(da+V2guon!Mr_Ig^P0Y~j@)@!HJA>DhdKMtNm6Ka&}v1j0jr<+@{-0V!2$Yn0VCh$+gF zVd{z!h&7e!mJ^7js1CZZfHqhPp(T^rRnP|Lx zm&BWwZjc*qf1dmz>8GCdQxmsVz0{=p{G6AX`}yMS*WdZE@KeFhtFE@<Q~vDSwEfo?zERa?_S7z>3mz@Pl#;<fO7n^@q`!R26P|KUPkO@S9+}!DQy!W2$+R0?`*09c z>UKW}tUp=;V84Q zoHo!MIr-Vt$5Wm>>dT`p8Kunje?X$gArOEXjzB52Zdp1V1olAxIQTPgHbW2s+mpw3 z=;M&3?57Y&HTWKy#?`RdJFOSK3DT0W>eQ)D8A{6FZle%WnBe=d3ze!? zH}x0jGhkvxwReHEd5kgqNgDq)6OC}TP?tIBqQ0wz(r*8C2M;%nH^)3Y;^UDvC*zlJ p8!>v1N8I6_E{(XME>yiPH%QwO~|nou2{`ER9SfkXfR literal 0 HcmV?d00001 diff --git a/medaille/app.py b/medaille/app.py new file mode 100644 index 0000000..ebbf072 --- /dev/null +++ b/medaille/app.py @@ -0,0 +1,117 @@ +from flask import Flask, jsonify, request +from pathlib import Path +import json +from athlete import ListeAthlete, Athlete +from flask_swagger_ui import get_swaggerui_blueprint +import os + +app = Flask(__name__) +app.config['MEDAILLE_FILE'] = os.getenv('MEDAILLE_FILE', Path(__file__).parent.parent / 'data' / 'medailles.json') +@app.route('/ping', methods=["GET"]) +def ping(): + return jsonify({"message": "pong"}), 200 +@app.route('/', methods=["GET"]) +def listeAthlete(): + """ + Renvoie la liste des athlètes + """ + # Offset / Limit + offset = request.args.get('offset', 0) + limit = request.args.get('limit', 10) + listeAthletes = ListeAthlete() + listeAthletes.loadFromJson(app.config['MEDAILLE_FILE']) + + if limit != 0: + listeAthletes.root = listeAthletes.root[int(offset):int(offset)+int(limit)] + else: + listeAthletes.root = listeAthletes.root[int(offset):] + + return jsonify(listeAthletes.model_dump()), 200 + +@app.route('/', methods=["GET"]) +def getAthlete(id: int): + """ + Renvoie un athlète par son id + """ + listeAthletes = ListeAthlete() + listeAthletes.loadFromJson(app.config['MEDAILLE_FILE']) + for athlete in listeAthletes.root: + if athlete.id == id: + return jsonify(athlete.model_dump()), 200 + return jsonify({"message": "Athlete introuvable"}), 404 +@app.route('/', methods=["DELETE"]) +def deleteAthlete(id: int): + """ + Supprime un athlète par son id + """ + listeAthletes = ListeAthlete() + listeAthletes.loadFromJson(app.config['MEDAILLE_FILE']) + for athlete in listeAthletes.root: + if athlete.id == id: + listeAthletes.root.remove(athlete) + with open(app.config['MEDAILLE_FILE'], 'w') as f: + json.dump(listeAthletes.model_dump(), f, indent=4) + return jsonify({"message": "Athlete supprimé"}), 200 + return jsonify({"message": "Athlete introuvable"}), 404 + +@app.route('/', methods=["PUT"]) +def updateAthlete(id: int): + """ + Met à jour un athlète par son id + """ + listeAthletes = ListeAthlete() + listeAthletes.loadFromJson(app.config['MEDAILLE_FILE']) + for athlete in listeAthletes.root: + if athlete.id == id: + data = json.loads(request.data) + for key, value in data.items(): + setattr(athlete, key, value) + with open(app.config['MEDAILLE_FILE'], 'w') as f: + json.dump(listeAthletes.model_dump(), f, indent=4) + return jsonify({"message": "Athlete mis à jour"}), 200 + return jsonify({"message": "Athlete introuvable"}), 404 + +@app.route('/', methods=["PATCH"]) +def patchAthlete(id: int): + """ + Met à jour un athlète par son id + """ + listeAthletes = ListeAthlete() + listeAthletes.loadFromJson(app.config['MEDAILLE_FILE']) + for athlete in listeAthletes.root: + if athlete.id == id: + data = json.loads(request.data) + data["id"] = athlete.id # On ne peut pas changer l'id + for key, value in data.items(): + if hasattr(athlete, key): + setattr(athlete, key, value) + with open(app.config['MEDAILLE_FILE'], 'w') as f: + json.dump(listeAthletes.model_dump(), f, indent=4) + return jsonify({"message": "Athlete mis à jour"}), 200 + return jsonify({"message": "Athlete introuvable"}), 404 + +@app.route('/', methods=["POST"]) +def addAthlete(): + """ + Ajoute un athlète + """ + listeAthletes = ListeAthlete() + listeAthletes.loadFromJson(app.config['MEDAILLE_FILE']) + athlete = Athlete(**json.loads(request.data)) + athlete.id = max([athlete.id for athlete in listeAthletes.root]) + 1 + listeAthletes.root.append(athlete) + with open(app.config['MEDAILLE_FILE'], 'w') as f: + json.dump(listeAthletes.model_dump(), f, indent=4) + return jsonify(athlete.model_dump()), 200 + +swaggerui_blueprint = get_swaggerui_blueprint( + "/swagger", + "/static/swagger.yaml" +) +app.register_blueprint(swaggerui_blueprint) + +def create_app(): + return app + +if __name__ == '__main__': + app.run() diff --git a/medaille/conftest.py b/medaille/conftest.py new file mode 100644 index 0000000..ac0de99 --- /dev/null +++ b/medaille/conftest.py @@ -0,0 +1,26 @@ +import pytest +import shutil +from pathlib import Path +from app import create_app + +@pytest.fixture() +def app(): + app = create_app() + # Copy the sample to the test folder using python + shutil.copy(Path(__file__).parent.parent / 'sample' / 'medailles.json', Path(__file__).parent / 'tests' / 'medailles.json') + app.config.update({ + "TESTING": True, + "MEDAILLE_FILE": Path(__file__).parent / 'tests' / 'medailles.json' + }) + yield app + + # Remove the file after the test + (Path(__file__).parent / 'tests' / 'medailles.json').unlink() + +@pytest.fixture() +def client(app): + return app.test_client() + +@pytest.fixture() +def runner(app): + return app.test_cli_runner() \ No newline at end of file diff --git a/medaille/static/swagger.yaml b/medaille/static/swagger.yaml new file mode 100644 index 0000000..7d18542 --- /dev/null +++ b/medaille/static/swagger.yaml @@ -0,0 +1,136 @@ +openapi: 3.0.1 +info: + title: API Médailles des J.O. 2024 + description: |- + Cette API uService sert à afficher les informations sur les médailles des JO 2024. + version: 0.0.1 +servers: + - url: /medailles/ +tags: + - name: medaille + description: Medaille des J.O. +paths: + /: + get: + tags: + - medaille + parameters: + - in: query + name: offset + schema: + type: integer + description: Le nombre d'éléments à ignorer avant de commencer à collecter l'ensemble de résultats + - in: query + name: limit + schema: + type: integer + description: Le nombre d'éléments à ignorer + summary: Liste l'ensemble des médailles + description: Affiche la liste des médailles enregistrés sur les J.O. 2024. + operationId: listemédailles + responses: + '200': + description: medaille + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/medaille' + post: + tags: + - medaille + summary: Créer une médaille + operationId: createAthelte + requestBody: + description: Objet médaille a créer + content: + application/json: + schema: + $ref: '#/components/schemas/medaille' + responses: + default: + description: Opération avec succès + content: + application/json: + schema: + $ref: '#/components/schemas/medaille' + /{id}: + parameters: + - name: id + in: path + description: ID de l'médaille à récupérer + required: true + schema: + type: integer + get: + tags: + - medaille + summary: Récupération d'une médaille selon son id + operationId: getmedaille + responses: + '200': + description: Opération avec succès + content: + application/json: + schema: + $ref: '#/components/schemas/medaille' + '400': + description: ID donné invalide + '404': + description: Médaille introuvable + patch: + tags: + - medaille + summary: Mettre à jour une médaille + operationId: updatemedaille + requestBody: + description: Mettre à jour une médaille existant + content: + application/json: + schema: + $ref: '#/components/schemas/medaille' + responses: + default: + description: Opération avec succès + delete: + tags: + - medaille + summary: Supprimer une médaille + operationId: deleteMedaille + responses: + '400': + description: ID donné invalide + '404': + description: Medaille introuvable +components: + schemas: + medaille: + type: object + properties: + id: + type: integer + format: uuid + example: 123456789 + type: + type: string + example: Or + sport: + type: integer + example: 1234567 + disclipine: + type: integer + example: 1234567 + pays: + type: string + example: France + logo: + type: string + example: https://olympics.com/images/static/sports/pictograms/v2/kte.svg + requestBodies: + User: + description: Objet médaille à ajouter + content: + application/json: + schema: + $ref: '#/components/schemas/medaille' \ No newline at end of file diff --git a/medaille/swagger.yaml b/medaille/swagger.yaml deleted file mode 100644 index e69de29..0000000 diff --git a/requirements.txt b/requirements.txt index b5661fc..70fbd72 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ flask==3.0.2 pydantic==2.6.4 pytest -flask_swagger_ui \ No newline at end of file +flask_swagger_ui +gunicorn \ No newline at end of file diff --git a/sample/athletes.json b/sample/athletes.json index bd72eda..9896058 100644 --- a/sample/athletes.json +++ b/sample/athletes.json @@ -1,8 +1,8 @@ [ { "id": 1, - "prenom": "Riner", - "nom": "Teddy", + "prenom": "Teddy", + "nom": "Riner", "pays": "France", "sexe": "H", "image": "https://upload.wikimedia.org/wikipedia/commons/4/4e/Teddy_Riner_2012.jpg", diff --git a/sample/disciplines.json b/sample/disciplines.json new file mode 100644 index 0000000..c9684f1 --- /dev/null +++ b/sample/disciplines.json @@ -0,0 +1,30 @@ +[ + { + "id": 1, + "intitule": "Judo", + "type": "Combat", + "description": "Le judo est un art martial japonais, fondé par Jigoro Kano en 1882. Il se compose pour l'essentiel de techniques de projection, de contrôle au sol, d'étranglements et de clefs.", + "logo": "https://upload.wikimedia.org/wikipedia/commons/4/4b/Judo_pictogram.svg" + }, + { + "id": 2, + "intitule": "Karate", + "type": "Combat", + "description": "Le karaté est un art martial d'origine japonaise, dont le développement s'est essentiellement effectué à Okinawa. Il est principalement constitué de techniques de percussions à mains nues, de coups de pieds, de projections, de balayages et de luxations.", + "logo": "https://upload.wikimedia.org/wikipedia/commons/2/2d/Karate_pictogram.svg" + }, + { + "id": 3, + "intitule": "100m", + "type": "Course", + "description": "Le 100 mètres est une épreuve de sprint en athlétisme. C'est l'épreuve reine des sprinteurs.", + "logo": "https://upload.wikimedia.org/wikipedia/commons/1/1b/Athletics_pictogram.svg", + }, + { + "id": 4, + "intitule": "Natation", + "type": "Nage", + "description": "La natation est un sport consistant à parcourir une certaine distance dans l'eau, en utilisant les bras et les jambes.", + "logo": "https://upload.wikimedia.org/wikipedia/commons/0/0d/Swimming_pictogram.svg", + } +] \ No newline at end of file