duplicate apis
This commit is contained in:
parent
b56019c694
commit
935b404338
60 changed files with 1065 additions and 80 deletions
54
PROJET.md
54
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
|
||||
```
|
|
@ -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"]
|
||||
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"]
|
||||
|
|
BIN
athlete/__pycache__/app.cpython-311.pyc
Normal file
BIN
athlete/__pycache__/app.cpython-311.pyc
Normal file
Binary file not shown.
BIN
athlete/__pycache__/athlete.cpython-311.pyc
Normal file
BIN
athlete/__pycache__/athlete.cpython-311.pyc
Normal file
Binary file not shown.
BIN
athlete/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc
Normal file
BIN
athlete/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc
Normal file
Binary file not shown.
119
athlete/app.py
119
athlete/app.py
|
@ -1,22 +1,115 @@
|
|||
from flask import Flask, jsonify
|
||||
import athlete
|
||||
from flask import Flask, jsonify, request
|
||||
from pathlib import Path
|
||||
import json
|
||||
from athlete import ListeAthlete, Athlete
|
||||
# noinspection PyUnresolvedReferences
|
||||
from flask_swagger_ui import get_swaggerui_blueprint
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
app.config['ATHLETE_FILE'] = os.getenv('ATHLETE_FILE', Path(__file__).parent.parent / 'data' / 'athletes.json')
|
||||
@app.route('/ping', methods=["GET"])
|
||||
def ping():
|
||||
return jsonify({"message": "pong"}), 200
|
||||
@app.route('/', methods=["GET"])
|
||||
def hello_world():
|
||||
return jsonify(athlete.Athlete(
|
||||
id=1,
|
||||
prenom="john",
|
||||
nom="doe",
|
||||
pays="France",
|
||||
sexe="Homme",
|
||||
image="localhost",
|
||||
disciplines=[123],
|
||||
).model_dump())
|
||||
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['ATHLETE_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('/<int:id>', 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('/<int:id>', 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('/<int:id>', 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('/<int:id>', 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
|
||||
|
|
|
@ -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))
|
|
@ -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()
|
|
@ -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.
|
||||
|
|
BIN
athlete/tests/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc
Normal file
BIN
athlete/tests/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
athlete/tests/__pycache__/test_ping.cpython-311-pytest-8.1.1.pyc
Normal file
BIN
athlete/tests/__pycache__/test_ping.cpython-311-pytest-8.1.1.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,38 +0,0 @@
|
|||
[
|
||||
{
|
||||
"id": 1,
|
||||
"prenom": "Riner",
|
||||
"nom": "Teddy",
|
||||
"pays": "France",
|
||||
"sexe": "H",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/4/4e/Teddy_Riner_2012.jpg",
|
||||
"disciplines": [
|
||||
1
|
||||
],
|
||||
"records": []
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"prenom": "Flessel",
|
||||
"nom": "Laura",
|
||||
"pays": "France",
|
||||
"sexe": "H",
|
||||
"image": "https://plusquedusport.files.wordpress.com/2012/04/124936207sl015_laura_flesse.jpg",
|
||||
"disciplines": [
|
||||
2
|
||||
],
|
||||
"records": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"prenom": "Monfils",
|
||||
"nom": "Ga\u00ebl",
|
||||
"pays": "France",
|
||||
"sexe": "H",
|
||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/26/Monfils_RG19_%2813%29_%2848199149362%29.jpg/1200px-Monfils_RG19_%2813%29_%2848199149362%29.jpg",
|
||||
"disciplines": [
|
||||
3
|
||||
],
|
||||
"records": []
|
||||
}
|
||||
]
|
6
athlete/tests/test_delAthlete.py
Normal file
6
athlete/tests/test_delAthlete.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
def test_delAthlete(client):
|
||||
response = client.delete("/1")
|
||||
assert response.status_code == 200
|
||||
|
||||
response = client.get("/1")
|
||||
assert response.status_code == 404
|
|
@ -1,8 +1,17 @@
|
|||
import unittest
|
||||
from athlete import Athlete
|
||||
def test_getAthlete(client):
|
||||
response = client.get("/1")
|
||||
# Check if can be mapped to Athlete RootModel object
|
||||
|
||||
class MyTestCase(unittest.TestCase):
|
||||
def test_something(self):
|
||||
self.assertEqual(True, False) # add assertion here
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
athlete = Athlete(
|
||||
id=1,
|
||||
prenom="Teddy",
|
||||
nom="Riner",
|
||||
pays="France",
|
||||
sexe="H",
|
||||
image="https://upload.wikimedia.org/wikipedia/commons/4/4e/Teddy_Riner_2012.jpg",
|
||||
disciplines=[1],
|
||||
records=[],
|
||||
)
|
||||
assert athlete.model_dump() == response.json
|
||||
assert response.status_code == 200
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
from athlete import ListeAthlete
|
||||
def test_listeAthlete(client):
|
||||
response = client.get("/")
|
||||
listeAthlete = ListeAthlete()
|
||||
listeAthlete.loadFromJsonData(response.data)
|
||||
assert listeAthlete.root is not None
|
||||
assert len(listeAthlete.root) > 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 != []
|
|
@ -1,5 +0,0 @@
|
|||
import pytest
|
||||
|
||||
def test_ping(client):
|
||||
response = client.get("/ping")
|
||||
assert b"{\"message\":\"pong\"}\n" in response.data
|
3
athlete/tests/test_ping_athlete.py
Normal file
3
athlete/tests/test_ping_athlete.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
def test_ping_athlete(client):
|
||||
response = client.get("/ping")
|
||||
assert b"{\"message\":\"pong\"}\n" in response.data
|
19
athlete/tests/test_postAthlete.py
Normal file
19
athlete/tests/test_postAthlete.py
Normal file
|
@ -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()
|
19
discipline/Dockerfile
Normal file
19
discipline/Dockerfile
Normal file
|
@ -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"]
|
BIN
discipline/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc
Normal file
BIN
discipline/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc
Normal file
Binary file not shown.
BIN
discipline/__pycache__/discipline.cpython-311.pyc
Normal file
BIN
discipline/__pycache__/discipline.cpython-311.pyc
Normal file
Binary file not shown.
121
discipline/app.py
Normal file
121
discipline/app.py
Normal file
|
@ -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('/<int:id>', 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('/<int:id>', 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('/<int:id>', 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('/<int:id>', 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()
|
26
discipline/conftest.py
Normal file
26
discipline/conftest.py
Normal file
|
@ -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()
|
50
discipline/discipline.py
Normal file
50
discipline/discipline.py
Normal file
|
@ -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))
|
133
discipline/static/swagger.yaml
Normal file
133
discipline/static/swagger.yaml
Normal file
|
@ -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'
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
6
discipline/tests/test_delDiscipline.py
Normal file
6
discipline/tests/test_delDiscipline.py
Normal file
|
@ -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
|
13
discipline/tests/test_getDiscipline.py
Normal file
13
discipline/tests/test_getDiscipline.py
Normal file
|
@ -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
|
9
discipline/tests/test_listeDiscipline.py
Normal file
9
discipline/tests/test_listeDiscipline.py
Normal file
|
@ -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
|
3
discipline/tests/test_ping_discipline.py
Normal file
3
discipline/tests/test_ping_discipline.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
def test_ping_discipline(client):
|
||||
response = client.get("/ping")
|
||||
assert b"{\"message\":\"pong\"}\n" in response.data
|
17
discipline/tests/test_postDiscipline.py
Normal file
17
discipline/tests/test_postDiscipline.py
Normal file
|
@ -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()
|
|
@ -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"
|
19
medaille/Dockerfile
Normal file
19
medaille/Dockerfile
Normal file
|
@ -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"]
|
BIN
medaille/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc
Normal file
BIN
medaille/__pycache__/conftest.cpython-311-pytest-8.1.1.pyc
Normal file
Binary file not shown.
117
medaille/app.py
Normal file
117
medaille/app.py
Normal file
|
@ -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('/<int:id>', 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('/<int:id>', 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('/<int:id>', 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('/<int:id>', 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()
|
26
medaille/conftest.py
Normal file
26
medaille/conftest.py
Normal file
|
@ -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()
|
136
medaille/static/swagger.yaml
Normal file
136
medaille/static/swagger.yaml
Normal file
|
@ -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'
|
|
@ -1,4 +1,5 @@
|
|||
flask==3.0.2
|
||||
pydantic==2.6.4
|
||||
pytest
|
||||
flask_swagger_ui
|
||||
flask_swagger_ui
|
||||
gunicorn
|
|
@ -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",
|
||||
|
|
30
sample/disciplines.json
Normal file
30
sample/disciplines.json
Normal file
|
@ -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",
|
||||
}
|
||||
]
|
Loading…
Reference in a new issue