Commit 75e96015 authored by Michele Del Giudice's avatar Michele Del Giudice
Browse files

Init

parents
# Car Rental Flask API
This is a small Flask application exposing CRUD endpoints for Cars and Reservations using SQLAlchemy and MySQL.
Environment variables (or set a DATABASE_URL):
- MYSQL_USER (default: root)
- MYSQL_PASSWORD (default: empty)
- MYSQL_HOST (default: localhost)
- MYSQL_PORT (default: 3306)
- MYSQL_DB (default: car_rental)
- DATABASE_URL (optional; overrides the pieces above)
Install dependencies:
```bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
Run the app:
```bash
export MYSQL_USER=root
export MYSQL_PASSWORD=yourpass
export MYSQL_DB=car_rental
python app.py
```
API endpoints summary:
- GET /cars
- GET /cars/<id>
- POST /cars
- PUT /cars/<id>
- DELETE /cars/<id>
- GET /reservations
- GET /reservations/<id>
- POST /reservations
- PUT /reservations/<id>
- DELETE /reservations/<id>
Notes:
- Dates must be ISO formatted strings (e.g. 2025-11-17T12:00:00)
- Car `ports` must be either 3 or 5.
- Car `type` must be one of 'suv', 'small', 'van'.
import os
from datetime import datetime
from flask import Flask, request, jsonify, abort
from config import Config
from models import db, Car, Reservation, CarType
def create_app():
app = Flask(__name__)
app.config.from_object(Config)
db.init_app(app)
@app.errorhandler(404)
def not_found(e):
return jsonify({'error': 'Not found'}), 404
@app.errorhandler(400)
def bad_request(e):
return jsonify({'error': 'Bad request', 'message': str(e)}), 400
@app.route('/cars', methods=['GET'])
def list_cars():
cars = Car.query.all()
return jsonify([c.to_dict() for c in cars])
@app.route('/cars/<int:car_id>', methods=['GET'])
def get_car(car_id):
car = Car.query.get_or_404(car_id)
return jsonify(car.to_dict())
@app.route('/cars', methods=['POST'])
def create_car():
data = request.get_json() or {}
required = ['name', 'seats', 'ports', 'type', 'brand', 'license_plate']
for f in required:
if f not in data:
abort(400, f"Missing field: {f}")
# Validate ports
if data['ports'] not in (3, 5):
abort(400, 'ports must be either 3 or 5')
# Validate type
try:
car_type = CarType(data['type'])
except ValueError:
abort(400, "type must be one of 'suv', 'small', 'van'")
# Create
car = Car(
name=data['name'],
seats=int(data['seats']),
ports=int(data['ports']),
type=car_type,
brand=data['brand'],
license_plate=data['license_plate'],
)
db.session.add(car)
try:
db.session.commit()
except Exception as e:
db.session.rollback()
abort(400, f'Could not create car: {e}')
return jsonify(car.to_dict()), 201
@app.route('/cars/<int:car_id>', methods=['PUT'])
def update_car(car_id):
car = Car.query.get_or_404(car_id)
data = request.get_json() or {}
if 'name' in data:
car.name = data['name']
if 'seats' in data:
car.seats = int(data['seats'])
if 'ports' in data:
if data['ports'] not in (3, 5):
abort(400, 'ports must be either 3 or 5')
car.ports = int(data['ports'])
if 'type' in data:
try:
car.type = CarType(data['type'])
except ValueError:
abort(400, "type must be one of 'suv', 'small', 'van'")
if 'brand' in data:
car.brand = data['brand']
if 'license_plate' in data:
car.license_plate = data['license_plate']
try:
db.session.commit()
except Exception as e:
db.session.rollback()
abort(400, f'Could not update car: {e}')
return jsonify(car.to_dict())
@app.route('/cars/<int:car_id>', methods=['DELETE'])
def delete_car(car_id):
car = Car.query.get_or_404(car_id)
db.session.delete(car)
db.session.commit()
return jsonify({'deleted': car_id})
# Reservations
@app.route('/reservations', methods=['GET'])
def list_reservations():
res = Reservation.query.all()
return jsonify([r.to_dict() for r in res])
@app.route('/reservations/<int:res_id>', methods=['GET'])
def get_reservation(res_id):
r = Reservation.query.get_or_404(res_id)
return jsonify(r.to_dict())
@app.route('/reservations', methods=['POST'])
def create_reservation():
data = request.get_json() or {}
required = ['pickup_date', 'return_date', 'car_id', 'customer_fullname', 'drive_license']
for f in required:
if f not in data:
abort(400, f"Missing field: {f}")
try:
pickup = datetime.fromisoformat(data['pickup_date'])
ret = datetime.fromisoformat(data['return_date'])
except Exception:
abort(400, 'pickup_date and return_date must be ISO datetime strings')
if ret <= pickup:
abort(400, 'return_date must be after pickup_date')
car = Car.query.get(data['car_id'])
if not car:
abort(400, 'car_id does not exist')
reservation = Reservation(
pickup_date=pickup,
return_date=ret,
car_id=int(data['car_id']),
customer_fullname=data['customer_fullname'],
drive_license=data['drive_license'],
)
db.session.add(reservation)
try:
db.session.commit()
except Exception as e:
db.session.rollback()
abort(400, f'Could not create reservation: {e}')
return jsonify(reservation.to_dict()), 201
@app.route('/reservations/<int:res_id>', methods=['PUT'])
def update_reservation(res_id):
r = Reservation.query.get_or_404(res_id)
data = request.get_json() or {}
if 'pickup_date' in data:
try:
r.pickup_date = datetime.fromisoformat(data['pickup_date'])
except Exception:
abort(400, 'pickup_date must be ISO datetime string')
if 'return_date' in data:
try:
r.return_date = datetime.fromisoformat(data['return_date'])
except Exception:
abort(400, 'return_date must be ISO datetime string')
if 'car_id' in data:
car = Car.query.get(data['car_id'])
if not car:
abort(400, 'car_id does not exist')
r.car_id = int(data['car_id'])
if 'customer_fullname' in data:
r.customer_fullname = data['customer_fullname']
if 'drive_license' in data:
r.drive_license = data['drive_license']
if r.return_date <= r.pickup_date:
abort(400, 'return_date must be after pickup_date')
try:
db.session.commit()
except Exception as e:
db.session.rollback()
abort(400, f'Could not update reservation: {e}')
return jsonify(r.to_dict())
@app.route('/reservations/<int:res_id>', methods=['DELETE'])
def delete_reservation(res_id):
r = Reservation.query.get_or_404(res_id)
db.session.delete(r)
db.session.commit()
return jsonify({'deleted': res_id})
return app
if __name__ == '__main__':
app = create_app()
app.run(host=os.getenv('FLASK_RUN_HOST', '0.0.0.0'), port=int(os.getenv('FLASK_RUN_PORT', 5000)), debug=True)
import os
class Config:
# Database connection - use environment variables
MYSQL_USER = os.getenv('MYSQL_USER', 'root')
MYSQL_PASSWORD = os.getenv('MYSQL_PASSWORD', '')
MYSQL_HOST = os.getenv('MYSQL_HOST', 'localhost')
MYSQL_PORT = os.getenv('MYSQL_PORT', '3306')
MYSQL_DB = os.getenv('MYSQL_DB', 'car_rental')
SQLALCHEMY_DATABASE_URI = os.getenv(
'DATABASE_URL',
f"mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DB}"
)
SQLALCHEMY_TRACK_MODIFICATIONS = False
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import CheckConstraint
import enum
db = SQLAlchemy()
class CarType(enum.Enum):
suv = 'suv'
small = 'small'
van = 'van'
class Car(db.Model):
__tablename__ = 'cars'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128), nullable=False)
seats = db.Column(db.Integer, nullable=False)
ports = db.Column(db.Integer, nullable=False)
type = db.Column(db.Enum(CarType), nullable=False)
brand = db.Column(db.String(64), nullable=False)
license_plate = db.Column(db.String(64), nullable=False, unique=True)
reservations = db.relationship('Reservation', backref='car', cascade='all, delete-orphan')
__table_args__ = (
CheckConstraint('ports IN (3,5)', name='check_ports'),
)
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'seats': self.seats,
'ports': self.ports,
'type': self.type.value if self.type else None,
'brand': self.brand,
'license_plate': self.license_plate,
}
class Reservation(db.Model):
__tablename__ = 'reservations'
id = db.Column(db.Integer, primary_key=True)
pickup_date = db.Column(db.DateTime, nullable=False)
return_date = db.Column(db.DateTime, nullable=False)
car_id = db.Column(db.Integer, db.ForeignKey('cars.id'), nullable=False)
customer_fullname = db.Column(db.String(128), nullable=False)
drive_license = db.Column(db.String(64), nullable=False)
def to_dict(self):
return {
'id': self.id,
'pickup_date': self.pickup_date.isoformat() if self.pickup_date else None,
'return_date': self.return_date.isoformat() if self.return_date else None,
'car_id': self.car_id,
'customer_fullname': self.customer_fullname,
'drive_license': self.drive_license,
}
Flask>=2.0
Flask-SQLAlchemy>=3.0
PyMySQL>=1.0
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment