TIL Python Basics Day 66 - Building Your Own API with RESTful Routing

이다연·2021년 2월 25일
0

Udemy Python Course

목록 보기
62/64

  • API
    클라이언트에게 서버 자원을 잘 가져다 쓸 수 있도록 만들어 놓은 인터페이스
    API stands for Application Programming Interface which allows software applications to communicate with each other via API calls.

REST

REST란, "웹에 존재하는 모든 자원(이미지, 동영상, DB 자원)에 고유한 URI를 부여해 활용"하는 것으로, 자원을 정의하고 자원에 대한 주소를 지정하는 방법론을 의미.

따라서 Restful API는 REST 특징을 지키면서 API를 제공하는 것을 의미한다. (일종의 Coding Convention)

  • 자원 (Resouce)
  • URI- 행위 (Verb)
  • HTTP Method- 표현 (Representations)

REST: Architectural Style When accessing the resources
by Roy Fielding

If every Web API was built using the same common guiding principles, then it would be so easy for everybody to work together and be able to use different APIs quickly, easily, and efficiently.

REpresentational State Transfer

URI shows which resource provided

             HTTP request           
                        ->
Client(Browser)         <-         Server 
                          response

Request using http language

HTTPS: Secured
FTP: File Transfer Protocol

REST API: url format
URI: resource + structure
SOAP: previous norm
GraphQL: Next Generation

URL은 Uniform Resource Locator로 인터넷 상 자원의 위치를 의미합니다. 자원의 위치라는 것은 결국 어떤 파일의 위치를 의미합니다. 반면에 URI는 Uniform Resource Identifier로 인터넷 상의 자원을 식별하기 위한 문자열의 구성으로, URI는 URL을 포함하게 됩니다. 그러므로 URI가 보다 포괄적인 범위라고 할 수 있습니다.

Rule

1. URI: Noun

URI is a resource, written in a noun form.

  GET /members/show/1     (x)
  GET /members/1          (o)     

2. Using the HTTP Verbs

GET: read
POST: create new info
PUT: update all
PATCH: update including index
DELETE: delete including index

3. Use Specific Pattern of Routes/Endpoint URLs

  • lowercase only
  • no hyphen '-', only underscore '_'
  • no file extension e.g. .jon, .xml

API reference : Document. A technical writer might help the developer to build it. Developers said most time consuming part of using API is reading docs.

params: ?
adding: &

Developer Console

Testing Tools:
Insomenia REST Client
POSTman

Project

Remote Work Friendly Cafe API

GET is allowed by default on all routes. No need to specify as like this @app.route("/random", methods=["GET"])

GET

because our server is now acting as an API, we want to return a JSON containing the necessary data. Just like real public APIs.

GET a Random Cafe: Serialization (JSON)

Turn SQLAlchemy Object into a JSON.
Flask has a built-in helper: jsonify()

  • Our server is now acting as an API, we want to return a JSON containing the necessary data.
  • Format
#1.
return jsonify(
	    username=user.username,
            email=user.email,
  • Notice the difference in format compared to above
#2.
 return jsonify(
 	cafe={
        "name": random_cafe.name,
        "map_url": random_cafe.map_url,
    })
  • Flexbility

    You could also structure the response by omitting some properties like id.
    You could also group the Boolean properties into a subsection called amenities.

#3.
    return jsonify(cafe={
        #Omit the id from the response
        # "id": random_cafe.id,
        "name": random_cafe.name,
        "map_url": random_cafe.map_url,
        "img_url": random_cafe.img_url,
        "location": random_cafe.location,
        
        #Put some properties in a sub-category
        "amenities": {
          "seats": random_cafe.seats,
          "has_toilet": random_cafe.has_toilet,
          "has_wifi": random_cafe.has_wifi,
          "has_sockets": random_cafe.has_sockets,
          "can_take_calls": random_cafe.can_take_calls,
          "coffee_price": random_cafe.coffee_price,
        }
    })
  • To_Dict -> JSON

    So another method of serialising our database row Object to JSON is by first converting it to a dictionary and then using jsonify() to convert the dictionary (which is very similar in structure to JSON) to a JSON.

#4.

class Cafe(db.Model):
    id = db.Column(db.Integer, primary_key=True)
     ~~
    
    def to_dict(self):
    # Method 1. 
        dictionary = {}
        # Loop through each column in the data record
        for column in self.__table__.columns:
            #Create a new dictionary entry;
            # where the key is the name of the column
            # and the value is the value of the column
            dictionary[column.name] = getattr(self, column.name)
        return dictionary
        
    # Method 2. Altenatively use Dictionary Comprehension to do the same thing.
        return {column.name: getattr(self, column.name) for column in self.__table__.columns}  

GET: All Cafes

@app.route("/all")
def get_all_cafe():
    cafes = db.session.query(Cafe).all()
    return jsonify(cafes=[cafe.to_dict() for cafe in cafes])

GET: Find a cafe by Location

  • url

    Parameters are passed in the URL with a ?
    query_location = request.args.get("loc")


@app.route("/search")
def get_cafe_at_location():
    query_location = request.args.get("loc")
    cafe = db.session.query(Cafe).filter_by(location=query_location).first()
    if cafe:
        return jsonify(cafe=cafe.to_dict())
    else:
        return jsonify(error={"Not Found": "Sorry, we don't have a cafe at that location."})

Postman (Testing Tool)

POST - A New Cafe

POST: Updating entire entry to replace the previous one.


@app.route("/add", methods=["POST"])
def post_new_cafe():
    new_cafe = Cafe(
        name=request.form.get("name"),
        map_url=request.form.get("map_url"),
        img_url=request.form.get("img_url"),
        location=request.form.get("loc"),
        has_sockets=bool(request.form.get("sockets")),
        has_toilet=bool(request.form.get("toilet")),
        has_wifi=bool(request.form.get("wifi")),
        can_take_calls=bool(request.form.get("calls")),
        seats=request.form.get("seats"),
        coffee_price=request.form.get("coffee_price"),
    )
    db.session.add(new_cafe) #it will be staged for creation
    db.session.commit() #saves the change
    return jsonify(response={"success": "Successfully added the new cafe."})

PATCH

PATCH: only Sending piece of data that needs to be updated.

## HTTP PUT/PATCH - Update Record

@app.route("/update-price/<int:cafe_id>", methods=["PATCH"])
def update_coffee_price(cafe_id):
    coffee_price = request.args.get("new_price")  #/update-price?new_price=1.2 -> get hold of url value into coffee_price value
    cafe_to_update = db.session.query(Cafe).get(cafe_id) #pick the cafe by id

    if cafe_to_update:
        cafe_to_update.coffee_price = coffee_price
        db.session.commit()
        return jsonify(response={"success": "Successfully updated a new price."})
    else:
        return jsonify(error={
            "Not Found": "Sorry a cafe with that id was not found in the database."
        })

DELETE

@app.route("/report-closed/<int:cafe_id>", methods=["DELETE"])
def delete_cafe(cafe_id):
    api_key = request.args.get("api-key")
    if api_key == "TopSecretAPIKey":
        cafe = db.session.query(Cafe).get(cafe_id)
        if cafe:
            db.session.delete(cafe)
            db.session.commit()
            return jsonify(response={"success": "Successfully deleted the cafe from the database."}), 200
        else:
            return jsonify(error={"Not Found": "Sorry a cafe with that id was not found in the database."}), 404
    else:
        return jsonify(error={"Forbidden": "Sorry, that's not allowed. Make sure you have the correct api_key."}), 403

Documentatiion with Postman

profile
Dayeon Lee | Django & Python Web Developer

0개의 댓글