Using HTTPX for Tornado testing - testing

I'd like to standardize the use of HTTPX for testing regardless of the Python web framework being used. I managed to get it to work with Quart and FastAPI, but I'm having issues with Tornado since it doesn't comply to ASGI, and it uses a particular asynchronous implementation, although it is currently based on asyncio.
The minimal application to test is divided in three parts: main.py, conftest.py and test_hello.py.
app/main.py:
from contextlib import contextmanager
from typing import Iterator
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler
from loguru import logger
async def start_resources() -> None:
'''
Initialize resources such as async Redis and Database connections
'''
logger.info('resources started...')
async def close_resources() -> None:
'''
Release resources
'''
logger.info('resources closed...')
class HelloHandler(RequestHandler):
def get(self) -> None:
self.write({'hello': 'world'})
#contextmanager
def create_app() -> Iterator[Application]:
IOLoop.current().run_sync(start_resources)
try:
app = Application([
("/hello", HelloHandler),
])
yield app
finally:
IOLoop.current().run_sync(close_resources)
if __name__ == '__main__':
with create_app() as app:
http_server = HTTPServer(app)
http_server.listen(8000)
logger.info('Listening to port 8000 (use CTRL + C to quit)')
IOLoop.current().start()
tests/conftest.py:
from typing import Iterator, AsyncIterable
from httpx import AsyncClient
from pytest import fixture
from tornado.platform.asyncio import AsyncIOLoop
from tornado.web import Application
from app.main import create_app # isort:skip
#fixture
def app(io_loop: AsyncIOLoop) -> Iterator[Application]:
'''
Return a Tornado.web.Application object with initialized resources
'''
with create_app() as app:
yield app
#fixture
async def client(app: Application,
base_url: str) -> AsyncIterable[AsyncClient]:
async with AsyncClient(base_url=base_url) as _client:
yield _client
tests/test_hello.py:
from httpx import AsyncClient
from pytest import mark
#mark.gen_test
async def test_hello(client: AsyncClient) -> None:
resp = await client.get('/hello')
assert resp.status_code == 200
assert resp.json() == {'hello': 'world'}
And the project structure is this:
.
├── app
│ ├── __init__.py
│ └── main.py
├── poetry.lock
├── pyproject.toml
└── tests
├── conftest.py
├── __init__.py
└── test_hello.py
And the error I get
$ pytest tests/test_hello.py
========================================================================== test session starts ==========================================================================
platform linux -- Python 3.6.9, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: /tmp/minimal-app
plugins: tornado-0.8.1
collected 1 item
tests/test_hello.py F [100%]
=============================================================================== FAILURES ================================================================================
______________________________________________________________________________ test_hello _______________________________________________________________________________
client = <async_generator object client at 0x7f78e3de75f8>
#mark.gen_test
async def test_hello(client: AsyncClient) -> None:
> resp = await client.get('/hello')
E AttributeError: 'async_generator' object has no attribute 'get'
tests/test_hello.py:7: AttributeError
------------------------------------------------------------------------- Captured stderr setup -------------------------------------------------------------------------
2020-06-17 10:21:28.574 | INFO | app.main:start_resources:15 - resources started...
----------------------------------------------------------------------- Captured stderr teardown ------------------------------------------------------------------------
2020-06-17 10:21:28.595 | INFO | app.main:close_resources:22 - resources closed...
======================================================================== short test summary info ========================================================================
FAILED tests/test_hello.py::test_hello - AttributeError: 'async_generator' object has no attribute 'get'
=========================================================================== 1 failed in 0.03s ===========================================================================

