Falcon es un marco ASGI/WSGI minimalista para crear microservicios y API REST de misión crítica, con un enfoque en la confiabilidad, la corrección y el rendimiento a escala.
Cuando se trata de crear API HTTP, otros marcos lo sobrecargan con toneladas de dependencias y abstracciones innecesarias. Falcon va al grano con un diseño limpio que adopta HTTP y el estilo arquitectónico REST.
Las aplicaciones Falcon funcionan con cualquier servidor WSGI o ASGI y se ejecutan como un campeón en CPython 3.8+ y PyPy 3.8+.
"Falcon es sólido como una roca y es rápido".
"Hemos estado usando Falcon como reemplazo de [otro marco] y simplemente nos encanta el rendimiento (tres veces más rápido) y el tamaño de la base del código (fácilmente la mitad de nuestro código [original]".
"¡Me encanta #falconframework! Súper limpio y simple, ¡finalmente tengo la velocidad y flexibilidad que necesito!"
"Falcon se ve genial hasta ahora. Hice una prueba rápida para un pequeño servidor mío y fui aproximadamente un 40 % más rápido con solo 20 minutos de trabajo".
"Siento que por fin estoy hablando de HTTP, sin nada en el medio. Falcon parece ser las solicitudes del backend".
"El código fuente de Falcon es tan bueno que casi lo prefiero a la documentación. Básicamente, no puede estar equivocado".
"¿Qué otro marco tiene soporte integrado para 786 PRUÉBELO AHORA?"
Falcon intenta hacer lo menos posible sin dejar de ser muy eficaz.
asyncio
¿Falcon te ha ayudado a crear una aplicación increíble? Muestre su apoyo hoy con una donación única o convirtiéndose en patrocinador. Los seguidores obtienen equipo genial, una oportunidad de promocionar su marca entre los desarrolladores de Python y soporte prioritario.
¡Gracias!
La perfección finalmente se alcanza no cuando ya no hay nada que añadir, sino cuando ya no hay nada que quitar.
- Antoine de Saint-Exupéry
Diseñamos Falcon para satisfacer las exigentes necesidades de microservicios a gran escala y backends de aplicaciones responsivos. Falcon complementa los marcos web Python más generales al brindar rendimiento, confiabilidad y flexibilidad básicos dondequiera que los necesite.
Confiable. Hacemos todo lo posible para evitar introducir cambios importantes y, cuando lo hacemos, están completamente documentados y solo se introducen (en el espíritu de SemVer) con un incremento importante de la versión. El código se prueba rigurosamente con numerosas entradas y requerimos una cobertura del 100 % en todo momento. Falcon no tiene dependencias fuera de la biblioteca estándar, lo que ayuda a minimizar la superficie de ataque de su aplicación y al mismo tiempo evita errores transitivos y cambios importantes.
Depurable. Falcon evita la magia. Es fácil saber qué entradas conducen a qué salidas. Las excepciones no controladas nunca se encapsulan ni se enmascaran. Los comportamientos potencialmente sorprendentes, como el análisis automático del cuerpo de la solicitud, están bien documentados y deshabilitados de forma predeterminada. Finalmente, cuando se trata del marco en sí, nos preocupamos de mantener las rutas lógicas simples y comprensibles. Todo esto hace que sea más fácil razonar sobre el código y depurar casos extremos en implementaciones a gran escala.
Rápido. Mismo hardware, más solicitudes. Falcon procesa solicitudes significativamente más rápido que otros marcos populares de Python como Django y Flask. Para aumentar la velocidad adicional, Falcon se compila con Cython cuando está disponible y también funciona bien con PyPy. ¿Está considerando cambiar a otro lenguaje de programación? ¡Compara con Falcon+PyPy primero!
Flexible. Falcon le deja muchas decisiones y detalles de implementación a usted, el desarrollador de API. Esto le brinda mucha libertad para personalizar y ajustar su implementación. También le ayuda a comprender sus aplicaciones a un nivel más profundo, haciéndolas más fáciles de ajustar, depurar y refactorizar a largo plazo. El diseño minimalista de Falcon proporciona espacio para que los miembros de la comunidad Python innoven de forma independiente en complementos y paquetes complementarios de Falcon.
Falcon es utilizado en todo el mundo por un número creciente de organizaciones, entre ellas:
Si está utilizando el marco Falcon para un proyecto comunitario o comercial, considere agregar su información a nuestra wiki en ¿Quién usa Falcon?
Hay varios complementos, plantillas y paquetes complementarios de Falcon disponibles para usar en sus proyectos. Hemos enumerado varios de estos en la wiki de Falcon como punto de partida, pero es posible que también desee buscar recursos adicionales en PyPI.
La comunidad Falconry en Gitter es un gran lugar para hacer preguntas y compartir ideas. Puedes encontrarnos en cetrería/usuario. También contamos con una sala de cetrería/desarrollo para discutir el diseño y desarrollo del marco en sí.
Según nuestro Código de conducta, esperamos que todos los que participan en debates comunitarios actúen de manera profesional y den ejemplo fomentando debates constructivos. Cada individuo de la comunidad es responsable de crear una cultura positiva, constructiva y productiva.
PyPy es la forma más rápida de ejecutar su aplicación Falcon. PyPy3.8+ es compatible a partir de PyPy v7.3.7+.
$ pip install falcon
O bien, para instalar la versión beta o candidata más reciente, si corresponde:
$ pip install --pre falcon
Falcon también es totalmente compatible con CPython 3.8+.
La última versión estable de Falcon se puede instalar directamente desde PyPI:
$ pip install falcon
O bien, para instalar la versión beta o candidata más reciente, si corresponde:
$ pip install --pre falcon
Para proporcionar un aumento de velocidad adicional, Falcon se compila automáticamente con Cython bajo cualquier instalador compatible con PEP 517.
Para su comodidad, PyPI dispone de ruedas que contienen archivos binarios precompilados para la mayoría de las plataformas comunes. Incluso si no hay disponible una compilación binaria para la plataforma de su elección, pip
elegirá una rueda pura de Python. También puedes citonizar a Falcon para tu entorno; consulte nuestros documentos de instalación para obtener más información sobre esta y otras opciones avanzadas.
Falcon no requiere la instalación de ningún otro paquete.
Falcon habla WSGI (o ASGI; ver también más abajo). Para poder servir una aplicación Falcon, necesitará un servidor WSGI. Gunicorn y uWSGI son algunos de los más populares que existen, pero cualquier cosa que pueda cargar una aplicación WSGI servirá.
$ pip install [gunicorn | uwsgi]
Para poder servir una aplicación Falcon ASGI, necesitará un servidor ASGI. Uvicorn es una opción popular:
$ pip install uvicorn
Falcon vive en GitHub, lo que hace que el código sea fácil de explorar, descargar, bifurcar, etc. ¡Las solicitudes de extracción siempre son bienvenidas! Además, recuerda destacar el proyecto si te hace feliz. :)
Una vez que haya clonado el repositorio o descargado un tarball de GitHub, puede instalar Falcon de esta manera:
$ cd falcon
$ pip install .
O, si desea editar el código, primero bifurque el repositorio principal, clone la bifurcación en su escritorio y luego ejecute lo siguiente para instalarlo mediante enlaces simbólicos, de modo que cuando cambie su código, los cambios estarán automáticamente disponibles para su aplicación sin tener que reinstalar el paquete:
$ cd falcon
$ FALCON_DISABLE_CYTHON=Y pip install -e .
Puede probar manualmente los cambios en el marco Falcon cambiando al directorio del repositorio clonado y luego ejecutando pytest:
$ cd falcon
$ pip install -r requirements/tests
$ pytest tests
O bien, para ejecutar el conjunto de pruebas predeterminado:
$ pip install tox && tox
Consulte también el archivo tox.ini para obtener una lista completa de los entornos disponibles.
Las cadenas de documentación en el código base de Falcon son bastante extensas y recomendamos mantener un REPL en ejecución mientras aprende el marco para que pueda consultar los distintos módulos y clases cuando tenga preguntas.
Los documentos en línea están disponibles en: https://falcon.readthedocs.io
Puede crear los mismos documentos localmente de la siguiente manera:
$ pip install tox && tox -e docs
Una vez que se hayan creado los documentos, puede verlos abriendo la siguiente página de índice en su navegador. En OS X es tan simple como:
$ abrir documentos/_build/html/index.html
O en Linux:
$ xdg-open docs/_build/html/index.html
Aquí hay un ejemplo simple y artificial que muestra cómo crear una aplicación WSGI basada en Falcon (la versión ASGI se incluye más abajo):
# examples/things.py
# Let's get this party started!
from wsgiref . simple_server import make_server
import falcon
# Falcon follows the REST architectural style, meaning (among
# other things) that you think in terms of resources and state
# transitions, which map to HTTP verbs.
class ThingsResource :
def on_get ( self , req , resp ):
"""Handles GET requests"""
resp . status = falcon . HTTP_200 # This is the default status
resp . content_type = falcon . MEDIA_TEXT # Default is JSON, so override
resp . text = ( ' n Two things awe me most, the starry sky '
'above me and the moral law within me. n '
' n '
' ~ Immanuel Kant n n ' )
# falcon.App instances are callable WSGI apps...
# in larger applications the app is created in a separate file
app = falcon . App ()
# Resources are represented by long-lived class instances
things = ThingsResource ()
# things will handle all requests to the '/things' URL path
app . add_route ( '/things' , things )
if __name__ == '__main__' :
with make_server ( '' , 8000 , app ) as httpd :
print ( 'Serving on port 8000...' )
# Serve until process is killed
httpd . serve_forever ()
Puede ejecutar el ejemplo anterior directamente utilizando el servidor wsgiref incluido:
$ pip install falcon
$ python things.py
Luego, en otra terminal:
$ curl localhost:8000/things
La versión ASGI del ejemplo es similar:
# examples/things_asgi.py
import falcon
import falcon . asgi
# Falcon follows the REST architectural style, meaning (among
# other things) that you think in terms of resources and state
# transitions, which map to HTTP verbs.
class ThingsResource :
async def on_get ( self , req , resp ):
"""Handles GET requests"""
resp . status = falcon . HTTP_200 # This is the default status
resp . content_type = falcon . MEDIA_TEXT # Default is JSON, so override
resp . text = ( ' n Two things awe me most, the starry sky '
'above me and the moral law within me. n '
' n '
' ~ Immanuel Kant n n ' )
# falcon.asgi.App instances are callable ASGI apps...
# in larger applications the app is created in a separate file
app = falcon . asgi . App ()
# Resources are represented by long-lived class instances
things = ThingsResource ()
# things will handle all requests to the '/things' URL path
app . add_route ( '/things' , things )
Puedes ejecutar la versión ASGI con uvicorn o cualquier otro servidor ASGI:
$ pip install falcon uvicorn
$ uvicorn things_asgi:app
A continuación se muestra un ejemplo más complicado que demuestra la lectura de encabezados y parámetros de consulta, el manejo de errores y el trabajo con cuerpos de solicitud y respuesta. Tenga en cuenta que este ejemplo supone que se ha instalado el paquete de solicitudes.
(Para ver la aplicación ASGI equivalente, consulte: Un ejemplo más complejo (ASGI)).
# examples/things_advanced.py
import json
import logging
import uuid
from wsgiref import simple_server
import falcon
import requests
class StorageEngine :
def get_things ( self , marker , limit ):
return [{ 'id' : str ( uuid . uuid4 ()), 'color' : 'green' }]
def add_thing ( self , thing ):
thing [ 'id' ] = str ( uuid . uuid4 ())
return thing
class StorageError ( Exception ):
@ staticmethod
def handle ( ex , req , resp , params ):
# TODO: Log the error, clean up, etc. before raising
raise falcon . HTTPInternalServerError ()
class SinkAdapter :
engines = {
'ddg' : 'https://duckduckgo.com' ,
'y' : 'https://search.yahoo.com/search' ,
}
def __call__ ( self , req , resp , engine ):
url = self . engines [ engine ]
params = { 'q' : req . get_param ( 'q' , True )}
result = requests . get ( url , params = params )
resp . status = str ( result . status_code ) + ' ' + result . reason
resp . content_type = result . headers [ 'content-type' ]
resp . text = result . text
class AuthMiddleware :
def process_request ( self , req , resp ):
token = req . get_header ( 'Authorization' )
account_id = req . get_header ( 'Account-ID' )
challenges = [ 'Token type="Fernet"' ]
if token is None :
description = ( 'Please provide an auth token '
'as part of the request.' )
raise falcon . HTTPUnauthorized ( title = 'Auth token required' ,
description = description ,
challenges = challenges ,
href = 'http://docs.example.com/auth' )
if not self . _token_is_valid ( token , account_id ):
description = ( 'The provided auth token is not valid. '
'Please request a new token and try again.' )
raise falcon . HTTPUnauthorized ( title = 'Authentication required' ,
description = description ,
challenges = challenges ,
href = 'http://docs.example.com/auth' )
def _token_is_valid ( self , token , account_id ):
return True # Suuuuuure it's valid...
class RequireJSON :
def process_request ( self , req , resp ):
if not req . client_accepts_json :
raise falcon . HTTPNotAcceptable (
description = 'This API only supports responses encoded as JSON.' ,
href = 'http://docs.examples.com/api/json' )
if req . method in ( 'POST' , 'PUT' ):
if 'application/json' not in req . content_type :
raise falcon . HTTPUnsupportedMediaType (
title = 'This API only supports requests encoded as JSON.' ,
href = 'http://docs.examples.com/api/json' )
class JSONTranslator :
# NOTE: Normally you would simply use req.media and resp.media for
# this particular use case; this example serves only to illustrate
# what is possible.
def process_request ( self , req , resp ):
# req.stream corresponds to the WSGI wsgi.input environ variable,
# and allows you to read bytes from the request body.
#
# See also: PEP 3333
if req . content_length in ( None , 0 ):
# Nothing to do
return
body = req . stream . read ()
if not body :
raise falcon . HTTPBadRequest ( title = 'Empty request body' ,
description = 'A valid JSON document is required.' )
try :
req . context . doc = json . loads ( body . decode ( 'utf-8' ))
except ( ValueError , UnicodeDecodeError ):
description = ( 'Could not decode the request body. The '
'JSON was incorrect or not encoded as '
'UTF-8.' )
raise falcon . HTTPBadRequest ( title = 'Malformed JSON' ,
description = description )
def process_response ( self , req , resp , resource , req_succeeded ):
if not hasattr ( resp . context , 'result' ):
return
resp . text = json . dumps ( resp . context . result )
def max_body ( limit ):
def hook ( req , resp , resource , params ):
length = req . content_length
if length is not None and length > limit :
msg = ( 'The size of the request is too large. The body must not '
'exceed ' + str ( limit ) + ' bytes in length.' )
raise falcon . HTTPContentTooLarge (
title = 'Request body is too large' , description = msg )
return hook
class ThingsResource :
def __init__ ( self , db ):
self . db = db
self . logger = logging . getLogger ( 'thingsapp.' + __name__ )
def on_get ( self , req , resp , user_id ):
marker = req . get_param ( 'marker' ) or ''
limit = req . get_param_as_int ( 'limit' ) or 50
try :
result = self . db . get_things ( marker , limit )
except Exception as ex :
self . logger . error ( ex )
description = ( 'Aliens have attacked our base! We will '
'be back as soon as we fight them off. '
'We appreciate your patience.' )
raise falcon . HTTPServiceUnavailable (
title = 'Service Outage' ,
description = description ,
retry_after = 30 )
# NOTE: Normally you would use resp.media for this sort of thing;
# this example serves only to demonstrate how the context can be
# used to pass arbitrary values between middleware components,
# hooks, and resources.
resp . context . result = result
resp . set_header ( 'Powered-By' , 'Falcon' )
resp . status = falcon . HTTP_200
@ falcon . before ( max_body ( 64 * 1024 ))
def on_post ( self , req , resp , user_id ):
try :
doc = req . context . doc
except AttributeError :
raise falcon . HTTPBadRequest (
title = 'Missing thing' ,
description = 'A thing must be submitted in the request body.' )
proper_thing = self . db . add_thing ( doc )
resp . status = falcon . HTTP_201
resp . location = '/%s/things/%s' % ( user_id , proper_thing [ 'id' ])
# Configure your WSGI server to load "things.app" (app is a WSGI callable)
app = falcon . App ( middleware = [
AuthMiddleware (),
RequireJSON (),
JSONTranslator (),
])
db = StorageEngine ()
things = ThingsResource ( db )
app . add_route ( '/{user_id}/things' , things )
# If a responder ever raises an instance of StorageError, pass control to
# the given handler.
app . add_error_handler ( StorageError , StorageError . handle )
# Proxy some things to another service; this example shows how you might
# send parts of an API off to a legacy system that hasn't been upgraded
# yet, or perhaps is a single cluster that all data centers have to share.
sink = SinkAdapter ()
app . add_sink ( sink , r'/search/(?P<engine>ddg|y)Z' )
# Useful for debugging problems in your API; works with pdb.set_trace(). You
# can also use Gunicorn to host your app. Gunicorn can be configured to
# auto-restart workers when it detects a code change, and it also works
# with pdb.
if __name__ == '__main__' :
httpd = simple_server . make_server ( '127.0.0.1' , 8000 , app )
httpd . serve_forever ()
Nuevamente, este código usa wsgiref, pero también puedes ejecutar el ejemplo anterior usando cualquier servidor WSGI, como uWSGI o Gunicorn. Por ejemplo:
$ pip install requests gunicorn
$ gunicorn things:app
En Windows puedes ejecutar Gunicorn y uWSGI a través de WSL, o puedes probar Waitress:
$ pip install requests waitress
$ waitress-serve --port=8000 things:app
Para probar este ejemplo, abra otra terminal y ejecute:
$ http localhost:8000/1/things authorization:custom-token
También puede ver la configuración de la aplicación desde la CLI a través del script falcon-inspect-app
que se incluye con el marco:
falcon-inspect-app things_advanced:app
Aquí está la versión ASGI de la aplicación desde arriba. Tenga en cuenta que utiliza el paquete httpx en lugar de solicitudes.
# examples/things_advanced_asgi.py
import json
import logging
import uuid
import falcon
import falcon . asgi
import httpx
class StorageEngine :
async def get_things ( self , marker , limit ):
return [{ 'id' : str ( uuid . uuid4 ()), 'color' : 'green' }]
async def add_thing ( self , thing ):
thing [ 'id' ] = str ( uuid . uuid4 ())
return thing
class StorageError ( Exception ):
@ staticmethod
async def handle ( ex , req , resp , params ):
# TODO: Log the error, clean up, etc. before raising
raise falcon . HTTPInternalServerError ()
class SinkAdapter :
engines = {
'ddg' : 'https://duckduckgo.com' ,
'y' : 'https://search.yahoo.com/search' ,
}
async def __call__ ( self , req , resp , engine ):
url = self . engines [ engine ]
params = { 'q' : req . get_param ( 'q' , True )}
async with httpx . AsyncClient () as client :
result = await client . get ( url , params = params )
resp . status = result . status_code
resp . content_type = result . headers [ 'content-type' ]
resp . text = result . text
class AuthMiddleware :
async def process_request ( self , req , resp ):
token = req . get_header ( 'Authorization' )
account_id = req . get_header ( 'Account-ID' )
challenges = [ 'Token type="Fernet"' ]
if token is None :
description = ( 'Please provide an auth token '
'as part of the request.' )
raise falcon . HTTPUnauthorized ( title = 'Auth token required' ,
description = description ,
challenges = challenges ,
href = 'http://docs.example.com/auth' )
if not self . _token_is_valid ( token , account_id ):
description = ( 'The provided auth token is not valid. '
'Please request a new token and try again.' )
raise falcon . HTTPUnauthorized ( title = 'Authentication required' ,
description = description ,
challenges = challenges ,
href = 'http://docs.example.com/auth' )
def _token_is_valid ( self , token , account_id ):
return True # Suuuuuure it's valid...
class RequireJSON :
async def process_request ( self , req , resp ):
if not req . client_accepts_json :
raise falcon . HTTPNotAcceptable (
description = 'This API only supports responses encoded as JSON.' ,
href = 'http://docs.examples.com/api/json' )
if req . method in ( 'POST' , 'PUT' ):
if 'application/json' not in req . content_type :
raise falcon . HTTPUnsupportedMediaType (
description = 'This API only supports requests encoded as JSON.' ,
href = 'http://docs.examples.com/api/json' )
class JSONTranslator :
# NOTE: Normally you would simply use req.get_media() and resp.media for
# this particular use case; this example serves only to illustrate
# what is possible.
async def process_request ( self , req , resp ):
# NOTE: Test explicitly for 0, since this property could be None in
# the case that the Content-Length header is missing (in which case we
# can't know if there is a body without actually attempting to read
# it from the request stream.)
if req . content_length == 0 :
# Nothing to do
return
body = await req . stream . read ()
if not body :
raise falcon . HTTPBadRequest ( title = 'Empty request body' ,
description = 'A valid JSON document is required.' )
try :
req . context . doc = json . loads ( body . decode ( 'utf-8' ))
except ( ValueError , UnicodeDecodeError ):
description = ( 'Could not decode the request body. The '
'JSON was incorrect or not encoded as '
'UTF-8.' )
raise falcon . HTTPBadRequest ( title = 'Malformed JSON' ,
description = description )
async def process_response ( self , req , resp , resource , req_succeeded ):
if not hasattr ( resp . context , 'result' ):
return
resp . text = json . dumps ( resp . context . result )
def max_body ( limit ):
async def hook ( req , resp , resource , params ):
length = req . content_length
if length is not None and length > limit :
msg = ( 'The size of the request is too large. The body must not '
'exceed ' + str ( limit ) + ' bytes in length.' )
raise falcon . HTTPContentTooLarge (
title = 'Request body is too large' , description = msg )
return hook
class ThingsResource :
def __init__ ( self , db ):
self . db = db
self . logger = logging . getLogger ( 'thingsapp.' + __name__ )
async def on_get ( self , req , resp , user_id ):
marker = req . get_param ( 'marker' ) or ''
limit = req . get_param_as_int ( 'limit' ) or 50
try :
result = await self . db . get_things ( marker , limit )
except Exception as ex :
self . logger . error ( ex )
description = ( 'Aliens have attacked our base! We will '
'be back as soon as we fight them off. '
'We appreciate your patience.' )
raise falcon . HTTPServiceUnavailable (
title = 'Service Outage' ,
description = description ,
retry_after = 30 )
# NOTE: Normally you would use resp.media for this sort of thing;
# this example serves only to demonstrate how the context can be
# used to pass arbitrary values between middleware components,
# hooks, and resources.
resp . context . result = result
resp . set_header ( 'Powered-By' , 'Falcon' )
resp . status = falcon . HTTP_200
@ falcon . before ( max_body ( 64 * 1024 ))
async def on_post ( self , req , resp , user_id ):
try :
doc = req . context . doc
except AttributeError :
raise falcon . HTTPBadRequest (
title = 'Missing thing' ,
description = 'A thing must be submitted in the request body.' )
proper_thing = await self . db . add_thing ( doc )
resp . status = falcon . HTTP_201
resp . location = '/%s/things/%s' % ( user_id , proper_thing [ 'id' ])
# The app instance is an ASGI callable
app = falcon . asgi . App ( middleware = [
# AuthMiddleware(),
RequireJSON (),
JSONTranslator (),
])
db = StorageEngine ()
things = ThingsResource ( db )
app . add_route ( '/{user_id}/things' , things )
# If a responder ever raises an instance of StorageError, pass control to
# the given handler.
app . add_error_handler ( StorageError , StorageError . handle )
# Proxy some things to another service; this example shows how you might
# send parts of an API off to a legacy system that hasn't been upgraded
# yet, or perhaps is a single cluster that all data centers have to share.
sink = SinkAdapter ()
app . add_sink ( sink , r'/search/(?P<engine>ddg|y)Z' )
Puedes ejecutar la versión ASGI con cualquier servidor ASGI, como uvicorn:
$ pip install falcon httpx uvicorn
$ uvicorn things_advanced_asgi:app
¡Gracias por tu interés en el proyecto! Damos la bienvenida a las solicitudes de extracción de desarrolladores de todos los niveles. Para comenzar, simplemente bifurque la rama maestra en GitHub en su cuenta personal y luego clone la bifurcación en su entorno de desarrollo.
Si desea contribuir pero aún no tiene algo en mente, lo invitamos a echar un vistazo a los temas enumerados en nuestro próximo hito. Si ve uno en el que le gustaría trabajar, deje un comentario rápido para que no terminemos con esfuerzos duplicados. ¡Gracias de antemano!
Tenga en cuenta que todos los contribuyentes y mantenedores de este proyecto están sujetos a nuestro Código de conducta.
Antes de enviar una solicitud de extracción, asegúrese de haber agregado/actualizado las pruebas apropiadas (y de que todas las pruebas existentes aún se aprueben con sus cambios) y de que su estilo de codificación siga PEP 8 y no provoque quejas de pyflakes.
Los mensajes de confirmación deben formatearse utilizando las convenciones de AngularJS.
Los comentarios siguen la guía de estilo de Google, con el requisito adicional de anteponer los comentarios en línea usando su nick de GitHub y un prefijo apropiado:
Los mantenedores principales del proyecto Falcon son:
No dude en comunicarse si tiene alguna pregunta o simplemente necesita un poco de ayuda para comenzar. Puedes encontrarnos en cetrería/dev en Gitter.
Ver también: CONTRIBUTING.md
Copyright 2013-2024 de contribuyentes individuales y corporativos como se indica en los archivos fuente individuales.
Licenciado bajo la Licencia Apache, Versión 2.0 (la "Licencia"); no puede utilizar ninguna parte del marco Falcon excepto de conformidad con la Licencia. Los contribuyentes aceptan licenciar su trabajo bajo la misma Licencia. Puede obtener una copia de la Licencia en http://www.apache.org/licenses/LICENSE-2.0
A menos que lo exija la ley aplicable o se acuerde por escrito, el software distribuido bajo la Licencia se distribuye "TAL CUAL", SIN GARANTÍAS NI CONDICIONES DE NINGÚN TIPO, ya sean expresas o implícitas. Consulte la Licencia para conocer el idioma específico que rige los permisos y limitaciones de la Licencia.