Falcon é uma estrutura ASGI/WSGI minimalista para a construção de APIs REST e microsserviços de missão crítica, com foco na confiabilidade, correção e desempenho em escala.
Quando se trata de construir APIs HTTP, outras estruturas sobrecarregam você com toneladas de dependências e abstrações desnecessárias. Falcon vai direto ao ponto com um design limpo que abraça HTTP e o estilo arquitetônico REST.
Os aplicativos Falcon funcionam com qualquer servidor WSGI ou ASGI e funcionam como um campeão no CPython 3.8+ e PyPy 3.8+.
"Falcon é sólido como uma rocha e é rápido."
"Temos usado o Falcon como substituto de [outra estrutura] e simplesmente adoramos o desempenho (três vezes mais rápido) e o tamanho da base de código (facilmente metade do nosso código [original])."
"Estou adorando #falconframework! Super limpo e simples, finalmente tenho a velocidade e a flexibilidade que preciso!"
"O Falcon parece ótimo até agora. Fiz um teste rápido para um pequeno servidor meu e foi cerca de 40% mais rápido com apenas 20 minutos de trabalho."
"Sinto que estou falando apenas de HTTP, sem nada no meio. Falcon parece uma solicitação de back-end."
"O código-fonte do Falcon é tão bom que quase o prefiro à documentação. Basicamente, não pode estar errado."
"Que outra estrutura integrou suporte para 786 EXPERIMENTE AGORA?"
Falcon tenta fazer o mínimo possível, mantendo-se altamente eficaz.
asyncio
nativoFalcon ajudou você a criar um aplicativo incrível? Mostre seu apoio hoje mesmo com uma doação única ou tornando-se um patrono. Os apoiadores recebem equipamentos interessantes, uma oportunidade de promover sua marca para desenvolvedores Python e suporte priorizado.
Obrigado!
A perfeição é finalmente alcançada não quando não há mais nada a acrescentar, mas quando não há mais nada a retirar.
-Antoine de Saint-Exupéry
Projetamos o Falcon para dar suporte às necessidades exigentes de microsserviços em grande escala e back-ends de aplicativos responsivos. Falcon complementa estruturas web Python mais gerais, fornecendo desempenho, confiabilidade e flexibilidade bare-metal sempre que você precisar.
Confiável. Fazemos todo o possível para evitar a introdução de alterações significativas e, quando o fazemos, elas são totalmente documentadas e introduzidas apenas (no espírito do SemVer) com um incremento de versão principal. O código é rigorosamente testado com inúmeras entradas e exigimos 100% de cobertura em todos os momentos. O Falcon não tem dependências fora da biblioteca padrão, ajudando a minimizar a superfície de ataque do seu aplicativo e evitando bugs transitivos e alterações significativas.
Depurável. Falcon evita magia. É fácil saber quais entradas levam a quais saídas. Exceções não tratadas nunca são encapsuladas ou mascaradas. Comportamentos potencialmente surpreendentes, como a análise automática do corpo da solicitação, são bem documentados e desabilitados por padrão. Finalmente, quando se trata do framework em si, tomamos cuidado para manter os caminhos lógicos simples e compreensíveis. Tudo isso torna mais fácil raciocinar sobre o código e depurar casos extremos em implantações em grande escala.
Rápido. Mesmo hardware, mais solicitações. O Falcon atende solicitações significativamente mais rápido do que outros frameworks Python populares, como Django e Flask. Para aumentar a velocidade extra, o Falcon se compila com o Cython quando disponível e também funciona bem com o PyPy. Considerando mudar para outra linguagem de programação? Compare primeiro com Falcon + PyPy!
Flexível. Falcon deixa muitas decisões e detalhes de implementação para você, o desenvolvedor da API. Isso lhe dá muita liberdade para personalizar e ajustar sua implementação. Também ajuda você a entender seus aplicativos em um nível mais profundo, tornando-os mais fáceis de ajustar, depurar e refatorar no longo prazo. O design minimalista do Falcon oferece espaço para os membros da comunidade Python inovarem de forma independente em complementos e pacotes complementares do Falcon.
O Falcon é usado em todo o mundo por um número crescente de organizações, incluindo:
Se você estiver usando a estrutura Falcon para um projeto comunitário ou comercial, considere adicionar suas informações ao nosso wiki em Quem está usando o Falcon?
Vários complementos, modelos e pacotes complementares do Falcon estão disponíveis para uso em seus projetos. Listamos vários deles no wiki do Falcon como ponto de partida, mas você também pode pesquisar no PyPI para obter recursos adicionais.
A comunidade Falconry no Gitter é um ótimo lugar para fazer perguntas e compartilhar suas ideias. Você pode nos encontrar em falcoaria/usuário. Também temos uma sala de falcoaria/desenvolvimento para discutir o design e o desenvolvimento do próprio framework.
De acordo com o nosso Código de Conduta, esperamos que todos os que participam nas discussões da comunidade atuem de forma profissional e liderem pelo exemplo, incentivando discussões construtivas. Cada indivíduo da comunidade é responsável por criar uma cultura positiva, construtiva e produtiva.
PyPy é a maneira mais rápida de executar seu aplicativo Falcon. PyPy3.8+ é compatível com PyPy v7.3.7+.
$ pip install falcon
Ou, para instalar a versão beta ou release candidate mais recente, se houver:
$ pip install --pre falcon
Falcon também oferece suporte total ao CPython 3.8+.
A versão estável mais recente do Falcon pode ser instalada diretamente do PyPI:
$ pip install falcon
Ou, para instalar a versão beta ou release candidate mais recente, se houver:
$ pip install --pre falcon
Para fornecer um aumento extra de velocidade, o Falcon compila-se automaticamente com o Cython em qualquer instalador compatível com PEP 517.
Para sua conveniência, rodas contendo binários pré-compilados estão disponíveis no PyPI para a maioria das plataformas comuns. Mesmo que uma construção binária para a plataforma de sua escolha não esteja disponível, pip
escolherá uma roda Python pura. Você também pode citonizar o Falcon para o seu ambiente; consulte nossos documentos de instalação para obter mais informações sobre esta e outras opções avançadas.
Falcon não requer a instalação de nenhum outro pacote.
Falcon fala WSGI (ou ASGI; veja também abaixo). Para servir um aplicativo Falcon, você precisará de um servidor WSGI. Gunicorn e uWSGI são alguns dos mais populares que existem, mas qualquer coisa que possa carregar um aplicativo WSGI serve.
$ pip install [gunicorn | uwsgi]
Para servir um aplicativo Falcon ASGI, você precisará de um servidor ASGI. Uvicorn é uma escolha popular:
$ pip install uvicorn
O Falcon reside no GitHub, tornando o código fácil de navegar, baixar, bifurcar, etc. Solicitações pull são sempre bem-vindas! Além disso, lembre-se de marcar o projeto com estrela se isso te deixar feliz. :)
Depois de clonar o repositório ou baixar um tarball do GitHub, você pode instalar o Falcon assim:
$ cd falcon
$ pip install .
Ou, se você quiser editar o código, primeiro bifurque o repositório principal, clone o fork em sua área de trabalho e, em seguida, execute o seguinte para instalá-lo usando link simbólico, para que quando você alterar seu código, as alterações estejam automaticamente disponíveis para seu aplicativo sem precisar reinstalar o pacote:
$ cd falcon
$ FALCON_DISABLE_CYTHON=Y pip install -e .
Você pode testar manualmente as alterações na estrutura Falcon alternando para o diretório do repositório clonado e executando o pytest:
$ cd falcon
$ pip install -r requirements/tests
$ pytest tests
Ou, para executar o conjunto padrão de testes:
$ pip install tox && tox
Consulte também o arquivo tox.ini para obter uma lista completa de ambientes disponíveis.
As docstrings na base de código do Falcon são bastante extensas e recomendamos manter um REPL em execução enquanto aprende a estrutura para que você possa consultar os vários módulos e classes conforme tiver dúvidas.
Os documentos online estão disponíveis em: https://falcon.readthedocs.io
Você pode criar os mesmos documentos localmente da seguinte maneira:
$ pip install tox && tox -e docs
Depois que os documentos forem criados, você poderá visualizá-los abrindo a seguinte página de índice em seu navegador. No OS X é tão simples quanto:
$ abra docs/_build/html/index.html
Ou no Linux:
$ xdg-open docs/_build/html/index.html
Aqui está um exemplo simples e inventado que mostra como criar um aplicativo WSGI baseado em Falcon (a versão ASGI está incluída mais abaixo):
# 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 ()
Você pode executar o exemplo acima diretamente usando o servidor wsgiref incluído:
$ pip install falcon
$ python things.py
Então, em outro terminal:
$ curl localhost:8000/things
A versão ASGI do exemplo é semelhante:
# 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 )
Você pode executar a versão ASGI com uvicorn ou qualquer outro servidor ASGI:
$ pip install falcon uvicorn
$ uvicorn things_asgi:app
Aqui está um exemplo mais envolvente que demonstra a leitura de cabeçalhos e parâmetros de consulta, tratamento de erros e trabalho com corpos de solicitação e resposta. Observe que este exemplo pressupõe que o pacote requests foi instalado.
(Para o aplicativo ASGI equivalente, consulte: Um exemplo mais complexo (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 ()
Novamente, este código usa wsgiref, mas você também pode executar o exemplo acima usando qualquer servidor WSGI, como uWSGI ou Gunicorn. Por exemplo:
$ pip install requests gunicorn
$ gunicorn things:app
No Windows você pode executar Gunicorn e uWSGI via WSL, ou você pode tentar Waitress:
$ pip install requests waitress
$ waitress-serve --port=8000 things:app
Para testar este exemplo, abra outro terminal e execute:
$ http localhost:8000/1/things authorization:custom-token
Você também pode visualizar a configuração do aplicativo na CLI por meio do script falcon-inspect-app
que acompanha a estrutura:
falcon-inspect-app things_advanced:app
Aqui está a versão ASGI do aplicativo acima. Observe que ele usa o pacote httpx em vez de solicitações.
# 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' )
Você pode executar a versão ASGI com qualquer servidor ASGI, como o uvicorn:
$ pip install falcon httpx uvicorn
$ uvicorn things_advanced_asgi:app
Obrigado pelo seu interesse no projeto! Aceitamos solicitações pull de desenvolvedores de todos os níveis de habilidade. Para começar, basta bifurcar o branch master no GitHub para sua conta pessoal e depois clonar o fork em seu ambiente de desenvolvimento.
Se você gostaria de contribuir, mas ainda não tem algo em mente, convidamos você a dar uma olhada nas questões listadas em nosso próximo marco. Se você encontrar algum em que gostaria de trabalhar, deixe um comentário rápido para que não acabemos com esforços duplicados. Desde já, obrigado!
Observe que todos os contribuidores e mantenedores deste projeto estão sujeitos ao nosso Código de Conduta.
Antes de enviar uma solicitação pull, certifique-se de ter adicionado/atualizado os testes apropriados (e de que todos os testes existentes ainda sejam aprovados com suas alterações) e de que seu estilo de codificação siga o PEP 8 e não cause reclamações de pyflakes.
As mensagens de commit devem ser formatadas usando convenções AngularJS.
Os comentários seguem o guia de estilo do Google, com o requisito adicional de prefixar comentários in-line usando seu apelido do GitHub e um prefixo apropriado:
Os principais mantenedores do projeto Falcon são:
Não hesite em entrar em contato se tiver alguma dúvida ou apenas precisar de uma ajudinha para começar. Você pode nos encontrar em falcoaria/dev no Gitter.
Veja também: CONTRIBUTING.md
Copyright 2013-2024 de colaboradores individuais e corporativos, conforme observado nos arquivos de origem individuais.
Licenciado sob a Licença Apache, Versão 2.0 (a "Licença"); você não pode usar nenhuma parte da estrutura Falcon, exceto em conformidade com a Licença. Os colaboradores concordam em licenciar seu trabalho sob a mesma licença. Você pode obter uma cópia da Licença em http://www.apache.org/licenses/LICENSE-2.0
A menos que exigido pela lei aplicável ou acordado por escrito, o software distribuído sob a Licença é distribuído "COMO ESTÁ", SEM GARANTIAS OU CONDIÇÕES DE QUALQUER TIPO, expressas ou implícitas. Consulte a Licença para saber o idioma específico que rege as permissões e limitações da Licença.