I could make it work replacing pytest-tornado fixtures for a custom one and adding alt-pytest-asyncio to support asynchronous tests. pytest-tornado is not necessary anymore.
conftest.py:
from typing import AsyncIterable, Iterator
from httpx import AsyncClient
from pytest import fixture
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.platform.asyncio import AsyncIOLoop
from tornado.testing import bind_unused_port
from tornado.web import Application
from app.main import create_app # isort:skip
#fixture
def io_loop() -> AsyncIOLoop:
'''
Copied from https://github.com/eukaryote/pytest-tornasync/blob/master/src/pytest_tornasync/plugin.py#L59-L68
'''
loop = IOLoop()
loop.make_current()
yield loop
loop.clear_current()
loop.close(all_fds=True)
#fixture
def app(io_loop: AsyncIOLoop) -> Iterator[Application]:
'''
Return a Tornado.web.Application object with initialized resources
'''
with create_app() as app:
yield app
#fixture
async def client(app: Application) -> AsyncIterable[AsyncClient]:
'''
Start a HTTPServer each time
'''
http_server = HTTPServer(app)
port = bind_unused_port()[1]
http_server.listen(port)
async with AsyncClient(base_url=f'http://localhost:{port}') as _client:
yield _client
pyproject.toml:
[tool.poetry.dependencies]
python = "^3.8"
tornado = "^6.0.4"
pytest = "^6.0.1"
httpx = "^0.13.3"
loguru = "^0.5.1"
alt-pytest-asyncio = "^0.5.3"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

Related

Trying to deploy an ml model as an api through bentoml

import bentoml
import numpy as np
from bentoml.io import NumpyNdarray
# Get the runner
xgb_runner = bentoml.models.get("xgb_booster:latest").to_runner()
# Create a Service object
svc = bentoml.Service("xgb_classifier", runners=[xgb_runner])
# Create an endpoint named classify
#svc.api(input=NumpyNdarray(), output=NumpyNdarray())
def classify(input_series) -> np.ndarray:
# Convert the input string to numpy array
label = xgb_runner.predict.run(input_series)
return label
bentoml serve service.py:svc --reload
so this is my code
and the error i am getting is
Error: [bentoml-cli] serve failed: Failed to load bento or import service 'Service.py:svc'.
If you are attempting to import bento in local store: 'Failed to import module "Service.py": No module named 'Service.py''.
If you are importing by python module path: 'Bento 'Service.py:svc' is not found in BentoML store <osfs '/root/bentoml/bentos'>'.
so i tried pip install service

Why is SQL Alchemy database engine still None?

I am following the factory design pattern. In the WSGI.py file, why does print(db) prints
<SQLAlchemy engine=None>
I have attached the app to db with db.init_app(app) command in MAIN.py file. Shouldn't the output be
<SQLAlchemy engine= "sqlite:///site.db">
My WSGI.py file
from main import create_app, db
app = create_app("sqlite:///site.db")
print(db)
if __name__ == "__main__" :
app.run(debug=True)
My MAIN.py file
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app(URI) :
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = URI
db.init_app(app)
return app

unable to open database file on a hosting service pythonanywhere

I want to deploy my project on pythonanywhere. Error.log says that server or machine is unable to open my database. Everything works fine on my local machine. I watched a video of Pretty Printed from YouTube
This how I initialize in app.py. This what I got from error.log
db_session.global_init("db/data.sqlite")
this in db_session:
def global_init(db_file):
global __factory
if __factory:
return
if not db_file or not db_file.strip():
raise Exception("Необходимо указать файл базы данных.")
conn_str = f'sqlite:///{db_file.strip()}?check_same_thread=False'
print(f"Подключение к базе данных по адресу {conn_str}")
engine = sa.create_engine(conn_str, echo=False)
__factory = orm.sessionmaker(bind=engine)
from . import __all_models
SqlAlchemyBase.metadata.create_all(engine)
def create_session() -> Session:
global __factory
return __factory()
last thing is my wsgi.py:
import sys
path = '/home/r1chter/Chicken-beta'
if path not in sys.path:
sys.path.append(path)
import os
from dotenv import load_dotenv
project_folder = os.path.expanduser(path)
load_dotenv(os.path.join(project_folder, '.env'))
import app # noqa
application = app.app()
Usually errors like this on PythonAnywhere are due to providing relative path instead of absolute path.

Django & Celery & Rabbit getting Not Registered error

