Skip to content

Commit bcc0782

Browse files
committed
Updated enumeration code
1 parent 1512884 commit bcc0782

File tree

4 files changed

+189
-75
lines changed

4 files changed

+189
-75
lines changed

service/models.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2016, 2021 John Rofrano. All Rights Reserved.
1+
# Copyright 2016, 2024 John Rofrano. All Rights Reserved.
22
#
33
# Licensed under the Apache License, Version 2.0 (the 'License');
44
# you may not use this file except in compliance with the License.
@@ -26,11 +26,13 @@
2626
name (string) - the name of the pet
2727
category (string) - the category the pet belongs to (i.e., dog, cat)
2828
available (boolean) - True for pets that are available for adoption
29+
gender (enum) - the gender of the pet
30+
birthday (date) - the day the pet was born
2931
3032
"""
3133
import logging
32-
from enum import Enum
3334
from datetime import date
35+
from enum import Enum
3436
from flask_sqlalchemy import SQLAlchemy
3537

3638
logger = logging.getLogger("flask.app")
@@ -70,6 +72,9 @@ class Pet(db.Model):
7072
db.Enum(Gender), nullable=False, server_default=(Gender.UNKNOWN.name)
7173
)
7274
birthday = db.Column(db.Date(), nullable=False, default=date.today())
75+
# Database auditing fields
76+
created_at = db.Column(db.DateTime, default=db.func.now(), nullable=False)
77+
last_updated = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now(), nullable=False)
7378

7479
##################################################
7580
# INSTANCE METHODS
@@ -78,9 +83,9 @@ class Pet(db.Model):
7883
def __repr__(self):
7984
return f"<Pet {self.name} id=[{self.id}]>"
8085

81-
def create(self):
86+
def create(self) -> None:
8287
"""
83-
Creates a Pet to the database
88+
Saves a Pet to the database
8489
"""
8590
logger.info("Creating %s", self.name)
8691
# id must be none to generate next primary key
@@ -93,7 +98,7 @@ def create(self):
9398
logger.error("Error creating record: %s", self)
9499
raise DataValidationError(e) from e
95100

96-
def update(self):
101+
def update(self) -> None:
97102
"""
98103
Updates a Pet to the database
99104
"""
@@ -107,8 +112,10 @@ def update(self):
107112
logger.error("Error updating record: %s", self)
108113
raise DataValidationError(e) from e
109114

110-
def delete(self):
111-
"""Removes a Pet from the data store"""
115+
def delete(self) -> None:
116+
"""
117+
Removes a Pet from the database
118+
"""
112119
logger.info("Deleting %s", self.name)
113120
try:
114121
db.session.delete(self)
@@ -126,7 +133,7 @@ def serialize(self) -> dict:
126133
"category": self.category,
127134
"available": self.available,
128135
"gender": self.gender.name, # convert enum to string
129-
"birthday": self.birthday.isoformat(),
136+
"birthday": self.birthday.isoformat()
130137
}
131138

132139
def deserialize(self, data: dict):
@@ -145,14 +152,13 @@ def deserialize(self, data: dict):
145152
"Invalid type for boolean [available]: "
146153
+ str(type(data["available"]))
147154
)
148-
self.gender = getattr(Gender, data["gender"]) # create enum from string
155+
# self.gender = getattr(Gender, data["gender"]) # create enum from string
156+
self.gender = Gender[data["gender"].upper()] # create enum from string
149157
self.birthday = date.fromisoformat(data["birthday"])
150158
except AttributeError as error:
151159
raise DataValidationError("Invalid attribute: " + error.args[0]) from error
152160
except KeyError as error:
153-
raise DataValidationError(
154-
"Invalid pet: missing " + error.args[0]
155-
) from error
161+
raise DataValidationError("Invalid pet: missing " + error.args[0]) from error
156162
except TypeError as error:
157163
raise DataValidationError(
158164
"Invalid pet: body of request contained bad or no data " + str(error)
@@ -222,6 +228,8 @@ def find_by_availability(cls, available: bool = True) -> list:
222228
:rtype: list
223229
224230
"""
231+
if not isinstance(available, bool):
232+
raise TypeError("Invalid availability, must be of type boolean")
225233
logger.info("Processing available query for %s ...", available)
226234
return cls.query.filter(cls.available == available)
227235

