MINI STARS WARS REST API

Objective:
Build a mini Star Wars REST API with Flask that serves, filters, creates, updates, and deletes characters, returning JSON responses and clear HTTP status codes with robust error handling.

How it works:

  1. Initialize Flask app: Create the application with Flask(__name__) and expose a root route / that returns a simple welcome message.

  2. In-memory dataset: Define a characters list of dicts (id, name, species) to act as temporary storage for all endpoints.

  3. List & filter (GET /characters): Accept an optional species query param, filter when provided, return the filtered list or an error message if no matches; otherwise return the full collection.

  4. Create character (POST /characters): Read JSON body, validate required fields (id, name, species), reject duplicate ids, append to characters, and return 201 Created with the new resource.

  5. Fetch by id (GET /characters/<id>): Look up the character with next(...); return 404 if not found, otherwise return the character JSON.

  6. Update by id (PUT/PATCH /characters/update/<id>): On PUT, require all fields and replace/update; on PATCH, apply only provided fields; return the updated resource or appropriate error (400 for missing fields on PUT, 404 if id not found).

  7. Delete by id (DELETE /characters/delete/<id>): Locate the character, remove it from the list, and respond with a success message; return 404 if the id doesn’t exist.

  8. Error handling & run: Use try/except blocks to translate common issues into 400/404/409/500 responses, and start the server with app.run(debug=True) for development.


from flask import Flask, jsonify, request  

app = Flask(__name__)  
# Creamos una ruta básica
@app.route('/') 
def home(): 
    return "Bienvenido a la API de Star Wars"  

characters = [ 
    {"id": 1, "name": "Luke Skywalker", "species": "Human"},
    {"id": 2, "name": "Darth Vader", "species": "Human"},
    {"id": 3, "name": "Yoda", "species": "Unknown"}
]

@app.route('/characters', methods=['GET', 'POST']) 
def manage_characters(): 
    if request.method == 'GET': 
        species = request.args.get('species')  
        if species:
            filtered_characters = [character for character in characters if character["species"] == species]  
            if not filtered_characters:
                return jsonify({"error":f"No se encontraron personajes con la especie {species}"})  
            return jsonify(filtered_characters)  # Devolvemos la lista filtrada en JSON
        return jsonify(characters) 
    
    elif request.method == 'POST':
        try:
            new_character = request.get_json()  
            required_fields = ["id", "name", "species"]  # Campos obligatorios
            for field in required_fields:
                if field not in new_character:
                    raise KeyError(f"Falta el campo obligatorio: {field}")
            
            if any(c["id"] == new_character["id"] for c in characters): 	
                raise ValueError(f"Ya existe un personaje con el id {new_character['id']}")
            
            # Agregamos el nuevo personaje
            characters.append(new_character)
            return jsonify({"message": "Personaje añadido exitosamente", "character": new_character}), 201
        
        except KeyError as ke:
            return jsonify({"error": str(ke)}), 400  # Error de campo faltante
        except ValueError as ve:
            return jsonify({"error": str(ve)}), 409  # Conflicto de ID
        except Exception as e:
            return jsonify({"error": "Ocurrió un error inesperado"}), 500 # Error genérico

@app.route('/characters/<int:id>') 
def get_characters_by_id(id):
    try:
        character = next((c for c in characters if c["id"] == id), None) 
        if character is None:
            # Lanzar un error si el personaje no existe
            raise ValueError(f"No se encontro un personaje con el id {id}")
        return jsonify(character)  # Devolver el personaje encontrado
    except ValueError as ve:
        return jsonify({"error": str(ve)}), 404  # 404
    except Exception as e:
        # Manejo genérico de errores
        return jsonify({"error": "Ocurrio un error inesperado"}), 500


@app.route('/characters/update/<int:id>', methods=['PUT', 'PATCH']) 
def update_character_by_id(id): 
    try:
        character = next((c for c in characters if c["id"] == id), None)
        if not character:
            raise ValueError(f"No se encontró un personaje con el id {id}")
        
        updates = request.get_json()  # Datos del cuerpo
        if request.method == 'PUT':
            # Validar todos los campos en PUT
            required_fields = ["id", "name", "species"]
            for field in required_fields:
                if field not in updates:
                    raise KeyError(f"Falta el campo obligatorio: {field}")
            character.update(updates)
        elif request.method == 'PATCH':
            # Solo campos enviados
            character.update(updates)
        
        return jsonify({"message": "Personaje actualizado exitosamente", "character": character}), 200
    except KeyError as ke:
        return jsonify({"error": str(ke)}), 400
    except ValueError as ve:
        return jsonify({"error": str(ve)}), 404
    except Exception as e:
        return jsonify({"error": "Ocurrió un error inesperado"}), 500

# Endpoint para eliminar un personaje por ID
@app.route('/characters/delete/<int:id>', methods=['DELETE'])
def delete_character_by_id(id):
    try:
        character = next((c for c in characters if c["id"] == id), None)
        if not character:
            raise ValueError(f"No se encontró un personaje con el id {id}")
        characters.remove(character)  
        return jsonify({"message": f"Personaje con id {id} eliminado exitosamente"}), 200
    except ValueError as ve:
        return jsonify({"error": str(ve)}), 404
    except Exception as e:
        return jsonify({"error": "Ocurrió un error inesperado"}), 500

if __name__ == "__main__": 
    app.run(debug=True)  
Scroll al inicio