From f0efad42261ea7172b5f764731f57cdfc9c4901a Mon Sep 17 00:00:00 2001 From: Bedru Umer <63902795+bedre7@users.noreply.github.com> Date: Fri, 11 Oct 2024 21:05:36 +0300 Subject: [PATCH] Feature: Implement API route for "Project" section (#40) * add Project dataclass to models * implement API route for 'Project' section * test 'Project' API route * fix linting error: app.py:184:0: R0911: Too many return statements --- app.py | 124 ++++++++++++++++++++++++++++++++++++++++++++++++- models.py | 11 +++++ test_pytest.py | 82 ++++++++++++++++++++++++++++++++ 3 files changed, 215 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index 35f1ae9..8b76079 100644 --- a/app.py +++ b/app.py @@ -3,7 +3,7 @@ """ from flask import Flask, jsonify, request -from models import Experience, Education, Skill, User +from models import Experience, Education, Project, Skill, User from utils import get_suggestion, check_phone_number, correct_spelling @@ -39,7 +39,19 @@ "example-logo.png", ) ], - "skill": [Skill("Python", "1-2 Years", "example-logo.png")], + "skill": [ + Skill("Python", + "1-2 Years", + "example-logo.png") + ], + "project": [ + Project( + title="Sample Project", + description="A sample project", + technologies=["Python", "Flask"], + link="https://github.com/username/sample-project" + ) + ] } @@ -168,6 +180,114 @@ def skill(): return jsonify({}) +@app.route('/resume/project', methods=['GET', 'POST', 'PUT', 'DELETE']) +def project(): + ''' + Handles Project requests + ''' + def validate_id(project_id): + ''' + Validates the id + ''' + if project_id is None: + raise ValueError("Missing id") + + if not project_id.isdigit(): + raise ValueError("Invalid id") + + # check if the id is within the range of the project list + int_id = int(project_id) + if int_id < 0 or int_id >= len(data['project']): + raise ValueError("Project not found") + + return int_id + + def get_project(project_id): + ''' + Get project by id + ''' + if project_id is not None: + try: + project_id = validate_id(project_id) + return jsonify(data['project'][project_id]), 200 + except ValueError as error: + return jsonify({"error": str(error)}), 400 + + return jsonify([ + {**project.__dict__, "id": str(index)} + for index, project in enumerate(data['project']) + ]), 200 + + def add_project(body): + ''' + Add project + ''' + mandatory_fields = ['title', 'description', 'technologies', 'link'] + missing_fields = [field for field in mandatory_fields if field not in body] + + if missing_fields: + return jsonify({"error": f"Missing fields: {', '.join(missing_fields)}"}), 400 + + new_project = Project( + body['title'], + body['description'], + body['technologies'], + body['link'] + ) + data['project'].append(new_project) + + return jsonify({**new_project.__dict__, "id": str(len(data['project']) - 1)}), 201 + + def edit_project(project_id, body): + ''' + Edit project + ''' + try: + project_id = validate_id(project_id) + except ValueError as error: + return jsonify({"error": str(error)}), 400 + + for key, value in body.items(): + if hasattr(data['project'][project_id], key): + setattr(data['project'][project_id], key, value) + else: + return jsonify({"error": f"invalid field: {key}"}), 400 + + return jsonify({**data['project'][project_id].__dict__, "id": str(project_id)}), 200 + + def delete_project(project_id): + ''' + Delete project + ''' + try: + project_id = validate_id(project_id) + except ValueError as error: + return jsonify({"error": str(error)}), 400 + + del data['project'][project_id] + return jsonify({}), 204 + + if request.method == 'GET': + project_id = request.args.get('id', None) + return get_project(project_id) + + if request.method == 'POST': + body = request.get_json() + return add_project(body) + + if request.method == 'PUT': + project_id = request.args.get('id', None) + body = request.get_json() + + return edit_project(project_id, body) + + if request.method == 'DELETE': + project_id = request.args.get('id', None) + + return delete_project(project_id) + + return jsonify({"error": "Unsupported request method"}), 405 + @app.route("/resume/spellcheck", methods=["POST"]) def spellcheck(): """ diff --git a/models.py b/models.py index f96bd04..01c9836 100644 --- a/models.py +++ b/models.py @@ -5,6 +5,7 @@ ''' from dataclasses import dataclass +from typing import List @dataclass class User: @@ -49,3 +50,13 @@ class Skill: name: str proficiency: str logo: str + +@dataclass +class Project: + ''' + Project Class + ''' + title: str + description: str + technologies: List[str] + link: str diff --git a/test_pytest.py b/test_pytest.py index 8dd6717..399d0a7 100644 --- a/test_pytest.py +++ b/test_pytest.py @@ -116,6 +116,88 @@ def test_skill(): assert response.json["skills"][item_id] == example_skill +def test_get_project(): + ''' + Test the get_project function + + Check that it returns a list of projects + ''' + response = app.test_client().get('/resume/project') + assert response.status_code == 200 + assert isinstance(response.json, list) + +def test_add_project(): + ''' + Test the add_project function + + Check that it returns the new project + Check that it returns an error when missing fields + ''' + new_project = { + 'title': 'Sample Project', + 'description': 'A sample project', + 'technologies': ['Python', 'Flask'], + 'link': 'https://github.com/username/sample-project' + } + response = app.test_client().post('/resume/project', json=new_project) + assert response.status_code == 201 + assert response.json == {**new_project, 'id': '1'} + + new_project.pop('title') + response = app.test_client().post('/resume/project', json=new_project) + assert response.status_code == 400 + assert response.json == {'error': 'Missing fields: title'} + +def test_edit_project(): + ''' + Test the edit_project function + + Check that it returns the updated project + Check that it returns an error when the project id is invalid + ''' + new_project = { + 'title': 'Sample Project', + 'description': 'A sample project', + 'technologies': ['Python', 'Flask'], + 'link': 'https://github.com/username/sample-project' + } + new_project_id = app.test_client().post('/resume/project', json=new_project).json['id'] + new_project['title'] = 'New Project' + new_project['description'] = 'A new project' + new_project['technologies'] = ['Python', 'Flask', 'Docker'] + + response = app.test_client().\ + put('/resume/project', json=new_project, query_string={'id': new_project_id}) + + assert response.status_code == 200 + assert response.json == {**new_project, 'id': new_project_id} + + response = app.test_client().\ + put('/resume/project', json=new_project, query_string={'id': 'invalid-id'}) + assert response.status_code == 400 + assert response.json == {'error': 'Invalid id'} + +def test_delete_project(): + ''' + Test the delete_project function + + Check that it returns a 204 status code + Check that it returns an error when the project id is invalid + ''' + new_project = { + 'title': 'Sample Project', + 'description': 'A sample project', + 'technologies': ['Python', 'Flask'], + 'link': 'https://github.com/username/sample-project' + } + new_project_id = app.test_client().post('/resume/project', json=new_project).json['id'] + response = app.test_client().delete('/resume/project', query_string={'id': new_project_id}) + assert response.status_code == 204 + + response = app.test_client().delete('/resume/project', query_string={'id': 'invalid-id'}) + assert response.status_code == 400 + assert response.json == {'error': 'Invalid id'} + @pytest.mark.parametrize('text, expected', [ ('thiss is an exmple of spell chcking.', 'this is an example of spell checking.'),