@@ -236,5 +244,7 @@ def find_by_gender(cls, gender: Gender = Gender.UNKNOWN) -> list:
236244
:rtype: list
237245
238246
"""
247+
if not isinstance(gender, Gender):
248+
raise TypeError("Invalid gender, must be type Gender")
239249
logger.info("Processing gender query for %s ...", gender.name)
240250
return cls.query.filter(cls.gender == gender)

service/routes.py

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
from flask import jsonify, request, url_for, abort
2323
from flask import current_app as app # Import Flask application
24-
from service.models import Pet
24+
from service.models import Pet, Gender
2525
from service.common import status # HTTP Status Codes
2626

2727

@@ -61,14 +61,29 @@ def list_pets():
6161

6262
pets = []
6363

64-
# See if any query filters were passed in
64+
# Parse any arguments from the query string
6565
category = request.args.get("category")
6666
name = request.args.get("name")
67+
available = request.args.get("available")
68+
gender = request.args.get("gender")
69+
6770
if category:
71+
app.logger.info("Find by category: %s", category)
6872
pets = Pet.find_by_category(category)
6973
elif name:
74+
app.logger.info("Find by name: %s", name)
7075
pets = Pet.find_by_name(name)
76+
elif available:
77+
app.logger.info("Find by available: %s", available)
78+
# create bool from string
79+
available_value = available.lower() in ["true", "yes", "1"]
80+
pets = Pet.find_by_availability(available_value)
81+
elif gender:
82+
app.logger.info("Find by gender: %s", gender)
83+
# create enum from string
84+
pets = Pet.find_by_gender(Gender[gender.upper()])
7185
else:
86+
app.logger.info("Find all")
7287
pets = Pet.all()
7388

7489
results = [pet.serialize() for pet in pets]
@@ -86,11 +101,12 @@ def get_pets(pet_id):
86101
87102
This endpoint will return a Pet based on it's id
88103
"""
89-
app.logger.info("Request for pet with id: %s", pet_id)
104+
app.logger.info("Request to Retrieve a pet with id [%s]", pet_id)
90105

106+
# Attempt to find the Pet and abort if not found
91107
pet = Pet.find(pet_id)
92108
if not pet:
93-
error(status.HTTP_404_NOT_FOUND, f"Pet with id '{pet_id}' was not found.")
109+
abort(status.HTTP_404_NOT_FOUND, f"Pet with id '{pet_id}' was not found.")
94110

95111
app.logger.info("Returning pet: %s", pet.name)
96112
return jsonify(pet.serialize()), status.HTTP_200_OK
@@ -102,21 +118,25 @@ def get_pets(pet_id):
102118
@app.route("/pets", methods=["POST"])
103119
def create_pets():
104120
"""
105-
Creates a Pet
106-
121+
Create a Pet
107122
This endpoint will create a Pet based the data in the body that is posted
108123
"""
109-
app.logger.info("Request to create a pet")
124+
app.logger.info("Request to Create a Pet...")
110125
check_content_type("application/json")
111126

112127
pet = Pet()
113-
pet.deserialize(request.get_json())
128+
# Get the data from the request and deserialize it
129+
data = request.get_json()
130+
app.logger.info("Processing: %s", data)
131+
pet.deserialize(data)
132+
133+
# Save the new Pet to the database
114134
pet.create()
115-
message = pet.serialize()
116-
location_url = url_for("get_pets", pet_id=pet.id, _external=True)
135+
app.logger.info("Pet with new id [%s] saved!", pet.id)
117136

118-
app.logger.info("Pet with ID: %d created.", pet.id)
119-
return jsonify(message), status.HTTP_201_CREATED, {"Location": location_url}
137+
# Return the location of the new Pet
138+
location_url = url_for("get_pets", pet_id=pet.id, _external=True)
139+
return jsonify(pet.serialize()), status.HTTP_201_CREATED, {"Location": location_url}
120140

121141