I am trying to set up Django & Celery & Rabbit for the first time following this tutorial. I am using Django 2.0 Celery 4.2.0 and Rabbit on Windows
I am getting the error: celery.exceptions.NotRegistered: 'GeneratePDF'
I have set up as follows:
in my init.py:
from __future__ import absolute_import, unicode_literals
import celery
from .celery import app as celery_app
__all__ = ['celery_app']
in my celery.py:
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
from django.conf import settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'abc.settings')
app = Celery('abc')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
#app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))
in my tasks.py:
from celery import shared_task
from abc.celery import app
#shared_task(name='GeneratePDF')
class GeneratePDF(View):
def get(self, request, *args, **kwargs):
....
in my views.py:
from abc.tasks import GeneratePDF
#method_decorator(login_required, name='dispatch')
class ClientProfilePDF(RedirectView):
def get(self, request, *args, **kwargs):
GeneratePDF.delay(request)
return HttpResponseRedirect('/home/')
in my settings.py:
CELERY_BROKER_URL = 'amqp://localhost'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_RESULT_BACKEND = 'django-db'
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Australia/Sydney'
CELERY_IMPORTS = ('abc.tasks',)
Can anyone point me in the right direction as to where I am going wrong and why I am getting this error? Any help is much appreciated!
Two quick things:
No need for any parameters to app.autodiscover_tasks() Celery alreayd knows how to use settings.INSTALLED_APPS.
The #shared_task decorator is for tasks that live in apps that do not have their own celery.py file that instantiates an app. From the looks of it, your tasks.py file lives in the same django app as the celery.py file. In this case, you should use #app.task and not #shared_task.
before you start, you can get a list of registered tasks by doing celery -A myapp inspect registered. That will let you see if your GeneratePDF task is registered or not.

Twisted trial error with SSL

SSLTest.testError passes, but Trial raises an exception after tearDown. For comparison there is RegularTest.testOk that works OK.
I have not found any Twisted bug that explain this, so I assume I'm doing something wrong given how easy this is to reproduce. Any ideas?
Here's the code:
from twisted.web import resource
from twisted.internet import ssl, reactor
from twisted.web.server import Site
from twisted.web.client import Agent, WebClientContextFactory
from twisted.trial.unittest import TestCase
class DummyServer(resource.Resource):
isLeaf = True
def render(self, request):
return 'hello world'
class SSLTest(TestCase):
def setUp(self):
site = Site(DummyServer())
SSLFactory = ssl.DefaultOpenSSLContextFactory('../server.key',
'../server.crt')
port = reactor.listenSSL(0, site, contextFactory=SSLFactory)
self.port = port
self.portNumber = port._realPortNumber
def tearDown(self):
self.port.stopListening()
def testError(self):
def finished(result):
self.assertEquals(result.code, 200)
url = 'https://127.0.0.1:%s' % self.portNumber
agent = Agent(reactor, WebClientContextFactory())
d = agent.request('GET', url)
d.addCallback(finished)
return d
class RegularTest(TestCase):
def setUp(self):
site = Site(DummyServer())
port = reactor.listenTCP(0, site)
self.port = port
self.portNumber = port._realPortNumber
def tearDown(self):
self.port.stopListening()
def testOk(self):
def finished(result):
self.assertEquals(result.code, 200)
url = 'http://127.0.0.1:%s' % self.portNumber
agent = Agent(reactor,)
d = agent.request('GET', url)
d.addCallback(finished)
return d
Here's stdout:
$ trial trialerror.py
trialerror
RegularTest
testOk ... [OK]
SSLTest
testError ... [OK]
[ERROR]
===============================================================================
[ERROR]
Traceback (most recent call last):
Failure: twisted.trial.util.DirtyReactorAggregateError: Reactor was unclean.
Selectables:
<TLSMemoryBIOProtocol #0 on 51135>
trialerror.SSLTest.testError
-------------------------------------------------------------------------------
Ran 2 tests in 0.018s
FAILED (errors=1, successes=2)
Jonathan Lange wrote about this problem and its solutions. You may also want to consider not using real network connections in your unit tests. Agent already works. So do Site,
reactor.listenSSL, etc. Try to write unit tests that exercise your code and not lots and lots of code from the libraries your code depends on.