I am working through "Days 9-12 API Star" in the 100 Days of Web Python course and I am having issues with the PUT method.
I have cross checked my code against the GitHub version and my code looks logically the same but when I test the PUT method using Postman the 'update_car' function is not called/executed.
I have added print statements to the 'update_car' function to confirm this and indeed there is nothing printed in the console. Adding a print to the 'get_car' function however does print to the console.
My sequence of testing is the some as the video i.e. submit a GET request from Postman (for id 55); the submit the PUT request with updated parameters in the body of the request.
I have created a Git of the server output: https://gist.github.com/gloc-mike/03d1b7eda91b3a07ced3c6efa4ce4d34
In that you can see that the GET request was submitted and the print statement shows id 55; then when I submit the PUT request, the server responds with a 302 (redirect?), does not print my debug messages and then performs a GET request (system generated).
I'm on Mac; PyCharm (2019.3.1); virtual environments pipenv and pyenv; Pyton 3.7.4; apistar version is the recommended version. Also tried running it from the terminal (to remove PyCharm from the equation) but I get the same results.
There is a setting in Postman:
Automatically follow redirects
Prevent requests that return a 300-series response from being automatically redirected.
which I have tried turning off (default is on) but apart from that nothing else has been changed in Postman.
The PUT request from Postman:
PUT /55 HTTP/1.1
Host: 127.0.0.1:5000
User-Agent: PostmanRuntime/7.20.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 914028c8-fc63-47f0-a99f-9132150dd2d3,80314209-e775-4466-8bbd-426400394684
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:5000/55
Connection: keep-alive
cache-control: no-cache
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="manufacturer"
Jeep
------WebKitFormBoundary7MA4YWxkTrZu0gW--,
Content-Disposition: form-data; name="manufacturer"
Jeep
------WebKitFormBoundary7MA4YWxkTrZu0gW--
Content-Disposition: form-data; name="model"
Mountain High II
------WebKitFormBoundary7MA4YWxkTrZu0gW--,
Content-Disposition: form-data; name="manufacturer"
Jeep
------WebKitFormBoundary7MA4YWxkTrZu0gW--
Content-Disposition: form-data; name="model"
Mountain High II
------WebKitFormBoundary7MA4YWxkTrZu0gW--
Content-Disposition: form-data; name="year"
2001
------WebKitFormBoundary7MA4YWxkTrZu0gW--
My pipfile
:
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
pytest = "*"
apistar = ">=0.5.41,<0.5.1000"
[requires]
python_version = "3.7"
My version of app.py
sans the delete method:
import json
from typing import List
from apistar import App, Route, types, validato
![Screen Shot 2019-12-20 at 18 40 08](https://user-images.githubusercontent.com/2324626/71239822-a5454d80-235b-11ea-9c70-7abdec867949.png)
![Screen Shot 2019-12-20 at 18 40 53](https://user-images.githubusercontent.com/2324626/71239826-ad04f200-235b-11ea-8c12-dcbf6ecf2201.png)
rs
from apistar.http import JSONResponse
# helpers
def _load_cars_data():
with open('cars.json') as f:
cars = json.loads(f.read())
# create a dictionary of the form {car_id: car}
return {car['id']: car for car in cars}
# To load in the cars call the cars helper function
cars_db = _load_cars_data()
# Some constants
VALID_MANUFACTURERS = set([car['manufacturer'] for car in cars_db.values()])
CAR_NOT_FOUND = 'Car not found!'
# Definition - using APIStar's types.Type to get access to validation &
# serialisation
class Car(types.Type):
# No auto-incrementer available so assign ID in POST - hence the need to
# use 'allow_null=True' in this code
id = validators.Integer(allow_null=True)
manufacturer = validators.String(enum=list(VALID_MANUFACTURERS))
model = validators.String(max_length=35)
year = validators.Integer(minimum=1900, maximum=2050)
# Make something OPTIONAL just add "default=''"
vin = validators.String(max_length=50, default='')
# API methods
def list_cars() -> List[Car]:
# cars_db.items() returns key, value tuple so to get the value from the
# tuple one must use index [1]
# Wrapping 'car[1]' with Car() serialises it :) i.e. turn a dictionary
# into a Car object
return [Car(car[1]) for car in sorted(cars_db.items())]
def create_car(car: Car) -> JSONResponse:
# Since no auto-increment available, manually determine the length of the
# DB and increment by 1
car_id = max(cars_db.keys())+1
# Update the ID for the car we want to create with the new ID
car.id = car_id
# Store the new car in the DB
cars_db[car_id] = car
return JSONResponse(Car(car), status_code=201)
def get_car(car_id: int) -> JSONResponse:
print(f"GET car: {car_id}")
car = cars_db.get(car_id)
# if a car exists, ".get(car_id)" returns the car otherwise returns None
if not car:
error = {'error': CAR_NOT_FOUND}
return JSONResponse(error, status_code=404)
return JSONResponse(Car(car), status_code=200)
def update_car(car_id: int, car: Car) -> JSONResponse:
print(f"PUT car: {car}")
# Check to see if the car exists so it can be updated
if not cars_db.get(car_id):
error = {'error', CAR_NOT_FOUND}
return JSONResponse(error, status_code=404)
# So it's ok, go ahead and update the car
car.id = car_id
cars_db[car_id] = car
print(f"PUT cars_db[car_id]: {cars_db[car_id]}")
return JSONResponse(Car(car), status_code=200)
def delete_car(car_id: int) -> JSONResponse:
pass
routes = [
Route('/', method='GET', handler=list_cars),
Route('/', method='POST', handler=create_car),
Route('/{car_id}/', method='GET', handler=get_car),
Route('/{car_id}/', method='PUT', handler=update_car),
Route('/{car_id}/', method='DELETE', handler=delete_car),
]
app = App(routes=routes)
if __name__ == '__main__':
app.serve('127.0.0.1', 5000, debug=True)
Any help with how I can trace this issue would be greatly appreciated.
Thanks
Michael