122142
######################################################################
@@ -129,15 +149,20 @@ def update_pets(pet_id):
129149
130150
This endpoint will update a Pet based the body that is posted
131151
"""
132-
app.logger.info("Request to update pet with id: %d", pet_id)
152+
app.logger.info("Request to Update a pet with id [%s]", pet_id)
133153
check_content_type("application/json")
134154

155+
# Attempt to find the Pet and abort if not found
135156
pet = Pet.find(pet_id)
136157
if not pet:
137-
error(status.HTTP_404_NOT_FOUND, f"Pet with id: '{pet_id}' was not found.")
158+
abort(status.HTTP_404_NOT_FOUND, f"Pet with id '{pet_id}' was not found.")
159+
160+
# Update the Pet with the new data
161+
data = request.get_json()
162+
app.logger.info("Processing: %s", data)
163+
pet.deserialize(data)
138164

139-
pet.deserialize(request.get_json())
140-
pet.id = pet_id
165+
# Save the updates to the database
141166
pet.update()
142167

143168
app.logger.info("Pet with ID: %d updated.", pet.id)
@@ -154,37 +179,36 @@ def delete_pets(pet_id):
154179
155180
This endpoint will delete a Pet based the id specified in the path
156181
"""
157-
app.logger.info("Request to delete pet with id: %d", pet_id)
182+
app.logger.info("Request to Delete a pet with id [%s]", pet_id)
158183

184+
# Delete the Pet if it exists
159185
pet = Pet.find(pet_id)
160186
if pet:
187+
app.logger.info("Pet with ID: %d found.", pet.id)
161188
pet.delete()
162189

163190
app.logger.info("Pet with ID: %d delete complete.", pet_id)
164-
return "", status.HTTP_204_NO_CONTENT
191+
return {}, status.HTTP_204_NO_CONTENT
165192

166193

167194
######################################################################
168195
# PURCHASE A PET
169196
######################################################################
170197
@app.route("/pets/<int:pet_id>/purchase", methods=["PUT"])
171198
def purchase_pets(pet_id):
172-
"""
173-
Purchasing a Pet
174-
175-
This endpoint will purchase a pet makes it unavailable
176-
"""
199+
"""Purchasing a Pet makes it unavailable"""
177200
app.logger.info("Request to purchase pet with id: %d", pet_id)
178201

202+
# Attempt to find the Pet and abort if not found
179203
pet = Pet.find(pet_id)
180204
if not pet:
181-
error(status.HTTP_404_NOT_FOUND, f"Pet with id '{pet_id}' was not found.")
205+
abort(status.HTTP_404_NOT_FOUND, f"Pet with id '{pet_id}' was not found.")
182206

183207
# you can only purchase pets that are available
184208
if not pet.available:
185-
error(
209+
abort(
186210
status.HTTP_409_CONFLICT,
187-
f"Pet with ID: '{pet_id}' is not available.",
211+
f"Pet with id '{pet_id}' is not available.",
188212
)
189213

190214
# At this point you would execute code to purchase the pet
@@ -205,11 +229,11 @@ def purchase_pets(pet_id):
205229
######################################################################
206230
# Checks the ContentType of a request
207231
######################################################################
208-
def check_content_type(content_type):
232+
def check_content_type(content_type) -> None:
209233
"""Checks that the media type is correct"""
210234
if "Content-Type" not in request.headers:
211235
app.logger.error("No Content-Type specified.")
212-
error(
236+
abort(
213237
status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
214238
f"Content-Type must be {content_type}",
215239
)
@@ -218,16 +242,7 @@ def check_content_type(content_type):
218242
return
219243

220244
app.logger.error("Invalid Content-Type: %s", request.headers["Content-Type"])
221-
error(
245+
abort(
222246
status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
223247
f"Content-Type must be {content_type}",
224248
)
225-
226-
227-
######################################################################
228-
# Logs error messages before aborting
229-
######################################################################
230-
def error(status_code, reason):
231-
"""Logs the error and then aborts"""
232-
app.logger.error(reason)
233-
abort(status_code, reason)

tests/test_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def test_deserialize_bad_gender(self):
214214
"""It should not deserialize a bad gender attribute"""
215215
test_pet = PetFactory()
216216
data = test_pet.serialize()
217-
data["gender"] = "male" # wrong case
217+
data["gender"] = "XXX" # invalid gender
218218
pet = Pet()
219219
self.assertRaises(DataValidationError, pet.deserialize, data)
220220

0 commit comments

Comments
 (0)