跳转到主要内容

超可靠、快速的ASGI+WSGI框架,用于大规模构建数据平面API。

项目描述

Build Status Falcon web framework docs codecov.io code style: blue

falcon web 框架

Falcon 是一个极简的 ASGI/WSGI 框架,用于构建关键任务 REST API 和微服务,重点在于可靠性、正确性和可扩展性。

在构建 HTTP API 方面,其他框架可能会拖累你,让你承受大量依赖和无关的抽象。Falcon 通过拥抱 HTTP 和 REST 架构风格,以干净的设计来直接解决问题。

Falcon 应用程序可以与任何 WSGIASGI 服务器一起工作,并在 CPython 3.5+ 和 PyPy 3.5+(ASGI 需要 3.6+)下运行得很好。

人们怎么说

“Falcon 是非常稳固且快速的。”

“我们已经使用 Falcon 来替换 [另一个框架],我们非常喜欢它的性能(快三倍)和代码库大小(原代码的一半)。”

“我非常喜欢 #falconframework!非常干净简单,我终于得到了我需要的速度和灵活性!”

“Falcon 看起来很棒。我为我的一个小服务器快速组装了一个测试,只花了 20 分钟,速度提高了 40%。”

“我感觉我终于在谈论 HTTP 了,中间没有其他东西。Falcon 好像就是后端的需求。”

“Falcon 的源代码非常好,我甚至更喜欢它而不是文档。基本上不会出错。”

“哪个框架集成了对 786 的支持?试试看?”

特性

Falcon 尽量做到最少,同时保持高效。

  • ASGI、WSGI 和 WebSocket 支持

  • 原生 asyncio 支持

  • 不依赖于魔法全局变量进行路由和状态管理

  • 具有向后兼容性的稳定接口

  • 通过集中的 RESTful 路由进行简单的 API 模型化

  • 高度优化的可扩展代码库

  • 通过请求和响应类轻松访问头和体

  • 通过中间件组件和钩子实现 DRY 请求处理

  • 严格遵守 RFC

  • 符合惯用 HTTP 错误响应

  • 简单的异常处理

  • 使用 WSGI/ASGI 辅助工具和模拟器进行快速测试

  • 支持 CPython 3.5+ 和 PyPy 3.5+

支持 Falcon 开发

Falcon 是否帮助您制作了出色的应用程序?今天通过一次性捐赠或成为赞助商来展示您的支持。赞助商将获得酷炫的装备,有机会向 Python 开发者推广他们的品牌,并获得优先支持。

谢谢!

Falcon 有何不同?

完美不是在再也没有可以添加的东西时才达到,而是在再也没有可以去除的东西时才达到。

- 安托万·德·圣埃克苏佩里

我们设计了Falcon以支持大规模微服务和响应式应用后端的需求。Falcon通过提供裸机性能、可靠性和灵活性,在任何需要的地方补充了更通用的Python Web框架。

可靠。我们竭尽全力避免引入破坏性变更,当我们确实需要引入时,这些变更将得到充分记录,并且仅通过主要版本号的增加进行引入(秉承SemVer的精神)。代码经过大量输入的严格测试,我们要求始终达到100%的覆盖率。Falcon没有使用标准库以外的依赖,这有助于最小化您的应用程序的攻击面,同时避免了传递性错误和破坏性变更。

可调试。Falcon避免了魔法。可以轻松地了解哪些输入导致了哪些输出。未处理的异常永远不会被封装或隐藏。如自动请求体解析等可能令人惊讶的行为都有良好的文档记录,并且默认禁用。最后,就框架本身而言,我们努力保持逻辑路径简单易懂。所有这些都使得推理代码和调试大规模部署中的边缘情况变得更加容易。

快速。相同的硬件,更多的请求。Falcon比其他流行的Python框架(如Django和Flask)处理请求的速度要快得多。为了额外的速度提升,Falcon在可用的情况下使用Cython进行编译,并且与PyPy配合得很好。考虑迁移到另一种编程语言?首先使用Falcon+PyPy进行基准测试!

灵活。Falcon将许多决策和实现细节留给了您,API开发者。这给了您很大的自由来定制和调整您的实现。这也帮助您更深入地了解您的应用程序,使它们更容易调整、调试和长期重构。Falcon的最简设计为Python社区成员提供了在Falcon附加组件和互补包上独立创新的空间。

谁在使用Falcon?

Falcon被世界各地的越来越多的组织使用,包括

  • 7ideas

  • Cronitor

  • EMC

  • Hurricane Electric

  • Leadpages

  • OpenStack

  • Rackspace

  • Shiftgig

  • tempfil.es

  • Opera Software

