diff --git a/athlete/.dockerignore b/athlete/.dockerignore new file mode 100644 index 0000000..1471709 --- /dev/null +++ b/athlete/.dockerignore @@ -0,0 +1,2 @@ +./venv +./__pycache__/ diff --git a/athlete/Dockerfile b/athlete/Dockerfile new file mode 100644 index 0000000..bee527a --- /dev/null +++ b/athlete/Dockerfile @@ -0,0 +1,9 @@ +FROM python:alpine3.19 + +COPY . /app +WORKDIR /app +RUN pip install --no-cache-dir --upgrade -r requirements.txt + +EXPOSE 80 + +CMD ["gunicorn", "--bind", "0.0.0.0:80", "main:app"] diff --git a/athlete/main.py b/athlete/main.py new file mode 100644 index 0000000..56ac64c --- /dev/null +++ b/athlete/main.py @@ -0,0 +1,157 @@ +import json +import os +from flask import Flask, request, jsonify +from pydantic import BaseModel + +app = Flask(__name__) +SAVE_FILE: str = "./data/athlete.json" + + +class Athlete(BaseModel): + current_idx: int = 0 + id: int + name: str + surname: str + email: str + country: str + isDisabled: bool + + def modify_from_key(self, key, value): + if key == "name": + self.name = value + if key == "surname": + self.surname = value + if key == "email": + self.email = value + if key == "country": + self.country = value + if key == "isDisabled": + self.isDisabled = bool(value) + + def serialize(self): + return { + 'id': self.id, + 'name': self.name, + 'surname': self.surname, + 'email': self.email, + 'country': self.country, + 'isDisabled': self.isDisabled, + } + + +def load_athlete_from_json(path: str) -> dict[int, Athlete]: + if not os.path.isfile(path): + Athlete.current_idx = 0 + return {} + with open(path, "r") as file: + content = file.read() + list_test = json.loads(content) + if len(list_test) != 0: + Athlete.current_idx = list_test[-1]['id'] + else: + Athlete.current_idx = 0 + obj: dict[int, Athlete] = {} + for element in list_test: + obj[int(element['id'])] = Athlete( + id=int(element['id']), name=element['name'], + surname=element['surname'], email=element['email'], + country=element['country'], isDisabled=bool(element['isDisabled'])) + return obj + + +def save_athlete_in_json(path: str, athlete_dict: dict[int, Athlete]): + athlete_list = [] + for _, value in athlete_dict.items(): + athlete_list.append(value.serialize()) + with open(path, "w") as file: + file.write(json.dumps(athlete_list)) + + +@ app.route('/') +def root(): + return jsonify({"name": "Jo API", "description": "An API to get information on 2024 olympic games"}), 200 + + +@ app.route('/athlete', methods=['GET', 'POST']) +def handleAthlete(): + if request.method == 'POST': + return handleAthletePOST() + else: + return handleAthleteGET() + + +def handleAthletePOST(): + athlete_list: dict[int, Athlete] = load_athlete_from_json(SAVE_FILE) + try: + post_data = request.get_json() + id = Athlete.current_idx + 1 + name = post_data['name'] + surname = post_data['surname'] + email = post_data['email'] + country = post_data['country'] + isDisabled = bool(post_data['isDisabled']) + athlete = Athlete(id=id, name=name, surname=surname, + email=email, country=country, isDisabled=isDisabled) + athlete_list[athlete.id] = athlete + Athlete.current_idx += 1 + save_athlete_in_json(SAVE_FILE, athlete_list) + except Exception: + return jsonify({"Message": "Bad request"}), 400 + return jsonify({'id': athlete.id}), 200 + + +def handleAthleteGET(): + athlete_list: dict[int, Athlete] = load_athlete_from_json(SAVE_FILE) + ret: list(Athlete) = [] + for _, val in athlete_list.items(): + ret.append(val.serialize()) + return jsonify(ret) + + +@ app.route('/athlete/', methods=['GET', 'PATCH', 'DELETE']) +def handleAthleteId(id: int): + if request.method == 'GET': + return handleAthleteIdGET(id) + elif request.method == 'PATCH': + return handleAthleteIdPATCH(id) + else: + return handleAthleteIdDELETE(id) + + +def handleAthleteIdGET(id: int): + athlete_list: dict[int, Athlete] = load_athlete_from_json(SAVE_FILE) + try: + athlete = athlete_list[int(id)] + return jsonify(athlete.serialize()) + except Exception: + return jsonify({'Message': 'Not found'}), 404 + + +def handleAthleteIdPATCH(id: int): + try: + athlete_list: dict[int, Athlete] = load_athlete_from_json( + SAVE_FILE) + patch_data = request.get_json() + key_list = ['name', 'surname', 'surname', + 'email', 'country', 'isDisabled'] + _ = athlete_list[int(id)] + for key, value in patch_data.items(): + if key in key_list: + athlete_list[int(id)].modify_from_key(key, value) + else: + return jsonify({'Message': 'Bad request'}), 400 + save_athlete_in_json(SAVE_FILE, athlete_list) + return "OK", 200 + except Exception: + return jsonify({'Message': 'Not found'}), 404 + + +def handleAthleteIdDELETE(id: int): + athlete_list: dict[int, Athlete] = load_athlete_from_json(SAVE_FILE) + try: + athlete_list.pop(int(id)) + save_athlete_in_json(SAVE_FILE, athlete_list) + except Exception: + return jsonify({"Message": "Not found"}) + + return "Ok" diff --git a/athlete/requirements.txt b/athlete/requirements.txt new file mode 100644 index 0000000..1ca9aac --- /dev/null +++ b/athlete/requirements.txt @@ -0,0 +1,3 @@ +flask +pydantic +gunicorn diff --git a/athlete/start_debug.sh b/athlete/start_debug.sh new file mode 100755 index 0000000..4ae4d86 --- /dev/null +++ b/athlete/start_debug.sh @@ -0,0 +1,9 @@ +if [ ! -d "./venv" ]; then + python3 -m venv ./venv +fi + +source ./venv/bin/activate + +pip install flask pydantic + +flask --app main run --reload --debug diff --git a/athlete/test.py b/athlete/test.py new file mode 100755 index 0000000..f70a792 --- /dev/null +++ b/athlete/test.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +import requests +import json + +HOST = "http://localhost:5000" + +# Get root +assert 200 == requests.get(HOST + "/").status_code + +# Get list of athletes as a json object +get_root = requests.get(HOST + "/athlete").text +if get_root != "[]\n": + assert json.loads(get_root) + +# Post on '/athlete' With no data +assert 400 == requests.post(HOST + "/athlete").status_code + +# Post on '/athlete' with uncorrect body +assert 400 == requests.post( + HOST + "/athlete", json={"should": "fail"}).status_code + + +# Post on '/athlete' with correct body +post_request = requests.post(HOST + "/athlete", json={"name": "test", "surname": "tests", + "email": "test.test@test.co", "country": "fr", "isDisabled": True}) +assert 200 == post_request.status_code +id = json.loads(post_request.text)['id'] + +# Get newly created object +expected_result = f"""{{ + "country": "fr", + "email": "test.test@test.co", + "id": {id}, + "isDisabled": true, + "name": "test", + "surname": "tests" +}} +""" +assert expected_result == requests.get(HOST + f"/athlete/{id}").text + +assert 400 == requests.patch( + HOST + f"/athlete/{id}", json={"should": "fail"}).status_code + +# Test to patch object +assert 200 == requests.patch( + HOST + f"/athlete/{id}", json={"name": "modify"}).status_code + +expected_result = f"""{{ + "country": "fr", + "email": "test.test@test.co", + "id": {id}, + "isDisabled": true, + "name": "modify", + "surname": "tests" +}} +""" +assert expected_result == requests.get(HOST + f"/athlete/{id}").text + +# test delete methode on the newly created athlete + +assert 200 == requests.delete(HOST + f"/athlete/{id}").status_code + +# verify that the object don't exist anymore +assert 404 == requests.get(HOST + f"/athlete/{id}").status_code