pytest `AssertionError: отображение функции отображения перезаписывает существующую функцию конечной точки:` flask -прерывно при регистрации чертежа - PullRequest
0 голосов
/ 05 марта 2020

Проблема заключается в следующем, я создал фиктивный пример. Где структура папок:

.
├── api_bp
│   └── __init__.py
├── app.py
├── pytest.ini
└── tests
    ├── conftest.py
    ├── __init__.py
    ├── test_todo1.py
    └── test_todo2.py

Код в папке api_bp внутри __init__.py:

# __init__.py

from flask import Blueprint

api_bp = Blueprint('api', __name__)

Flask приложение:

# app.py

from flask import Flask, Blueprint
from flask_restful import Api, Resource


class TodoItem(Resource):
    def get(self, id):
        return {'task': 'Say "Hello, World!"'}


def create_app():
    """Initialize the app. """
    app = Flask(__name__)
    from api_bp import api_bp
    # api_bp = Blueprint('api', __name__)
    api = Api(api_bp)

    api.add_resource(TodoItem, '/todos/<int:id>')
    app.register_blueprint(api_bp, url_prefix='/api')

    return app


if __name__ == '__main__':
    app = create_app()
    app.run(debug=True)

Для целей тестирования у меня есть клиентское устройство и два теста (которые я намеренно поместил в отдельные модули) для разных задач:

# conftest.py

import pytest

from app_factory import create_app

@pytest.fixture(scope='module')
def client():
    flask_app = create_app()

    testing_client = flask_app.test_client()

    context = flask_app.app_context()
    context.push()

    yield testing_client

    context.pop()
# test_todo1.py

import pytest

def test_todo2(client):
    """Test"""
    response = client.get('/api/todos/1')
    print(response)
    assert response.status_code == 200
# test_todo2.py

import pytest

def test_todo2(client):
    """Test"""
    response = client.get('/api/todos/2')
    print(response)
    assert response.status_code == 200

Поэтому, когда я запускаю $ pytest -v, чтобы проверить его Я получаю следующую ошибку:

AssertionError: View function mapping is overwriting an existing endpoint function: api.todoitem

Это происходит из-за регистрации чертежа. И я хотел понять магию c, которая происходит под капотом flask (flask -restful) в сочетании с pytest. Потому что, если бы я определил мой app.py модуль следующим образом, он успешно прошел бы тесты:

# app.py

from flask import Flask, Blueprint
from flask_restful import Api, Resource


class TodoItem(Resource):
    def get(self, id):
        return {'task': 'Say "Hello, World!"'}


def create_app():
    """Initialize the app. """
    app = Flask(__name__)
    # note: I commented the line below and defined the blueprint in-place
    # from api_bp import api_bp  
    api_bp = Blueprint('api', __name__)
    api = Api(api_bp)

    api.add_resource(TodoItem, '/todos/<int:id>')
    app.register_blueprint(api_bp, url_prefix='/api')

    return app


if __name__ == '__main__':
    app = create_app()
    app.run(debug=True)
$ pytest -v
tests/test_api1.py::test_todo2 PASSED    [ 50%]
tests/test_api2.py::test_todo2 PASSED    [100%]

Или, если бы я не использовал фабрику приложений, он также работал бы нормально:

# app.py

from flask import Flask, Blueprint
from flask_restful import Api, Resource

app = Flask(__name__)
api_bp = Blueprint('api', __name__)
api = Api(api_bp)


class TodoItem(Resource):
    def get(self, id):
        return {'task': 'Say "Hello, World!"'}


api.add_resource(TodoItem, '/todos/<int:id>')
app.register_blueprint(api_bp, url_prefix='/api')

Также это может быть исправлено, если я поместил все свои тесты в один модуль или если я сначала зарегистрировал план, а затем добавил ресурсы следующим образом:

# app.py

...

def create_app():
    """Initialize the app. """
    app = Flask(__name__)

    from api_bp import api_bp

    api = Api(api_bp)

    app.register_blueprint(api_bp, url_prefix='/api')
    api.add_resource(TodoItem, '/todos/<int:id>')


    return app

...

Кто знает, что здесь произошло, и может объяснить magic? Заранее спасибо.

1 Ответ

0 голосов
/ 05 марта 2020

Таким образом, объяснение проблемы заключается в том, что при установке pytest и использовании клиента в тестах он запускает create_app() и пытается повторно использовать Blueprint, если не определяет Blueprint внутри app.py:

tests/test_api1.py::test_todo2 <flask.blueprints.Blueprint object at 0x7f04a8c9c610>

    SETUP    M client
        tests/test_api1.py::test_todo2 (fixtures used: client)<Response streamed [200 OK]>
PASSED
    TEARDOWN M client
tests/test_api2.py::test_todo2 <flask.blueprints.Blueprint object at 0x7f04a8c9c610>

    SETUP    M clientERROR
    TEARDOWN M client

исправьте это следующим образом:

# api_bp/__init__.py

from flask import Blueprint


get_blueprint = lambda: Blueprint('api', __name__)

И используя:

def create_app():
    """Initialize the app. """
    app = Flask(__name__)
    from api_bp import get_blueprint

    api_bp = get_blueprint()
    api = Api(api_bp)

    api.add_resource(TodoItem, '/todos/<int:id>')
    app.register_blueprint(api_bp, url_prefix='/api')

    return app

Таким образом, простейшим решением для такой проблемы будет использование правильной области действия pytest (не 'module'):

@pytest.fixture(scope='session')
def client():
...

Обновление:

Этот подход не будет работать с определением команды управления, такой как:

class Test(Command):
    def run(self):
        """Runs the tests."""
        pytest.main(['-s', '-v', './tests'])


manager.add_command('test', Test)  # run the tests

Использование python app.py test позволит вам получить ту же ошибку, что и в предыдущих примерах. Подробнее см. В разделе «Примечание:» по следующей ссылке: https://docs.pytest.org/en/latest/usage.html#calling -pytest-from- python -код

...