At the end of this project, you are expected to be able to explain to anyone, without the help of Google:
- What REST means
- What API means
- What CORS means
- What is an API
- What is a REST API
- What are other type of APIs
- Which is the HTTP method to retrieve resource(s)
- Which is the HTTP method to create a resource
- Which is the HTTP method to update resource
- Which is the HTTP method to delete resource
- How to request REST API
- Allowed editors:
vi
,vim
,emacs
- All your files will be interpreted/compiled on Ubuntu 14.04 LTS using
python3
(version 3.4.3) - All your files should end with a new line
- The first line of all your files should be exactly
#!/usr/bin/python3
- A
README.md
file, at the root of the folder of the project, is mandatory - Your code should use the
PEP 8
style (version 1.7) - All your files must be executable
- The length of your files will be tested using
wc
- All your modules should have documentation
(python3 -c 'print(__import__("my_module").__doc__)'
) - All your classes should have documentation
(python3 -c 'print(__import__("my_module").MyClass.__doc__)'
) - All your functions (inside and outside a class) should have documentation
(python3 -c 'print(__import__("my_module").my_function.__doc__)'
andpython3 -c 'print(__import__("my_module").MyClass.my_function.__doc__)'
) - A documentation is not a simple word, it’s a real sentence explaining what’s the purpose of the module, class or method (the length of it will be verified)
- Allowed editors:
vi
,vim
,emacs
- All your files should end with a new line
- All your test files should be inside a folder
tests
- You have to use the unittest module
- All your test files should be python files (extension:
.py
) - All your test files and folders should start by
test_
- Your file organization in the tests folder should be the same as your project: ex: for
models/base_model.py
, unit tests must be in:tests/test_models/test_base_model.py
- All your tests should be executed by using this command:
python3 -m unittest discover tests
- You can also test file by file by using this command:
python3 -m unittest tests/test_models/test_base_model.py
- We strongly encourage you to work together on test cases, so that you don’t miss any edge cases.
$ pip3 install Flask
No no no! We are already too far in the project to restart everything.
But once again, let’s work on a new codebase.
For this project you will fork this codebase:
- Update the repository name to
AirBnB_clone_v3
- Update the
README.md
:- Add yourself as an author of the project
- Add new information about your new contribution
- Make it better!
- If you’re the owner of this codebase, create a new repository called
AirBnB_clone_v3
and copy over all files fromAirBnB_clone_v2
Since the beginning we’ve been using the unittest module, but do you know why unittests are so important? Because when you add a new feature, you refactor a piece of code, etc… you want to be sure you didn’t break anything.
At Holberton, we have a lot of tests, and they all pass! Just for the Intranet itself, there are:
5,213
assertions (as of 08/20/2018)13,061
assertions (as of 01/25/2021)
The following requirements must be met for your project:
- all current tests must pass (don’t delete them…)
- add new tests as much as you can (tests are mandatory for some tasks)
guillaume@ubuntu:~/AirBnB_v3$ python3 -m unittest discover tests 2>&1 | tail -1
OK
guillaume@ubuntu:~/AirBnB_v3$ HBNB_ENV=test HBNB_MYSQL_USER=hbnb_test HBNB_MYSQL_PWD=hbnb_test_pwd HBNB_MYSQL_HOST=localhost HBNB_MYSQL_DB=hbnb_test_db HBNB_TYPE_STORAGE=db python3 -m unittest discover tests 2>&1 /dev/null | tail -n 1
OK
guillaume@ubuntu:~/AirBnB_v3$
Update DBStorage
and FileStorage
, adding two new methods. All changes should be done in the branch storage_get_count
:
A method to retrieve one object:
- Prototype:
def get(self, cls, id):
cls
: classid
: string representing the object ID
- Returns the object based on the class and its ID, or
None
if not found
A method to count the number of objects in storage:
- Prototype:
def count(self, cls=None):
cls
: class (optional)
- Returns the number of objects in storage matching the given class. If no class is passed, returns the count of all objects in storage.
Don’t forget to add new tests for these 2 methods on each storage engine.
guillaume@ubuntu:~/AirBnB_v3$ cat test_get_count.py
#!/usr/bin/python3
""" Test .get() and .count() methods
"""
from models import storage
from models.state import State
print("All objects: {}".format(storage.count()))
print("State objects: {}".format(storage.count(State)))
first_state_id = list(storage.all(State).values())[0].id
print("First state: {}".format(storage.get(State, first_state_id)))
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ HBNB_MYSQL_USER=hbnb_dev HBNB_MYSQL_PWD=hbnb_dev_pwd HBNB_MYSQL_HOST=localhost HBNB_MYSQL_DB=hbnb_dev_db HBNB_TYPE_STORAGE=db ./test_get_count.py
All objects: 1013
State objects: 27
First state: [State] (f8d21261-3e79-4f5c-829a-99d7452cd73c) {'name': 'Colorado', 'updated_at': datetime.datetime(2017, 3, 25, 2, 17, 6), 'created_at': datetime.datetime(2017, 3, 25, 2, 17, 6), '_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x7fc0103a8e80>, 'id': 'f8d21261-3e79-4f5c-829a-99d7452cd73c'}
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ ./test_get_count.py
All objects: 19
State objects: 5
First state: [State] (af14c85b-172f-4474-8a30-d4ec21f9795e) {'updated_at': datetime.datetime(2017, 4, 13, 17, 10, 22, 378824), 'name': 'Arizona', 'id': 'af14c85b-172f-4474-8a30-d4ec21f9795e', 'created_at': datetime.datetime(2017, 4, 13, 17, 10, 22, 378763)}
guillaume@ubuntu:~/AirBnB_v3$
For this task, you must make a pull request on GitHub.com, and ask at least one of your peer to review and merge it.
It’s time to start your API!
Your first endpoint (route) will be to return the status of your API:
guillaume@ubuntu:~/AirBnB_v3$ HBNB_MYSQL_USER=hbnb_dev HBNB_MYSQL_PWD=hbnb_dev_pwd HBNB_MYSQL_HOST=localhost HBNB_MYSQL_DB=hbnb_dev_db HBNB_TYPE_STORAGE=db HBNB_API_HOST=0.0.0.0 HBNB_API_PORT=5000 python3 -m api.v1.app
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
In another terminal:
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET http://0.0.0.0:5000/api/v1/status
{
"status": "OK"
}
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET -s http://0.0.0.0:5000/api/v1/status -vvv 2>&1 | grep Content-Type
< Content-Type: application/json
guillaume@ubuntu:~/AirBnB_v3$
Magic right? (No need to have a pretty rendered output, it’s a JSON, only the structure is important)
Ok, let starts:
- Create a folder
api
at the root of the project with an empty file__init__.py
- Create a folder
v1
insideapi
:- create an empty file
__init__.py
- create a file
app.py
:- create a variable
app
, instance ofFlask
- import
storage
frommodels
- import
app_views
fromapi.v1.views
- register the blueprint
app_views
to your Flask instanceapp
- declare a method to handle
@app.teardown_appcontext
that callsstorage.close
() - inside
if __name__ == "__main__":
, run your Flask server (variableapp
) with:- host = environment variable
HBNB_API_HOST
or0.0.0.0
if not defined - port = environment variable
HBNB_API_PORT
or5000
if not defined threaded=True
- host = environment variable
- create a variable
- create an empty file
- Create a folder
views
insidev1
:- create a file
__init__.py:
- import
Blueprint
fromflask
doc - create a variable
app_views
which is an instance ofBlueprint
(url prefix must be/api/v1
) - wildcard import of everything in the package
api.v1.views.index
=> PEP8 will complain about it, don’t worry, it’s normal and this file (v1/views/__init__.py
) won’t be check.
- import
- create a file
- create a file
index.py
- import
app_views
fromapi.v1.views
- create a route
/status
on the objectapp_views
that returns a JSON:"status": "OK"
(see example)
- import
Create an endpoint that retrieves the number of each objects by type:
- In
api/v1/views/index.py
- Route:
/api/v1/stats
- You must use the newly added
count()
method fromstorage
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET http://0.0.0.0:5000/api/v1/stats
{
"amenities": 47,
"cities": 36,
"places": 154,
"reviews": 718,
"states": 27,
"users": 31
}
guillaume@ubuntu:~/AirBnB_v3$
(No need to have a pretty rendered output, it’s a JSON, only the structure is important
Designers are really creative when they have to design a “404 page”, a “Not found”… 34 brilliantly designed 404 error pages
Today it’s different, because you won’t use HTML and CSS, but JSON!
In api/v1/app.py
, create a handler for 404
errors that returns a JSON-formatted 404
status code response. The content should be: "error": "Not found"
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET http://0.0.0.0:5000/api/v1/nop
{
"error": "Not found"
}
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET http://0.0.0.0:5000/api/v1/nop -vvv
* Trying 0.0.0.0...
* TCP_NODELAY set
* Connected to 0.0.0.0 (127.0.0.1) port 5000 (#0)
> GET /api/v1/nop HTTP/1.1
> Host: 0.0.0.0:5000
> User-Agent: curl/7.51.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 404 NOT FOUND
< Content-Type: application/json
< Content-Length: 27
< Server: Werkzeug/0.12.1 Python/3.4.3
< Date: Fri, 14 Apr 2017 23:43:24 GMT
<
{
"error": "Not found"
}
guillaume@ubuntu:~/AirBnB_v3$
Create a new view for State
objects that handles all default RESTFul API actions:
- In the file
api/v1/views/states.py
- You must use
to_dict()
to retrieve an object into a valid JSON - Update
api/v1/views/__init__.py
to import this new file
Retrieves the list of all State
objects: GET /api/v1/states
Retrieves a State
object: GET /api/v1/states/<state_id>
- If the
state_id
is not linked to anyState
object, raise a404
error
Deletes a State
object:: DELETE /api/v1/states/<state_id>
- If the
state_id
is not linked to anyState
object, raise a404
error - Returns an empty dictionary with the status code
200
Creates a State
: POST /api/v1/states
- You must use
request.get_json
from Flask to transform the HTTP body request to a dictionary - If the HTTP body request is not valid JSON, raise a
400
error with the messageNot a JSON
- If the dictionary doesn’t contain the key
name
, raise a400
error with the messageMissing name
- Returns the new
State
with the status code201
Updates a State
object: PUT /api/v1/states/<state_id>
- If the
state_id
is not linked to anyState
object, raise a404
error - You must use
request.get_json
from Flask to transform the HTTP body request to a dictionary - If the HTTP body request is not valid JSON, raise a
400
error with the messageNot a JSON
- Update the
State
object with all key-value pairs of the dictionary. - Ignore keys:
id
,created_at
andupdated_at
- Returns the
State
object with the status code200
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET http://0.0.0.0:5000/api/v1/states/
[
{
"__class__": "State",
"created_at": "2017-04-14T00:00:02",
"id": "8f165686-c98d-46d9-87d9-d6059ade2d99",
"name": "Louisiana",
"updated_at": "2017-04-14T00:00:02"
},
{
"__class__": "State",
"created_at": "2017-04-14T16:21:42",
"id": "1a9c29c7-e39c-4840-b5f9-74310b34f269",
"name": "Arizona",
"updated_at": "2017-04-14T16:21:42"
},
...
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET http://0.0.0.0:5000/api/v1/states/8f165686-c98d-46d9-87d9-d6059ade2d99
{
"__class__": "State",
"created_at": "2017-04-14T00:00:02",
"id": "8f165686-c98d-46d9-87d9-d6059ade2d99",
"name": "Louisiana",
"updated_at": "2017-04-14T00:00:02"
}
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ curl -X POST http://0.0.0.0:5000/api/v1/states/ -H "Content-Type: application/json" -d '{"name": "California"}' -vvv
* Trying 0.0.0.0...
* TCP_NODELAY set
* Connected to 0.0.0.0 (127.0.0.1) port 5000 (#0)
> POST /api/v1/states/ HTTP/1.1
> Host: 0.0.0.0:5000
> User-Agent: curl/7.51.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 22
>
* upload completely sent off: 22 out of 22 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 201 CREATED
< Content-Type: application/json
< Content-Length: 195
< Server: Werkzeug/0.12.1 Python/3.4.3
< Date: Sat, 15 Apr 2017 01:30:27 GMT
<
{
"__class__": "State",
"created_at": "2017-04-15T01:30:27.557877",
"id": "feadaa73-9e4b-4514-905b-8253f36b46f6",
"name": "California",
"updated_at": "2017-04-15T01:30:27.558081"
}
* Curl_http_done: called premature == 0
* Closing connection 0
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ curl -X PUT http://0.0.0.0:5000/api/v1/states/feadaa73-9e4b-4514-905b-8253f36b46f6 -H "Content-Type: application/json" -d '{"name": "California is so cool"}'
{
"__class__": "State",
"created_at": "2017-04-15T01:30:28",
"id": "feadaa73-9e4b-4514-905b-8253f36b46f6",
"name": "California is so cool",
"updated_at": "2017-04-15T01:51:08.044996"
}
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET http://0.0.0.0:5000/api/v1/states/feadaa73-9e4b-4514-905b-8253f36b46f6
{
"__class__": "State",
"created_at": "2017-04-15T01:30:28",
"id": "feadaa73-9e4b-4514-905b-8253f36b46f6",
"name": "California is so cool",
"updated_at": "2017-04-15T01:51:08"
}
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ curl -X DELETE http://0.0.0.0:5000/api/v1/states/feadaa73-9e4b-4514-905b-8253f36b46f6
{}
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET http://0.0.0.0:5000/api/v1/states/feadaa73-9e4b-4514-905b-8253f36b46f6
{
"error": "Not found"
}
guillaume@ubuntu:~/AirBnB_v3$
Same as State
, create a new view for City
objects that handles all default RESTFul API actions:
- In the file
api/v1/views/cities.py
- You must use
to_dict()
to serialize an object into valid JSON - Update
api/v1/views/__init__.py
to import this new file
Retrieves the list of all City
objects of a State
: GET /api/v1/states/<state_id>/cities
If the state_id
is not linked to any State
object, raise a 404
error
Retrieves a City
object. :GET /api/v1/cities/<city_id>
- If the
city_id
is not linked to anyCity
object, raise a404
error
Deletes a City
object: DELETE /api/v1/cities/<city_id>
- If the
city_id
is not linked to anyCity
object, raise a404
error - Returns an empty dictionary with the status code
200
Creates a City
: POST /api/v1/states/<state_id>/cities
- You must use
request.get_json
from Flask to transform the HTTP body request to a dictionary - If the
state_id
is not linked to anyState
object, raise a404
error - If the HTTP body request is not a valid JSON, raise a
400
error with the messageNot a JSON
- If the dictionary doesn’t contain the key
name
, raise a400
error with the messageMissing name
- Returns the new
City
with the status code201
Updates a City
object: PUT /api/v1/cities/<city_id>
- If the
city_id
is not linked to anyCity
object, raise a404
error - You must use
request.get_json
from Flask to transform the HTTP body request to a dictionary - If the HTTP request body is not valid JSON, raise a
400
error with the messageNot a JSON
- Update the
City
object with all key-value pairs of the dictionary - Ignore keys:
id
,state_id
,created_at
andupdated_at
- Returns the
City
object with the status code200
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET http://0.0.0.0:5000/api/v1/states/not_an_id/cities/
{
"error": "Not found"
}
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET http://0.0.0.0:5000/api/v1/states/2b9a4627-8a9e-4f32-a752-9a84fa7f4efd/cities
[
{
"__class__": "City",
"created_at": "2017-03-25T02:17:06",
"id": "1da255c0-f023-4779-8134-2b1b40f87683",
"name": "New Orleans",
"state_id": "2b9a4627-8a9e-4f32-a752-9a84fa7f4efd",
"updated_at": "2017-03-25T02:17:06"
},
{
"__class__": "City",
"created_at": "2017-03-25T02:17:06",
"id": "45903748-fa39-4cd0-8a0b-c62bfe471702",
"name": "Lafayette",
"state_id": "2b9a4627-8a9e-4f32-a752-9a84fa7f4efd",
"updated_at": "2017-03-25T02:17:06"
},
{
"__class__": "City",
"created_at": "2017-03-25T02:17:06",
"id": "e4e40a6e-59ff-4b4f-ab72-d6d100201588",
"name": "Baton rouge",
"state_id": "2b9a4627-8a9e-4f32-a752-9a84fa7f4efd",
"updated_at": "2017-03-25T02:17:06"
}
]
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET http://0.0.0.0:5000/api/v1/cities/1da255c0-f023-4779-8134-2b1b40f87683
{
"__class__": "City",
"created_at": "2017-03-25T02:17:06",
"id": "1da255c0-f023-4779-8134-2b1b40f87683",
"name": "New Orleans",
"state_id": "2b9a4627-8a9e-4f32-a752-9a84fa7f4efd",
"updated_at": "2017-03-25T02:17:06"
}
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ curl -X POST http://0.0.0.0:5000/api/v1/states/2b9a4627-8a9e-4f32-a752-9a84fa7f4efd/cities -H "Content-Type: application/json" -d '{"name": "Alexandria"}' -vvv
* Trying 0.0.0.0...
* TCP_NODELAY set
* Connected to 0.0.0.0 (127.0.0.1) port 5000 (#0)
> POST /api/v1/states/2b9a4627-8a9e-4f32-a752-9a84fa7f4efd/cities/ HTTP/1.1
> Host: 0.0.0.0:5000
> User-Agent: curl/7.51.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 22
>
* upload completely sent off: 22 out of 22 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 201 CREATED
< Content-Type: application/json
< Content-Length: 249
< Server: Werkzeug/0.12.1 Python/3.4.3
< Date: Sun, 16 Apr 2017 03:14:05 GMT
<
{
"__class__": "City",
"created_at": "2017-04-16T03:14:05.655490",
"id": "b75ae104-a8a3-475e-bf74-ab0a066ca2af",
"name": "Alexandria",
"state_id": "2b9a4627-8a9e-4f32-a752-9a84fa7f4efd",
"updated_at": "2017-04-16T03:14:05.655748"
}
* Curl_http_done: called premature == 0
* Closing connection 0
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ curl -X PUT http://0.0.0.0:5000/api/v1/cities/b75ae104-a8a3-475e-bf74-ab0a066ca2af -H "Content-Type: application/json" -d '{"name": "Bossier City"}'
{
"__class__": "City",
"created_at": "2017-04-16T03:14:06",
"id": "b75ae104-a8a3-475e-bf74-ab0a066ca2af",
"name": "Bossier City",
"state_id": "2b9a4627-8a9e-4f32-a752-9a84fa7f4efd",
"updated_at": "2017-04-16T03:15:12.895894"
}
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET http://0.0.0.0:5000/api/v1/cities/b75ae104-a8a3-475e-bf74-ab0a066ca2af
{
"__class__": "City",
"created_at": "2017-04-16T03:14:06",
"id": "b75ae104-a8a3-475e-bf74-ab0a066ca2af",
"name": "Bossier City",
"state_id": "2b9a4627-8a9e-4f32-a752-9a84fa7f4efd",
"updated_at": "2017-04-16T03:15:13"
}
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ curl -X DELETE http://0.0.0.0:5000/api/v1/cities/b75ae104-a8a3-475e-bf74-ab0a066ca2af
{}
guillaume@ubuntu:~/AirBnB_v3$
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET http://0.0.0.0:5000/api/v1/cities/b75ae104-a8a3-475e-bf74-ab0a066ca2af
{
"error": "Not found"
}
guillaume@ubuntu:~/AirBnB_v3$
Create a new view for menity
objects that handles all default RESTFul API actions:
- In the file
pi/v1/views/amenities.py
- You must use
to_dict()
to serialize an object into valid JSON - Update
api/v1/views/__init__.py
to import this new file
Retrieves the list of all Amenity
objects: GET /api/v1/amenities
Retrieves a Amenity
object: GET /api/v1/amenities/<amenity_id>
- If the
amenity_id
is not linked to anyAmenity
object, raise a404
error
Deletes a Amenity
object:: DELETE /api/v1/amenities/<amenity_id>
- If the
amenity_id
is not linked to anyAmenity
object, raise a404
error - Returns an empty dictionary with the status code
200
Creates a Amenity
: POST /api/v1/amenities
- You must use
request.get_json
from Flask to transform the HTTP request to a dictionary - If the HTTP request body is not valid JSON, raise a
400
error with the messageNot a JSON
- If the dictionary doesn’t contain the key
name
, raise a400
error with the messageMissing name
- Returns the new
Amenity
with the status code201
Updates a Amenity
object: PUT /api/v1/amenities/<amenity_id>
- If the
amenity_id
is not linked to anyAmenity
object, raise a404
error - You must use
request.get_json
from Flask to transform the HTTP request to a dictionary - If the HTTP request body is not valid JSON, raise a
400
error with the messageNot a JSON
- Update the
Amenity
object with all key-value pairs of the dictionary - Ignore keys:
id
,created_at
andupdated_at
- Returns the
Amenity
object with the status code200
Create a new view for User
object that handles all default RESTFul API actions:
- In the file
api/v1/views/users.py
- You must use
to_dict()
to retrieve an object into a valid JSON - Update
api/v1/views/__init__.py
to import this new file
Retrieves the list of all User
objects: GET /api/v1/users
Retrieves a User
object: GET /api/v1/users/<user_id>
- If the
user_id
is not linked to anyUser
object, raise a404
error
Deletes a User
object:: DELETE /api/v1/users/<user_id>
- If the
user_id
is not linked to anyUser
object, raise a404
error - Returns an empty dictionary with the status code
200
Creates a User: POST /api/v1/users
- You must use
request.get_json
from Flask to transform the HTTP body request to a dictionary - If the HTTP body request is not valid JSON, raise a
400
error with the messageNot a JSON
- If the dictionary doesn’t contain the key
email
, raise a400
error with the messageMissing email
- If the dictionary doesn’t contain the key
password
, raise a400
error with the messageMissing password
- Returns the new
User
with the status code201
Updates a User object
: PUT /api/v1/users/<user_id>
- If the
user_id
is not linked to anyUser
object, raise a404
error - You must use
request.get_json
from Flask to transform the HTTP body request to a dictionary - If the HTTP body request is not valid JSON, raise a
400
error with the messageNot a JSON
- Update the
User
object with all key-value pairs of the dictionary - Ignore keys:
id
,email
,created_at
andupdated_at
- Returns the
User
object with the status code200
Create a new view for Place
objects that handles all default RESTFul API actions:
- In the file
api/v1/views/places.py
- You must use
to_dict()
to retrieve an object into a valid JSON - Update
api/v1/views/__init__.py
to import this new file
Retrieves the list of all Place
objects of a City
: GET /api/v1/cities/<city_id>/places
- If the
city_id
is not linked to anyCity
object, raise a404
error
Retrieves a Place
object. : GET /api/v1/places/<place_id>
- If the
place_id
is not linked to anyPlace
object, raise a404
error
Deletes a Place
object: DELETE /api/v1/places/<place_id>
- If the
place_id
is not linked to anyPlace
object, raise a404
error - Returns an empty dictionary with the status code
200
Creates a Place
: POST /api/v1/cities/<city_id>/places
- You must use
request.get_json
from Flask to transform the HTTP request to a dictionary - If the
city_id
is not linked to anyCity
object, raise a404
error - If the HTTP request body is not valid JSON, raise a
400
error with the messageNot a JSON
- If the dictionary doesn’t contain the key
user_id
, raise a400
error with the messageMissing user_id
- If the
user_id
is not linked to anyUser
object, raise a404
error - If the dictionary doesn’t contain the key
name
, raise a400
error with the messageMissing name
- Returns the new
Place
with the status code201
Updates a Place
object: PUT /api/v1/places/<place_id>
- If the
place_id
is not linked to anyPlace
object, raise a404
error - You must use
request.get_json
from Flask to transform the HTTP request to a dictionary - If the HTTP request body is not valid JSON, raise a
400
error with the messageNot a JSON
- Update the
Place
object with all key-value pairs of the dictionary - Ignore keys:
id
,user_id
,city_id
,created_at
andupdated_at
- Returns the
Place
object with the status code200
Create a new view for Review
object that handles all default RESTFul API actions:
- In the file
api/v1/views/places_reviews.py
- You must use
to_dict()
to retrieve an object into valid JSON - Update
api/v1/views/__init__.py
to import this new file
Retrieves the list of all Review
objects of a Place
: GET /api/v1/places/<place_id>/reviews
- If the
place_id
is not linked to any Place object, raise a404
error
Retrieves a Review
object. : GET /api/v1/reviews/<review_id>
- If the
review_id
is not linked to anyReview
object, raise a404
error
Deletes a Review
object: DELETE /api/v1/reviews/<review_id>
- If the
review_id
is not linked to anyReview
object, raise a404
error - Returns an empty dictionary with the status code
200
Creates a Review
: POST /api/v1/places/<place_id>/reviews
- You must use
request.get_json
from Flask to transform the HTTP request to a dictionary - If the
place_id
is not linked to any Place object, raise a404
error - If the HTTP body request is not valid JSON, raise a
400
error with the messageNot a JSON
- If the dictionary doesn’t contain the key
user_id
, raise a400
error with the messageMissing user_id
- If the
user_id
is not linked to anyUser
object, raise a404
error - If the dictionary doesn’t contain the key
text
, raise a400
error with the messageMissing text
- Returns the new
Review
with the status code201
Updates a Review
object: PUT /api/v1/reviews/<review_id>
- If the
review_id
is not linked to anyReview
object, raise a404
error - You must use
request.get_json
from Flask to transform the HTTP request to a dictionary - If the HTTP request body is not valid JSON, raise a
400 error with the message
Not a JSON` - Update the
Review
object with all key-value pairs of the dictionary - Ignore keys:
id
,user_id
,place_id
,created_at
andupdated_at
- Returns the
Review
object with the status code200
A resource makes a cross-origin HTTP request when it requests a resource from a different domain, or port, than the one the first resource itself serves.
Read the full definition here
Why do we need this?
Because you will soon start allowing a web client to make requests your API. If your API doesn’t have a correct CORS setup, your web client won’t be able to access your data.
With Flask, it’s really easy, you will use the class CORS
of the module flask_cors
.
How to install it: $ pip3 install flask_cors
Update api/v1/app.py
to create a CORS
instance allowing: /* for 0.0.0.0
You will update it later when you will deploy your API to production.
Now you can see this HTTP Response Header: < Access-Control-Allow-Origin: 0.0.0.0
guillaume@ubuntu:~/AirBnB_v3$ curl -X GET http://0.0.0.0:5000/api/v1/cities/1da255c0-f023-4779-8134-2b1b40f87683 -vvv
* Trying 0.0.0.0...
* TCP_NODELAY set
* Connected to 0.0.0.0 (127.0.0.1) port 5000 (#0)
> GET /api/v1/states/2b9a4627-8a9e-4f32-a752-9a84fa7f4efd/cities/1da255c0-f023-4779-8134-2b1b40f87683 HTTP/1.1
> Host: 0.0.0.0:5000
> User-Agent: curl/7.51.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Access-Control-Allow-Origin: 0.0.0.0
< Content-Length: 236
< Server: Werkzeug/0.12.1 Python/3.4.3
< Date: Sun, 16 Apr 2017 04:20:13 GMT
<
{
"__class__": "City",
"created_at": "2017-03-25T02:17:06",
"id": "1da255c0-f023-4779-8134-2b1b40f87683",
"name": "New Orleans",
"state_id": "2b9a4627-8a9e-4f32-a752-9a84fa7f4efd",
"updated_at": "2017-03-25T02:17:06"
}
* Curl_http_done: called premature == 0
* Closing connection 0
guillaume@ubuntu:~/AirBnB_v3$
Create a new view for the link between Place
objects and Amenity
objects that handles all default RESTFul API actions:
- In the file
api/v1/views/places_amenities.py
- You must use
to_dict()
to retrieve an object into a valid JSON - Update
api/v1/views/__init__.py
to import this new file - Depending of the storage:
DBStorage
: list, create and deleteAmenity
objects fromamenities
relationshipFileStorage
: list, add and removeAmenity
ID in the listamenity_ids
of aPlace
object
Retrieves the list of all Amenity
objects of a Place
: GET /api/v1/places/<place_id>/amenities
- If the
place_id
is not linked to anyPlace
object, raise a404
error
Deletes a Amenity
object to a Place
: DELETE /api/v1/places/<place_id>/amenities/<amenity_id>
- If the
place_id
is not linked to anyPlace
object, raise a404
error - If the
amenity_id
is not linked to anyAmenity
object, raise a404
error - If the
Amenity
is not linked to thePlace
before the request, raise a404
error - Returns an empty dictionary with the status code
200
Link a Amenity
object to a Place
: POST /api/v1/places/<place_id>/amenities/<amenity_id>
- No HTTP body needed
- If the
place_id
is not linked to anyPlace
object, raise a404
error - If the
amenity_id
is not linked to anyAmenity
object, raise a404
error - If the
Amenity
is already linked to thePlace
, return theAmenity
with the status code200
- Returns the
Amenity
with the status code201
Currently, the User
object is designed to store the user password in cleartext.
It’s super bad!
To avoid that, improve the User
object:
- Update the method
to_dict()
ofBaseModel
to remove thepassword
key except when it’s used byFileStorage
to save data to disk. Tips: default parameters - Each time a new
User
object is created or password updated, the password is hashed to a MD5 value - In the database for
DBStorage
, the password stored is now hashed to a MD5 value - In the file for
FileStorage
, the password stored is now hashed to a MD5 value
For the moment, the only way to list Place
objects is via GET /api/v1/cities/<city_id>/places.
Good, but not enough…
Update api/v1/views/places.py
to add a new endpoint: POST /api/v1/places_search
that retrieves all Place
objects depending of the JSON in the body of the request.
The JSON can contain 3 optional keys:
states
: list ofState
idscities
: list ofCity
idsamenities
: list ofAmenity
ids
Search rules:
- If the HTTP request body is not valid JSON, raise a
400
error with the messageNot a JSON
- If the JSON body is empty or each list of all keys are empty: retrieve all
Place
objects - If
states
list is not empty, results should include allPlace
objects for eachState
id listed - If
cities
list is not empty, results should include allPlace
objects for eachCity
id listed - Keys
states
andcities
are inclusive. Search results should include allPlace
objects in storage related to eachCity
in everyState
listed instates
, plus everyCity
listed individually incities
, unless thatCity
was already included bystates
.- Context:
- State A has 2 cities A1 and A2
- State B has 3 cities B1, B2 and B3
- A1 has 1 place
- A2 has 2 places
- B1 has 3 places
- B2 has 4 places
- B3 has 5 places
- Search: states = State A and cities = B2
- Result: all 4 places from the city B2 and the place from the city A1 and the 2 places of the city A2 (because they are part of State A) => 7 places returned
- Context:
- If
amenities
list is not empty, limit search results to onlyPlace
objects having allAmenity
ids listed - The key
amenities
is exclusive, acting as a filter on the results generated bystates
andcities
, or on allPlace
ifstates
andcities
are both empty or missing. - Results will only include
Place
objects having all listedamenities
. If aPlace
doesn’t have even one of theseamenities
, it won’t be retrieved.
guillaume@ubuntu:~/AirBnB_v3$ curl -X POST http://0.0.0.0:5000/api/v1/places_search -H "Content-Type: application/json" -d '{"states": ["2b9a4627-8a9e-4f32-a752-9a84fa7f4efd", "459e021a-e794-447d-9dd2-e03b7963f7d2"], "cities": ["5976f0e7-5c5f-4949-aae0-90d68fd239c0"]}'
[
{
"__class__": "Place",
"created_at": "2017-03-25T02:17:06",
"id": "dacec983-cec4-4f68-bd7f-af9068a305f5",
"name": "The Lynn House",
"city_id": "5976f0e7-5c5f-4949-aae0-90d68fd239c0",
"user_id": "3ea61b06-e22a-459b-bb96-d900fb8f843a",
"description": "Our place is 2 blocks from Vista Park (Farmer's Market), Historic Warren Ballpark, and about 2 miles from Old Bisbee where there is shopping, dining, and site seeing. We offer continental breakfast. You get the quiet life with great mountain and garden views. This is a 100+ year old cozy home which has been on both the Garden and Home tours. You have access to whole house, except for 1 restricted area (She-Shack). Hosts are on site in a casita in the back from 8pm until 7am when we are in town.<BR /><BR />Our home has two bedrooms, one king and one queen. There are 2 bathrooms, 1 1950's soak tub with shower and 1 with shower only. Guests have access to the living/dining room area, and the kitchen (except for use of stove/oven). Each morning, coffee/tea, and muffins are ready for guests. A small frig is available in the dining room with water/juice and an area for guest items. 1 parking space is directly across the street.",
"number_rooms": 2,
"number_bathrooms": 2,
"max_guest": 4,
"price_by_night": 82,
"latitude": 31.4141,
"longitude": -109.879,
"updated_at": "2017-03-25T02:17:06"
},
{
"__class__": "Place",
"created_at": "2017-03-25T12:17:06",
"id": "85f979ad-a345-4190-9d1b-719bb3c642ba",
"name": "Little blue House in New Orleans",
"city_id": "1da255c0-f023-4779-8134-2b1b40f87683",
"user_id": "44b3ab44-4798-4a3a-9f72-ee1eeace4b33",
"description": "Nice place closed to Bourbon street.",
"number_rooms": 1,
"number_bathrooms": 1,
"max_guest": 3,
"price_by_night": 42,
"latitude": 29.951065,
"longitude": -90.071533,
"updated_at": "2017-03-25T02:17:06"
},
...
guillaume@ubuntu:~/AirBnB_v3$