KR_Pyramid - somaz94/python-study GitHub Wiki
Pyramidλ μ μ°νκ³ νμ₯ κ°λ₯ν νμ΄μ¬ μΉ νλ μμν¬μ΄λ€. νμ₯μ±κ³Ό λͺ¨λμ±μ μ€μ μ λ μ΄ νλ μμν¬λ κ°λ¨ν μ ν리μΌμ΄μ
λΆν° λκ·λͺ¨ μν°νλΌμ΄μ¦ μμ€μ μΉ μλΉμ€κΉμ§ λ€μν κ·λͺ¨μ νλ‘μ νΈμ μ ν©νλ€.
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
def hello_world(request):
"""λ¨μν Hello World μλ΅μ λ°ννλ λ·° ν¨μ"""
return Response('Hello World!')
def create_user(request):
"""μ¬μ©μ μμ± API μλν¬μΈνΈ"""
user_data = request.json_body
# μ¬μ©μ μμ± λ‘μ§
return Response(json={'status': 'created', 'user_id': 123})
class PyramidApp:
"""Pyramid μ ν리μΌμ΄μ
μ μ€μ νκ³ μ€ννλ ν΄λμ€"""
def __init__(self, host='0.0.0.0', port=6543):
self.host = host
self.port = port
self.config = None
self.app = None
def setup(self):
"""μ ν리μΌμ΄μ
μ€μ """
self.config = Configurator()
# κΈ°λ³Έ λΌμ°νΈ μ€μ
self.config.add_route('hello', '/')
self.config.add_view(hello_world, route_name='hello')
# API λΌμ°νΈ μ€μ
self.config.add_route('users', '/users')
self.config.add_view(create_user, route_name='users',
request_method='POST', renderer='json')
# μ μ νμΌ μ€μ
self.config.add_static_view('static', 'static', cache_max_age=3600)
# WSGI μ ν리μΌμ΄μ
μμ±
self.app = self.config.make_wsgi_app()
return self
def serve(self):
"""μλ² μ€ν"""
print(f"μλ² μμ: http://{self.host}:{self.port}")
server = make_server(self.host, self.port, self.app)
server.serve_forever()
if __name__ == '__main__':
app = PyramidApp()
app.setup().serve()
β
νΉμ§:
- μ μ°ν λΌμ°ν μ€μ
- λ€μν λ·° ν¨μ μ μ λ°©μ
- WSGI νΈν μλ² μ€ν
- νμ₯μ±μ΄ λ°μ΄λ ꡬ쑰
- μ€μ κ°λ₯ν λ―Έλ€μ¨μ΄
- λ€μν ν νλ¦Ώ μμ§ μ§μ
- νλ¬κ·ΈμΈ κΈ°λ° μν€ν μ²
Pyramidλ μ μ°ν λΌμ°ν
μμ€ν
κ³Ό λ€μν λ·° μ μ λ°©μμ μ 곡νμ¬ μΉ μ ν리μΌμ΄μ
μ URL ꡬ쑰μ μμ² μ²λ¦¬λ₯Ό ν¨κ³Όμ μΌλ‘ κ΄λ¦¬ν μ μλ€.
from pyramid.view import view_config, view_defaults
from pyramid.response import Response
from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest
# ν΄λμ€ κΈ°λ° λ·° μ μ
@view_defaults(route_name='users', renderer='json')
class UserViews:
"""μ¬μ©μ κ΄λ¦¬λ₯Ό μν λ·° ν΄λμ€"""
def __init__(self, request):
self.request = request
self.user_db = {
'1': {'id': '1', 'name': 'User 1', 'email': '[email protected]'},
'2': {'id': '2', 'name': 'User 2', 'email': '[email protected]'}
}
@view_config(request_method='GET')
def get_users(self):
"""λͺ¨λ μ¬μ©μ μ‘°ν"""
return {'users': list(self.user_db.values())}
@view_config(request_method='POST')
def create_user(self):
"""μ μ¬μ©μ μμ±"""
try:
data = self.request.json_body
if not all(k in data for k in ('name', 'email')):
return HTTPBadRequest(json={'error': 'νμ νλκ° λλ½λμμ΅λλ€'})
# κ°λ¨ν ID μμ± (μ€μ λ‘λ UUID λ± μ¬μ©)
new_id = str(len(self.user_db) + 1)
self.user_db[new_id] = {
'id': new_id,
'name': data['name'],
'email': data['email']
}
return {'status': 'created', 'user': self.user_db[new_id]}
except Exception as e:
return HTTPBadRequest(json={'error': str(e)})
# ν¨μ κΈ°λ° λ·° (ν¨ν΄ λ§€μΉ μ¬μ©)
@view_config(route_name='user', request_method='GET', renderer='json')
def get_user(request):
"""νΉμ μ¬μ©μ μ 보 μ‘°ν"""
user_id = request.matchdict['id']
user_db = {
'1': {'id': '1', 'name': 'User 1', 'email': '[email protected]'},
'2': {'id': '2', 'name': 'User 2', 'email': '[email protected]'}
}
if user_id not in user_db:
raise HTTPNotFound(json={'error': f'User {user_id} not found'})
return {'user': user_db[user_id]}
# λΌμ°νΈ μ€μ μ μν ν¨μ
def includeme(config):
"""λΌμ°νΈ μ€μ μ μ€μννλ ν¨μ"""
# κΈ°λ³Έ λΌμ°νΈ
config.add_route('home', '/')
config.add_view(lambda r: Response('Welcome to Pyramid!'), route_name='home')
# RESTful API λΌμ°νΈ
config.add_route('users', '/api/users')
config.add_route('user', '/api/users/{id}')
# μ€μΊνμ¬ λ°μ½λ μ΄ν°λ‘ μ μλ λ·° λ±λ‘
config.scan('.')
β
νΉμ§:
- ν΄λμ€ κΈ°λ° λ·° μ§μ
- ν¨μ κΈ°λ° λ·° μ§μ
- URL ν¨ν΄ λ§€μΉ (κ²½λ‘ λ³μ)
- λ°μ½λ μ΄ν°λ₯Ό ν΅ν λ·° μ€μ
- λ€μν HTTP λ©μλ μ²λ¦¬
- JSON λ° λ€μν λ λλ¬ μ§μ
- HTTP μμΈ μ²λ¦¬ λ©μ»€λμ¦
- μ€μ²© λΌμ°ν ꡬ쑰 μ§μ
Pyramidλ SQLAlchemyμ κ°μ ORM λΌμ΄λΈλ¬λ¦¬μ μ½κ² ν΅ν©λμ΄ λ°μ΄ν°λ² μ΄μ€ μμ
μ ν¨μ¨μ μΌλ‘ μ²λ¦¬ν μ μλ€.
from sqlalchemy import create_engine, Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, scoped_session
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config
from datetime import datetime
import transaction
# SQLAlchemy λͺ¨λΈ μ μ
Base = declarative_base()
class User(Base):
"""μ¬μ©μ λͺ¨λΈ"""
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(50), unique=True, nullable=False)
email = Column(String(100), unique=True, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
posts = relationship('Post', back_populates='author', cascade='all, delete-orphan')
def __repr__(self):
return f"<User(username='{self.username}', email='{self.email}')>"
def to_dict(self):
return {
'id': self.id,
'username': self.username,
'email': self.email,
'created_at': self.created_at.isoformat(),
'post_count': len(self.posts)
}
class Post(Base):
"""κ²μλ¬Ό λͺ¨λΈ"""
__tablename__ = 'posts'
id = Column(Integer, primary_key=True)
title = Column(String(100), nullable=False)
content = Column(String, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
author = relationship('User', back_populates='posts')
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'content': self.content,
'created_at': self.created_at.isoformat(),
'user_id': self.user_id,
'author_username': self.author.username
}
# λ°μ΄ν°λ² μ΄μ€ μΈμ
κ΄λ¦¬
def get_db_session(request):
"""μμ² κ°μ²΄μ λ°μ΄ν°λ² μ΄μ€ μΈμ
μ μΆκ°νλ ν©ν 리 ν¨μ"""
session = request.registry.dbmaker()
def cleanup(request):
if request.exception is not None:
transaction.abort()
session.rollback()
else:
try:
transaction.commit()
except:
transaction.abort()
session.rollback()
raise
session.close()
request.add_finished_callback(cleanup)
return session
# λ°μ΄ν°λ² μ΄μ€ μ΄κΈ°ν ν¨μ
def initialize_db(settings):
"""λ°μ΄ν°λ² μ΄μ€ μ΄κΈ°ν λ° μΈμ
ν©ν 리 μμ±"""
engine = create_engine(settings['sqlalchemy.url'])
Base.metadata.create_all(engine)
session_factory = sessionmaker(bind=engine)
return scoped_session(session_factory)
# λ·° ν¨μ μμ
@view_config(route_name='users', request_method='POST', renderer='json')
def create_user(request):
"""μ μ¬μ©μ μμ± API"""
session = request.db
try:
user_data = request.json_body
# μ¬μ©μ μμ±
user = User(
username=user_data['username'],
email=user_data['email']
)
session.add(user)
session.flush() # ID μμ±μ μν΄ νλ¬μ
return user.to_dict()
except Exception as e:
session.rollback()
return {'error': str(e)}
@view_config(route_name='user_posts', request_method='POST', renderer='json')
def create_post(request):
"""μ¬μ©μμ μ κ²μλ¬Ό μμ± API"""
session = request.db
user_id = int(request.matchdict['user_id'])
try:
# μ¬μ©μ μ‘΄μ¬ μ¬λΆ νμΈ
user = session.query(User).filter_by(id=user_id).first()
if not user:
return {'error': 'User not found'}
post_data = request.json_body
post = Post(
title=post_data['title'],
content=post_data['content'],
user_id=user_id
)
session.add(post)
session.flush()
return post.to_dict()
except Exception as e:
session.rollback()
return {'error': str(e)}
# Pyramid μ ν리μΌμ΄μ
μ€μ
def main(global_config, **settings):
"""WSGI μ ν리μΌμ΄μ
ν©ν 리"""
config = Configurator(settings=settings)
# λ°μ΄ν°λ² μ΄μ€ μ΄κΈ°ν
dbmaker = initialize_db(settings)
config.registry.dbmaker = dbmaker
config.add_request_method(get_db_session, 'db', reify=True)
# λΌμ°νΈ μ€μ
config.add_route('users', '/api/users')
config.add_route('user', '/api/users/{id}')
config.add_route('user_posts', '/api/users/{user_id}/posts')
config.scan()
return config.make_wsgi_app()
β
νΉμ§:
- SQLAlchemy ORM ν΅ν©
- νΈλμμ κ΄λ¦¬
- μΈμ λΌμ΄νμ¬μ΄ν΄ κ΄λ¦¬
- λͺ¨λΈ μ μ λ° κ΄κ³ μ€μ
- μμ²λ³ λ°μ΄ν°λ² μ΄μ€ μΈμ
- μμΈ μ²λ¦¬μ λ‘€λ°± λ©μ»€λμ¦
- JSON μλ΅ μ§λ ¬ν
- λ€μν λ°μ΄ν°λ² μ΄μ€ μ§μ
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.security import (
Allow,
Everyone,
Authenticated,
ALL_PERMISSIONS
)
class RootFactory(object):
__acl__ = [
(Allow, Everyone, 'view'),
(Allow, Authenticated, 'create'),
(Allow, 'admin', ALL_PERMISSIONS)
]
def __init__(self, request):
pass
def groupfinder(userid, request):
user = request.db.query(User).filter_by(id=userid).first()
if user:
return ['group:' + g.name for g in user.groups]
@view_config(route_name='secure', permission='view')
def secure_view(request):
return Response('This is a secure view')
β
νΉμ§:
- μΈμ¦ μ μ±
- κΆν κ΄λ¦¬
- ACL μ€μ
- μ μ°ν 보μ λͺ¨λΈ
- μν κΈ°λ° μ κ·Ό μ μ΄
- μΈμ κ΄λ¦¬ μ΅μ
- 보μ μ μ± ν΅ν©
Pyramidλ λ€μν ν
νλ¦Ώ μμ§μ μ§μνκ³ μ μ νμΌ μ 곡 κΈ°λ₯μ ν΅ν΄ νλ‘ νΈμλ νλ μμν¬μ μ½κ² ν΅ν©λλ€.
from pyramid.view import view_config
from pyramid.renderers import render_to_response
import json
# Jinja2 ν
νλ¦Ώ μ¬μ© μμ
@view_config(route_name='home', renderer='templates/home.jinja2')
def home_view(request):
"""Jinja2 ν
νλ¦Ώμ μ¬μ©νλ ν νμ΄μ§ λ·°"""
return {
'title': 'Pyramid μ ν리μΌμ΄μ
',
'message': 'νμν©λλ€!',
'features': [
'μ μ°ν λΌμ°ν
',
'κ°λ ₯ν 보μ λͺ¨λΈ',
'λ°μ΄ν°λ² μ΄μ€ ν΅ν©',
'λ€μν ν
νλ¦Ώ μ§μ'
]
}
# Chameleon ν
νλ¦Ώ μ¬μ© μμ
@view_config(route_name='dashboard', renderer='templates/dashboard.pt')
def dashboard_view(request):
"""Chameleon ν
νλ¦Ώμ μ¬μ©νλ λμ보λ λ·°"""
# λ°μ΄ν°λ² μ΄μ€μμ ν΅κ³ λ°μ΄ν° λ‘λ (μμ)
stats = {
'users': 1250,
'posts': 5432,
'comments': 18345,
'active_users': 328
}
# μ΅κ·Ό νλ λ°μ΄ν° (μμ)
recent_activities = [
{'user': 'user1', 'action': 'κ²μλ¬Ό μμ±', 'time': '5λΆ μ '},
{'user': 'user2', 'action': 'λκΈ μμ±', 'time': '10λΆ μ '},
{'user': 'user3', 'action': 'λ‘κ·ΈμΈ', 'time': '15λΆ μ '}
]
return {
'title': 'κ΄λ¦¬μ λμ보λ',
'stats': stats,
'activities': recent_activities
}
# APIμ νλ‘ νΈμλ ν΅ν©
@view_config(route_name='app', renderer='templates/app.jinja2')
def spa_view(request):
"""SPA(Single Page Application) νλ‘ νΈμλλ₯Ό μν μ§μ
μ """
# μ΄κΈ° μν λ°μ΄ν° μ€λΉ
initial_state = {
'user': request.user.to_dict() if hasattr(request, 'user') else None,
'config': {
'apiUrl': request.route_url('api_root'),
'staticUrl': request.static_url('myapp:static/'),
'debug': request.registry.settings.get('debug', False)
}
}
return {
'title': 'Pyramid SPA',
'initial_state': json.dumps(initial_state)
}
# μ μ νμΌ μ€μ
def configure_static(config):
"""μ μ νμΌ μ€μ """
# κΈ°λ³Έ μ μ νμΌ κ²½λ‘
config.add_static_view('static', 'static', cache_max_age=3600)
# λΉλλ νλ‘ νΈμλ μ μ
(μ: React, Vue λ±)
config.add_static_view('assets', 'assets', cache_max_age=3600)
# νλΉμ½ μ€μ
config.add_route('favicon', '/favicon.ico')
config.add_view(
lambda r: FileResponse('static/favicon.ico', request=r),
route_name='favicon'
)
# ν
νλ¦Ώ μ€μ
def configure_templates(config):
"""ν
νλ¦Ώ μμ§ μ€μ """
# Jinja2 μ€μ
config.include('pyramid_jinja2')
config.add_jinja2_renderer('.html')
config.add_jinja2_search_path('templates', name='.html')
# Chameleon μ€μ
config.include('pyramid_chameleon')
# κ³΅ν΅ ν
νλ¦Ώ λ³μ
config.add_renderer_globals_subscriber(add_renderer_globals)
def add_renderer_globals(event):
"""λͺ¨λ ν
νλ¦Ώμ μ μ λ³μ μΆκ°"""
request = event.get('request')
if request is None:
return
event['site_name'] = 'Pyramid μ ν리μΌμ΄μ
'
event['current_year'] = datetime.now().year
event['debug'] = request.registry.settings.get('debug', False)
# μΈμ¦λ μ¬μ©μ μ 보
if hasattr(request, 'user'):
event['user'] = request.user
β
νΉμ§:
- λ€μν ν νλ¦Ώ μμ§ μ§μ (Jinja2, Chameleon λ±)
- μ μ νμΌ μ 곡 λ° μΊμ±
- SPA νλ μμν¬μ ν΅ν© (React, Vue, Angular λ±)
- κ΅μ ν λ° μ§μν μ§μ
- μμ° λ²λ€λ§ ν΅ν©
- ν νλ¦Ώ νμ₯ λ° μμ
- ν νλ¦Ώ ν¬νΌ ν¨μ
- μ μ ν νλ¦Ώ λ³μ
β
λͺ¨λ² μ¬λ‘:
- ν¨ν€μ§ ꡬ쑰ν: νμ₯μ±μ μν λͺ¨λμ μ€κ³ μ μ©
- λΌμ°ν μ΅μ ν: 체κ³μ μΈ URL ν¨ν΄ μ€κ³λ‘ μ μ§λ³΄μμ± ν₯μ
- 보μ κ°ν: CSRF 보νΈ, XSS λ°©μ§, μμ ν μΈμ¦ ꡬν
- λ°μ΄ν°λ² μ΄μ€ μ€κ³: κ΄κ³ν λͺ¨λΈ μ€κ³μ λ§μ΄κ·Έλ μ΄μ μ λ΅ μ립
- μΊμ± μ λ΅: μλ² λΆν κ°μμ μλ΅ μκ° κ°μ μ μν μΊμ± μ μ©
- λ‘κΉ κ΅¬ν: ꡬ쑰νλ λ‘κΉ μΌλ‘ λ¬Έμ μ§λ¨ λ° λͺ¨λν°λ§ μ©μ΄ν
- ν μ€νΈ μλν: λ¨μ ν μ€νΈ, ν΅ν© ν μ€νΈ, κΈ°λ₯ ν μ€νΈ μμ±
- λ°°ν¬ μλν: CI/CD νμ΄νλΌμΈ κ΅¬μΆ λ° μ»¨ν μ΄λν μ μ©
- νμ₯μ± μ€κ³: νλ¬κ·ΈμΈ μν€ν μ² νμ© λ° μ΄λ²€νΈ μμ€ν λμ
- μ±λ₯ μ΅μ ν: νλ‘νμΌλ§ λ° λ³λͺ© νμ μλ³/ν΄κ²°
- λ¬Έμν: API λ¬Έμμ κ°λ°μ κ°μ΄λ μμ±