Python 应用部署 #
Flask 应用部署 #
项目结构 #
text
myapp/
├── app/
│ ├── __init__.py
│ ├── routes.py
│ ├── models.py
│ └── config.py
├── migrations/
├── tests/
│ └── test_app.py
├── requirements.txt
├── Procfile
├── runtime.txt
├── .env.example
└── app.json
requirements.txt #
text
Flask==3.0.0
gunicorn==21.2.0
psycopg2-binary==2.9.9
redis==5.0.1
Flask-SQLAlchemy==3.1.1
Flask-Migrate==4.0.5
python-dotenv==1.0.0
flask-cors==4.0.0
runtime.txt #
text
python-3.12.0
应用代码 #
python
# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
import redis
import os
db = SQLAlchemy()
redis_client = None
def create_app():
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev')
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
CORS(app)
global redis_client
redis_url = os.environ.get('REDIS_URL')
if redis_url:
redis_client = redis.from_url(
redis_url,
ssl_cert_reqs=None
)
from app.routes import bp
app.register_blueprint(bp)
@app.route('/health')
def health():
return {'status': 'ok', 'timestamp': datetime.utcnow().isoformat()}
return app
python
# app/routes.py
from flask import Blueprint, jsonify, request
from app import db
from app.models import User
bp = Blueprint('routes', __name__)
@bp.route('/')
def index():
return jsonify({
'message': 'Welcome to My App',
'version': '1.0.0'
})
@bp.route('/users', methods=['GET'])
def get_users():
users = User.query.order_by(User.created_at.desc()).all()
return jsonify([u.to_dict() for u in users])
@bp.route('/users/<int:id>', methods=['GET'])
def get_user(id):
user = User.query.get_or_404(id)
return jsonify(user.to_dict())
@bp.route('/users', methods=['POST'])
def create_user():
data = request.get_json()
if not data.get('email'):
return jsonify({'error': 'Email is required'}), 400
user = User(email=data['email'], name=data.get('name'))
db.session.add(user)
try:
db.session.commit()
return jsonify(user.to_dict()), 201
except Exception as e:
db.session.rollback()
return jsonify({'error': 'Email already exists'}), 409
python
# app/models.py
from datetime import datetime
from app import db
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True, nullable=False)
name = db.Column(db.String(255))
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'email': self.email,
'name': self.name,
'created_at': self.created_at.isoformat()
}
Procfile #
text
web: gunicorn app:create_app\(\)
release: flask db upgrade
app.json #
json
{
"name": "myapp",
"description": "Python Flask app on Heroku",
"buildpacks": [
{
"name": "heroku/python"
}
],
"env": {
"SECRET_KEY": {
"generator": "secret"
}
},
"addons": [
{
"plan": "heroku-postgresql:mini"
},
{
"plan": "heroku-redis:mini"
}
]
}
部署 Flask 应用 #
bash
# 创建应用
heroku create myapp
# 添加数据库
heroku addons:create heroku-postgresql:mini
heroku addons:create heroku-redis:mini
# 设置环境变量
heroku config:set SECRET_KEY=$(openssl rand -hex 32)
heroku config:set FLASK_APP=app
# 部署
git push heroku main
# 初始化数据库
heroku run flask db init
heroku run flask db migrate -m "Initial migration"
heroku run flask db upgrade
Django 应用部署 #
项目结构 #
text
myproject/
├── myproject/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── myapp/
│ ├── __init__.py
│ ├── models.py
│ ├── views.py
│ └── urls.py
├── manage.py
├── requirements.txt
├── Procfile
├── runtime.txt
└── static/
requirements.txt #
text
Django==5.0
gunicorn==21.2.0
psycopg2-binary==2.9.9
django-heroku==0.3.1
whitenoise==6.6.0
redis==5.0.1
django-redis==5.4.0
settings.py #
python
import os
import django_heroku
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-secret-key')
DEBUG = os.environ.get('DEBUG', 'False') == 'True'
ALLOWED_HOSTS = ['*']
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myapp',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DATABASE_URL'),
}
}
if os.environ.get('DATABASE_URL'):
import dj_database_url
DATABASES['default'] = dj_database_url.parse(os.environ.get('DATABASE_URL'))
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/'
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': os.environ.get('REDIS_URL', 'redis://localhost:6379/1'),
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
django_heroku.settings(locals())
Procfile #
text
web: gunicorn myproject.wsgi
release: python manage.py migrate
部署 Django 应用 #
bash
# 创建应用
heroku create myproject
# 添加数据库
heroku addons:create heroku-postgresql:mini
heroku addons:create heroku-redis:mini
# 设置环境变量
heroku config:set SECRET_KEY=$(openssl rand -hex 32)
heroku config:set DEBUG=False
# 部署
git push heroku main
# 创建超级用户
heroku run python manage.py createsuperuser
# 收集静态文件
heroku run python manage.py collectstatic --noinput
测试 #
Flask 测试 #
python
# tests/test_app.py
import pytest
from app import create_app, db
@pytest.fixture
def app():
app = create_app()
app.config['TESTING'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
with app.app_context():
db.create_all()
yield app
db.drop_all()
@pytest.fixture
def client(app):
return app.test_client()
def test_health(client):
response = client.get('/health')
assert response.status_code == 200
assert response.json['status'] == 'ok'
def test_index(client):
response = client.get('/')
assert response.status_code == 200
assert 'message' in response.json
def test_create_user(client):
response = client.post('/users', json={
'email': 'test@example.com',
'name': 'Test User'
})
assert response.status_code == 201
assert response.json['email'] == 'test@example.com'
Django 测试 #
python
# myapp/tests.py
from django.test import TestCase
from django.urls import reverse
from .models import User
class UserModelTest(TestCase):
def test_create_user(self):
user = User.objects.create(
email='test@example.com',
name='Test User'
)
self.assertEqual(user.email, 'test@example.com')
class UserViewTest(TestCase):
def test_user_list(self):
response = self.client.get(reverse('user-list'))
self.assertEqual(response.status_code, 200)
最佳实践 #
1. 环境变量管理 #
python
# app/config.py
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY')
DATABASE_URL = os.environ.get('DATABASE_URL')
REDIS_URL = os.environ.get('REDIS_URL')
@classmethod
def validate(cls):
required = ['SECRET_KEY', 'DATABASE_URL']
missing = [k for k in required if not getattr(cls, k)]
if missing:
raise ValueError(f"Missing required config: {missing}")
2. 日志配置 #
python
import logging
import sys
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s %(levelname)s %(name)s %(message)s',
handlers=[logging.StreamHandler(sys.stdout)]
)
logger = logging.getLogger(__name__)
3. 错误处理 #
python
@app.errorhandler(404)
def not_found(error):
return jsonify({'error': 'Not found'}), 404
@app.errorhandler(500)
def internal_error(error):
return jsonify({'error': 'Internal server error'}), 500
下一步 #
Python 应用部署完成后,接下来学习 全栈应用部署 了解前后端分离部署!
最后更新:2026-03-28