Skip to content

Commit f0efad4

Browse files
authored
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
1 parent 262c29a commit f0efad4

File tree

3 files changed

+215
-2
lines changed

3 files changed

+215
-2
lines changed

app.py

+122-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44

55
from flask import Flask, jsonify, request
6-
from models import Experience, Education, Skill, User
6+
from models import Experience, Education, Project, Skill, User
77
from utils import get_suggestion, check_phone_number, correct_spelling
88

99

@@ -39,7 +39,19 @@
3939
"example-logo.png",
4040
)
4141
],
42-
"skill": [Skill("Python", "1-2 Years", "example-logo.png")],
42+
"skill": [
43+
Skill("Python",
44+
"1-2 Years",
45+
"example-logo.png")
46+
],
47+
"project": [
48+
Project(
49+
title="Sample Project",
50+
description="A sample project",
51+
technologies=["Python", "Flask"],
52+
link="https://github.com/username/sample-project"
53+
)
54+
]
4355
}
4456

4557

@@ -168,6 +180,114 @@ def skill():
168180
return jsonify({})
169181

170182

183+
@app.route('/resume/project', methods=['GET', 'POST', 'PUT', 'DELETE'])
184+
def project():
185+
'''
186+
Handles Project requests
187+
'''
188+
def validate_id(project_id):
189+
'''
190+
Validates the id
191+
'''
192+
if project_id is None:
193+
raise ValueError("Missing id")
194+
195+
if not project_id.isdigit():
196+
raise ValueError("Invalid id")
197+
198+
# check if the id is within the range of the project list
199+
int_id = int(project_id)
200+
if int_id < 0 or int_id >= len(data['project']):
201+
raise ValueError("Project not found")
202+
203+
return int_id
204+
205+
def get_project(project_id):
206+
'''
207+
Get project by id
208+
'''
209+
if project_id is not None:
210+
try:
211+
project_id = validate_id(project_id)
212+
return jsonify(data['project'][project_id]), 200
213+
except ValueError as error:
214+
return jsonify({"error": str(error)}), 400
215+
216+
return jsonify([
217+
{**project.__dict__, "id": str(index)}
218+
for index, project in enumerate(data['project'])
219+
]), 200
220+
221+
def add_project(body):
222+
'''
223+
Add project
224+
'''
225+
mandatory_fields = ['title', 'description', 'technologies', 'link']
226+
missing_fields = [field for field in mandatory_fields if field not in body]
227+
228+
if missing_fields:
229+
return jsonify({"error": f"Missing fields: {', '.join(missing_fields)}"}), 400
230+
231+
new_project = Project(
232+
body['title'],
233+
body['description'],
234+
body['technologies'],
235+
body['link']
236+
)
237+
data['project'].append(new_project)
238+
239+
return jsonify({**new_project.__dict__, "id": str(len(data['project']) - 1)}), 201
240+
241+
def edit_project(project_id, body):
242+
'''
243+
Edit project
244+
'''
245+
try:
246+
project_id = validate_id(project_id)
247+
except ValueError as error:
248+
return jsonify({"error": str(error)}), 400
249+
250+
for key, value in body.items():
251+
if hasattr(data['project'][project_id], key):
252+
setattr(data['project'][project_id], key, value)
253+
else:
254+
return jsonify({"error": f"invalid field: {key}"}), 400
255+
256+
return jsonify({**data['project'][project_id].__dict__, "id": str(project_id)}), 200
257+
258+
def delete_project(project_id):
259+
'''
260+
Delete project
261+
'''
262+
try:
263+
project_id = validate_id(project_id)
264+
except ValueError as error:
265+
return jsonify({"error": str(error)}), 400
266+
267+
del data['project'][project_id]
268+
return jsonify({}), 204
269+
270+
if request.method == 'GET':
271+
project_id = request.args.get('id', None)
272+
return get_project(project_id)
273+
274+
if request.method == 'POST':
275+
body = request.get_json()
276+
return add_project(body)
277+
278+
if request.method == 'PUT':
279+
project_id = request.args.get('id', None)
280+
body = request.get_json()
281+
282+
return edit_project(project_id, body)
283+
284+
if request.method == 'DELETE':
285+
project_id = request.args.get('id', None)
286+
287+
return delete_project(project_id)
288+
289+
return jsonify({"error": "Unsupported request method"}), 405
290+
171291
@app.route("/resume/spellcheck", methods=["POST"])
172292
def spellcheck():
173293
"""

models.py

+11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
'''
66

77
from dataclasses import dataclass
8+
from typing import List
89

910
@dataclass
1011
class User:
@@ -49,3 +50,13 @@ class Skill:
4950
name: str
5051
proficiency: str
5152
logo: str
53+
54+
@dataclass
55+
class Project:
56+
'''
57+
Project Class
58+
'''
59+
title: str
60+
description: str
61+
technologies: List[str]
62+
link: str

test_pytest.py

+82
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,88 @@ def test_skill():
116116
assert response.json["skills"][item_id] == example_skill
117117

118118

119+
def test_get_project():
120+
'''
121+
Test the get_project function
122+
123+
Check that it returns a list of projects
124+
'''
125+
response = app.test_client().get('/resume/project')
126+
assert response.status_code == 200
127+
assert isinstance(response.json, list)
128+
129+
def test_add_project():
130+
'''
131+
Test the add_project function
132+
133+
Check that it returns the new project
134+
Check that it returns an error when missing fields
135+
'''
136+
new_project = {
137+
'title': 'Sample Project',
138+
'description': 'A sample project',
139+
'technologies': ['Python', 'Flask'],
140+
'link': 'https://github.com/username/sample-project'
141+
}
142+
response = app.test_client().post('/resume/project', json=new_project)
143+
assert response.status_code == 201
144+
assert response.json == {**new_project, 'id': '1'}
145+
146+
new_project.pop('title')
147+
response = app.test_client().post('/resume/project', json=new_project)
148+
assert response.status_code == 400
149+
assert response.json == {'error': 'Missing fields: title'}
150+
151+
def test_edit_project():
152+
'''
153+
Test the edit_project function
154+
155+
Check that it returns the updated project
156+
Check that it returns an error when the project id is invalid
157+
'''
158+
new_project = {
159+
'title': 'Sample Project',
160+
'description': 'A sample project',
161+
'technologies': ['Python', 'Flask'],
162+
'link': 'https://github.com/username/sample-project'
163+
}
164+
new_project_id = app.test_client().post('/resume/project', json=new_project).json['id']
165+
new_project['title'] = 'New Project'
166+
new_project['description'] = 'A new project'
167+
new_project['technologies'] = ['Python', 'Flask', 'Docker']
168+
169+
response = app.test_client().\
170+
put('/resume/project', json=new_project, query_string={'id': new_project_id})
171+
172+
assert response.status_code == 200
173+
assert response.json == {**new_project, 'id': new_project_id}
174+
175+
response = app.test_client().\
176+
put('/resume/project', json=new_project, query_string={'id': 'invalid-id'})
177+
assert response.status_code == 400
178+
assert response.json == {'error': 'Invalid id'}
179+
180+
def test_delete_project():
181+
'''
182+
Test the delete_project function
183+
184+
Check that it returns a 204 status code
185+
Check that it returns an error when the project id is invalid
186+
'''
187+
new_project = {
188+
'title': 'Sample Project',
189+
'description': 'A sample project',
190+
'technologies': ['Python', 'Flask'],
191+
'link': 'https://github.com/username/sample-project'
192+
}
193+
new_project_id = app.test_client().post('/resume/project', json=new_project).json['id']
194+
response = app.test_client().delete('/resume/project', query_string={'id': new_project_id})
195+
assert response.status_code == 204
196+
197+
response = app.test_client().delete('/resume/project', query_string={'id': 'invalid-id'})
198+
assert response.status_code == 400
199+
assert response.json == {'error': 'Invalid id'}
200+
119201
@pytest.mark.parametrize('text, expected', [
120202
('thiss is an exmple of spell chcking.',
121203
'this is an example of spell checking.'),

0 commit comments

Comments
 (0)