如果您正在将Falcon框架用于社区或商业项目,请考虑在我们的维基上添加您的信息,详情请见谁在使用Falcon?

社区

有许多Falcon附加组件、模板和互补包可供您在项目中使用。我们已经在Falcon维基上列出了一些这些,作为起点,但您也可以在PyPI上搜索其他资源。

Gitter上的Falcon社区是一个提问和分享想法的好地方。您可以在falconry/user找到我们。我们还有一个falconry/dev房间,用于讨论框架的设计和开发。

根据我们的行为准则,我们期望所有参与社区讨论的人都要专业行事,以身作则,鼓励建设性讨论。社区中的每个人都要为创造积极、建设性和富有成效的文化负责。

安装

PyPy

PyPy是运行您的Falcon应用程序最快的途径。PyPy3.5+从PyPy v5.10开始支持。

$ pip install falcon

或者,如果您想安装最新的beta版或发布候选版(如果有)

$ pip install --pre falcon

CPython

Falcon也完全支持CPython 3.5+。

最新稳定版本的Falcon可以直接从PyPI安装

$ pip install falcon

或者,如果您想安装最新的beta版或发布候选版(如果有)

$ pip install --pre falcon

为了提供额外的速度提升,Falcon 可以使用 Cython 进行编译。PyPI 上提供了几个常见平台的预编译二进制文件的 wheel 包。然而,如果您的平台没有可用的 wheel 包,您可以选择安装源代码分发版。安装过程将自动尝试为您当前的环境 cythonize Falcon,如果在 cythonization 步骤中遇到任何问题,将回退到正常的纯 Python 安装。

$ pip install --no-binary :all: falcon

如果您想验证 Cython 是否被调用,只需将详细标志 -v 传递给 pip,以便回显编译命令。

cythonization 步骤仅在使用 CPython Python 实现时才有效,因此使用 PyPy 安装时会跳过该步骤。如果您想跳过 Cython 编译步骤并直接安装纯 Python 版本,可以在安装前将环境变量 FALCON_DISABLE_CYTHON 设置为非空值。

$ FALCON_DISABLE_CYTHON=Y pip install -v --no-binary :all: falcon

请注意,需要 pip>=10 才能从源代码安装 Falcon。

在 OS X 上安装

编译 Cython 需要 Xcode 命令行工具。使用以下命令安装它们:

$ xcode-select --install

Clang 编译器将未识别的命令行选项视为错误,例如:

clang: error: unknown argument: '-mno-fused-madd' [-Wunused-command-line-argument-hard-error-in-future]

您可能会看到关于未使用函数的警告。您可以通过以下方式设置额外的 Clang C 编译器标志来解决这个问题:

$ export CFLAGS="-Qunused-arguments -Wno-unused-function"

依赖关系

Falcon 不需要安装任何其他包,尽管如果 Cython 已安装到环境中,它将按照上述方式用于优化框架。

WSGI 服务器

Falcon 支持 WSGI(或 ASGI;下面也会介绍)。为了服务 Falcon 应用程序,您需要一个 WSGI 服务器。Gunicorn 和 uWSGI 是其中一些较为流行的选择,但任何可以加载 WSGI 应用的服务器都可以。

$ pip install [gunicorn|uwsgi]

ASGI 服务器

为了服务 Falcon ASGI 应用,您需要一个 ASGI 服务器。Uvicorn 是一个流行的选择。

$ pip install uvicorn

源代码

Falcon 代码库位于 GitHub 上,这使得代码易于浏览、下载、分支等。欢迎提交 pull requests!此外,如果您喜欢这个项目,请记得给它点 star。 :)

一旦您已克隆了仓库或从 GitHub 下载了 tarball,您可以使用以下方法安装 Falcon:

$ cd falcon
$ pip install .

或者,如果您想编辑代码,首先将主仓库 fork 到您的桌面,然后运行以下命令,使用符号链接安装它,这样当您更改代码时,更改将自动对您的应用程序可用,而无需重新安装包

$ cd falcon
$ pip install -e .

您可以通过切换到克隆仓库的目录并运行 pytest 来手动测试对 Falcon 框架的更改

$ cd falcon
$ pip install -r requirements/tests
$ pytest tests

或者,要运行默认测试集

$ pip install tox && tox

有关可用环境的完整列表,请参阅 tox.ini 文件。

Read the Docs

Falcon 代码库中的 docstrings 非常详尽,我们建议在学习框架时运行一个 REPL,这样您就可以在有问题时查询各种模块和类。

在线文档可在以下位置查看: https://falcon.readthedocs.io

您可以使用以下方式在本地构建相同的文档:

$ pip install tox && tox -e docs

构建文档后,您可以通过在浏览器中打开以下索引页面来查看它们。在 OS X 上,操作非常简单:

$ open docs/_build/html/index.html

或者,在 Linux 上

$ xdg-open docs/_build/html/index.html

入门指南

以下是一个简单的示例,说明如何创建基于 Falcon 的 WSGI 应用程序(ASGI 版本在下面介绍)

# 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 = ('\nTwo 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()

您可以直接使用附带的 wsgiref 服务器运行上述示例

$ pip install falcon
$ python things.py

然后,在另一个终端中

$ curl localhost:8000/things

示例的 ASGI 版本类似

# 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 = ('\nTwo 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)

您可以使用uvicorn或其他ASGI服务器运行ASGI版本

$ pip install falcon uvicorn
$ uvicorn things_asgi:app

更复杂的示例(WSGI)

以下是一个更复杂的示例,它演示了读取头信息和查询参数,处理错误以及处理请求和响应体。请注意,此示例假定已安装requests包。

(有关等效的ASGI应用程序,请参阅:更复杂的示例(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.HTTPPayloadTooLarge(
                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()

同样,此代码使用wsgiref,但您也可以使用任何WSGI服务器(如uWSGI或Gunicorn)运行上述示例。例如

$ pip install requests gunicorn
$ gunicorn things:app

在Windows上,您可以通过WSL运行Gunicorn和uWSGI,或者您也可以尝试Waitress

$ pip install requests waitress
$ waitress-serve --port=8000 things:app

要测试此示例,请打开另一个终端并运行

$ http localhost:8000/1/things authorization:custom-token

您还可以通过框架附带的应用程序配置脚本falcon-inspect-app从CLI查看应用程序配置

falcon-inspect-app things_advanced:app

更复杂的示例(ASGI)

这是上述应用程序的ASGI版本。请注意,它使用httpx包代替requests

# 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.HTTPPayloadTooLarge(
                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')

您可以使用任何ASGI服务器(如uvicorn)运行ASGI版本

$ pip install falcon httpx uvicorn
$ uvicorn things_advanced_asgi:app

贡献

感谢您对我们项目的兴趣!我们欢迎所有技能水平的开发者的pull请求。要开始,只需在GitHub上将master分支fork到您的个人账户,然后将其克隆到您的开发环境中。

如果您想贡献,但没有具体想法,我们邀请您查看我们下一个里程碑下列出的问题。如果您看到您想工作的一个问题,请留下简短的评论,这样我们就不会出现重复的工作。提前感谢!

请注意,本项目的所有贡献者和维护者都受我们行为准则的约束。

在提交pull请求之前,请确保您已添加/更新了适当的测试(并确保所有现有测试在您的更改下仍然通过),并且您的编码风格遵循PEP 8,并且不会导致pyflakes报错。

提交消息应使用AngularJS约定格式化。

注释遵循Google的样式指南,并额外要求使用GitHub昵称和适当的前缀来前置内联注释

  • TODO(riker): 损坏报告!

  • NOTE(riker): 嗯,这确实是个好消息。

  • PERF(riker): 去最近的星基地旅行时间?

  • APPSEC(riker): 在所有信任中,都有背叛的可能性。

Falcon核心项目维护者包括

  • Kurt Griffiths,项目负责人(在GH、Gitter和Twitter上为kgriffs

  • John Vrbanac(在GH、Gitter和Twitter上为jmvrbanac

  • Vytautas Liuolia(在GH和Gitter上为vytas7,在Twitter上为vliuolia

  • Nick Zaccardi(在GH和Gitter上为nZac

  • Federico Caselli(在GH和Gitter上为CaselIT

如果您有任何问题或只是需要一点帮助开始,请不要犹豫,随时与我们联系。您可以在Gitter的falconry/dev找到我们。

另请参阅:CONTRIBUTING.md

项目详情


发布历史 发布通知 | RSS订阅

下载文件

下载您平台对应的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。

源代码分发

falcon-3.1.3.tar.gz (577.8 kB 查看哈希值)

上传时间: 源代码

构建分发

falcon-3.1.3-cp312-cp312-win_amd64.whl (1.9 MB 查看哈希值)

上传时间: CPython 3.12 Windows x86-64

falcon-3.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (11.2 MB 查看哈希值)

上传时间: CPython 3.12 manylinux: glibc 2.17+ x86-64

falcon-3.1.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl (11.5 MB 查看哈希值)

上传时间: CPython 3.12 manylinux: glibc 2.17+ s390x

falcon-3.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (11.0 MB 查看哈希值)

上传时间: CPython 3.12 manylinux: glibc 2.17+ ARM64

falcon-3.1.3-cp312-cp312-macosx_10_9_universal2.whl (3.9 MB 查看哈希值)

上传时间: CPython 3.12 macOS 10.9+ universal2 (ARM64, x86-64)

falcon-3.1.3-cp311-cp311-win_amd64.whl (2.0 MB 查看哈希值)

上传时间: CPython 3.11 Windows x86-64

falcon-3.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (11.0 MB 查看哈希值)

上传时间: CPython 3.11 manylinux: glibc 2.17+ x86-64

falcon-3.1.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl (11.4 MB 查看哈希值)

上传时间: CPython 3.11 manylinux: glibc 2.17+ s390x

falcon-3.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (10.9 MB 查看哈希值)

上传时间: CPython 3.11 manylinux: glibc 2.17+ ARM64

falcon-3.1.3-cp311-cp311-macosx_10_9_universal2.whl (4.0 MB 查看哈希值)

上传时间: CPython 3.11 macOS 10.9+ universal2 (ARM64, x86-64)

falcon-3.1.3-cp310-cp310-win_amd64.whl (2.0 MB 查看哈希值)

上传时间: CPython 3.10 Windows x86-64

falcon-3.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (10.0 MB 查看哈希值)

上传时间: CPython 3.10 manylinux: glibc 2.17+ x86-64

falcon-3.1.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl (10.4 MB 查看哈希值)

上传时间: CPython 3.10 manylinux: glibc 2.17+ s390x

falcon-3.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (9.9 MB 查看哈希值)

上传时间: CPython 3.10 manylinux: glibc 2.17+ ARM64

falcon-3.1.3-cp310-cp310-macosx_11_0_x86_64.whl (2.2 MB 查看哈希值)

上传时间: CPython 3.10 macOS 11.0+ x86-64

falcon-3.1.3-cp39-cp39-win_amd64.whl (2.0 MB 查看哈希值)

上传时间: CPython 3.9 Windows x86-64

falcon-3.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (10.0 MB 查看哈希值)

上传时间: CPython 3.9 manylinux: glibc 2.17+ x86-64

falcon-3.1.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl (10.4 MB 查看哈希值)

上传时间: CPython 3.9 manylinux: glibc 2.17+ s390x

falcon-3.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (9.9 MB 查看哈希值)

上传时间: CPython 3.9 manylinux: glibc 2.17+ ARM64

falcon-3.1.3-cp39-cp39-macosx_11_0_x86_64.whl (2.2 MB 查看哈希值)

上传时间: CPython 3.9 macOS 11.0+ x86-64

falcon-3.1.3-cp38-cp38-win_amd64.whl (2.0 MB 查看哈希值)

上传时间: CPython 3.8 Windows x86-64

falcon-3.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (10.3 MB 查看哈希值)

上传时间: CPython 3.8 manylinux: glibc 2.17+ x86-64

falcon-3.1.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl (10.7 MB 查看哈希值)

上传时间: CPython 3.8 manylinux: glibc 2.17+ s390x

falcon-3.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (10.1 MB 查看哈希值)

上传时间: CPython 3.8 manylinux: glibc 2.17+ ARM64

falcon-3.1.3-cp38-cp38-macosx_11_0_x86_64.whl (2.2 MB 查看哈希值)

上传时间: CPython 3.8 macOS 11.0+ x86-64

falcon-3.1.3-cp37-cp37m-win_amd64.whl (1.9 MB 查看哈希值)

上传时间: CPython 3.7m Windows x86-64

falcon-3.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (9.2 MB 查看哈希值)

上传时间: CPython 3.7m manylinux: glibc 2.17+ x86-64

falcon-3.1.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl (9.6 MB 查看哈希值)

上传时间: CPython 3.7m manylinux: glibc 2.17+ s390x

falcon-3.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (9.1 MB 查看哈希值)

上传时间: CPython 3.7m manylinux: glibc 2.17+ ARM64

falcon-3.1.3-cp37-cp37m-macosx_11_0_x86_64.whl (2.2 MB 查看哈希值)

上传时间: CPython 3.7m macOS 11.0+ x86-64

falcon-3.1.3-cp36-cp36m-win_amd64.whl (2.1 MB 查看哈希值)

上传时间: CPython 3.6m Windows x86-64

falcon-3.1.3-cp36-cp36m-macosx_10_14_x86_64.whl (2.1 MB 查看哈希值)

上传时间: CPython 3.6m macOS 10.14+ x86-64

支持者