tortoise / tortoise-orm

Familiar asyncio ORM for python, built with relations in mind
https://tortoise.github.io
Apache License 2.0
4.52k stars 370 forks source link

Usage with Tornado (was: No DB associated to model) #99

Open asyncins opened 5 years ago

asyncins commented 5 years ago

from tortoise.models import Model from tortoise.fields import *

class Tournament(Model): id = IntField(pk=True) name = TextField()

if name == "main": from tortoise import Tortoise, run_async

async def init():
    # Here we create a SQLite DB using file "db.sqlite3"
    #  also specify the app name of "models"
    #  which contain models from "app.models"
    await Tortoise.init(
        db_url='sqlite://otc.db',
        modules={'models': ['model']} # 传入模块名称,本模块名为 model
    )
    # Generate the schema
    await Tortoise.generate_schemas()

run_async(init())

async def dps():
    await Tournament.create(name='Another Tournament')

    # Now search for a record
    tour = await Tournament.filter(name__contains='Another').first()
    print(tour.name)
run_async(dps())

============================== tortoise.exceptions.ConfigurationError: No DB associated to model

WHY ?

grigi commented 5 years ago

Hi asyncins

run_async() is a helper function that ensures connections are closed when it is done: https://tortoise-orm.readthedocs.io/en/latest/setup.html#cleaningup https://tortoise-orm.readthedocs.io/en/latest/setup.html#tortoise.run_async

Only use it once:

from tortoise import Tortoise, run_async
from tortoise.models import Model
from tortoise.fields import *

class Tournament(Model):
    id = IntField(pk=True)
    name = TextField()

async def init():
    # Here we create a SQLite DB using file "db.sqlite3"
    #  also specify the app name of "models"
    #  which contain models from "app.models"
    await Tortoise.init(
        db_url='sqlite://otc.db',
        modules={'models': ['model']} # 传入模块名称,本模块名为 model
    )
    # Generate the schema
    await Tortoise.generate_schemas()

async def dps():
    await Tournament.create(name='Another Tournament')

    # Now search for a record
    tour = await Tournament.filter(name__contains='Another').first()
    print(tour.name)

async def main():
    await init()
    await dps()

if __name__ == "__main__":
    run_async(main())

Or, alternatively manage the loop & cleanup yourself.

    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(main())
    finally:
        loop.run_until_complete(Tortoise.close_connections())
asyncins commented 5 years ago

Hi asyncins

run_async() is a helper function that ensures connections are closed when it is done: https://tortoise-orm.readthedocs.io/en/latest/setup.html#cleaningup https://tortoise-orm.readthedocs.io/en/latest/setup.html#tortoise.run_async

Only use it once:

from tortoise import Tortoise, run_async
from tortoise.models import Model
from tortoise.fields import *

class Tournament(Model):
    id = IntField(pk=True)
    name = TextField()

async def init():
    # Here we create a SQLite DB using file "db.sqlite3"
    #  also specify the app name of "models"
    #  which contain models from "app.models"
    await Tortoise.init(
        db_url='sqlite://otc.db',
        modules={'models': ['model']} # 传入模块名称,本模块名为 model
    )
    # Generate the schema
    await Tortoise.generate_schemas()

async def dps():
    await Tournament.create(name='Another Tournament')

    # Now search for a record
    tour = await Tournament.filter(name__contains='Another').first()
    print(tour.name)

async def main():
    await init()
    await dps()

if __name__ == "__main__":
    run_async(main())

Or, alternatively manage the loop & cleanup yourself.

    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(main())
    finally:
        loop.run_until_complete(Tortoise.close_connections())

========================== I used it in the tornado project. But,just do it your way, and the result is still wrong: ‘tortoise.exceptions.ConfigurationError: No DB associated to model’

asyncins commented 5 years ago

Traceback (most recent call last): File "/home/asyncins/anaconda3/envs/envpoll/lib/python3.7/site-packages/tortoise/models.py", line 47, in db return current_transaction_map[self.default_connection].get() KeyError: None

grigi commented 5 years ago

Sorry, I didn't test my example, there was 2 issues. In Tortoise.init(), the modules param, it tries to import a module called "models" which would work if you have a models.py in the path. If you define the models in the same file, you should either use the name of the module, or '__main__' When I take that example and do:

    await Tortoise.init(
        db_url='sqlite://otc.db',
        modules={'models': ['__main__']} # Change here
    )
    # Generate the schema

It works for me

grigi commented 5 years ago

I don't have much experience with Tornado, at least not since they adopted asyncio event loop. I imagine it will have a setup section, and in there you call await init(), and in the teardown section you should call await Tortoise.close_connections(). Then in the HTTP requests you should just use the ORM as per normal.

We should probably have a small tutorial or integration projects for the common frameworks. :thinking:

asyncins commented 5 years ago

I don't have much experience with Tornado, at least not since they adopted asyncio event loop. I imagine it will have a setup section, and in there you call await init(), and in the teardown section you should call await Tortoise.close_connections(). Then in the HTTP requests you should just use the ORM as per normal.

We should probably have a small tutorial or integration projects for the common frameworks. thinking

I like this ORM very much. It's very similar to Django ORM. It's very enjoyable to use.

I now have a lightweight Tornado project that wants to use the same lightweight Sqlite, but now it seems that they don't work well together, which is a great pity.

asyncins commented 5 years ago

OK!

grigi, Thank you !

If need to use Tortoise in tornado, you need to specify sqliter files when starting tornado.

It works for me

from tornado.web import Application
from tornado import ioloop
from tornado.options import options, define
from tortoise import Tortoise

from urls import router
from component.home.model import AddVersionModel

# define 定义一些可以在命令行中传递的参数以及类型
define('port', default=8888, help='run on the given port', type=int)
define('debug', default=True, help='set tornado debug mode', type=bool)
options.parse_command_line()

if __name__ == "__main__":
    async def run():
        await Tortoise.init(db_url='sqlite://db.sqlite3',
        modules={'models': ['__main__']})
        await Tortoise.generate_schemas()

    app = Application(router, debug=options.debug)
    app.listen(port=options.port)
    loops = ioloop.IOLoop.current()
    loops.run_sync(run)
    loops.start()

Chinese explanation:如果需要在Tornado中使用Tortoise,则需要在启动Tornado时指定sqliter文件。 照着示例代码启动就行,使用 ORM 跟平时无异。

grigi commented 5 years ago

Thank you! I will note to turn this into documentation.

SultanNasyrovDeveloper commented 5 years ago

Hello everyone! I get the same error in my aiohttp project.

Here is my db_init.py

from tortoise import Tortoise, run_async
from tight_trader.settings import config

DSN = "postgres://{user}:{password}@{host}:{port}/{database}"

async def create_tables(url):
    await Tortoise.init(
        db_url=url, modules={'models': ['tight_trader.trading_bot.models',
                                    ]}
    )
    await Tortoise.generate_schemas()

if __name__ == '__main__':
    db_url = DSN.format(**config['postgres'])
    run_async(create_tables(db_url))

models.py

import asyncio

from aiologger import Logger
from tortoise.models import Model
from tortoise import fields

from tight_trader.trading_bot.enums import BotType

logger = Logger.with_default_handlers(name=__name__)

class BaseTradingBot(Model):
    """ Abstract trading bot model """
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=100)
    type = None

    on = fields.BooleanField(default=False)
    frequency = fields.IntField(default=1)

    instruments = None  # to set up all instruments bot can trade

    class Meta:
        abstract = True

def __str__(self):
    return 'Trading Bot {}'.format(self.id)

def _tear_down(self):
    """ Makes all actions bot needs to stop trading"""
    self.session.stop()

class CryptoBot(BaseTradingBot):
    """  """
    type = BotType.CRYPTO

I get the error when i try to

@aiohttp_jinja2.template('trading_bot/list.html')
def bot_list(request):
     context = {'bots': CryptoBot.all()}
SultanNasyrovDeveloper commented 5 years ago

I didn't understand what is the right way to solve the problem. init script creates proper database tables. But seems that cant match them with the models

abondar commented 5 years ago

@SultanNasyrovDeveloper could you please show stacktrace for error and elaborate on how are you launching you script

ozturkberkay commented 4 years ago

Tortoise works (+ with migrations) in Tornado 6.0.3 and I have been using it for a while:

Define the models inside a folder called models under the root directory of the project. Just create an __init__.py file like this:

__models__ = [
    SomeModel,
    ...
]

Then use tornado.options for your configurations:

{
    'connections': {
         'mysql': {
            'engine': 'tortoise.backends.mysql',
            'credentials': {
                'host': options.mysql_host,
                'port': options.mysql_port,
                'user': options.mysql_user,
                'password': options.mysql_password,
                'database': options.mysql_database,
        },
        }
    },
    'apps': {
    'models': {'models': ['models'], 'default_connection': 'mysql'}
    },
}

For your tornado.web.Application, you can create your entry-point like this:

try:
    IOLoop.current().run_sync(init)  # Initialize your database here.
    IOLoop.current().start()  # Application starts here.
except KeyboardInterrupt:
    _logger.warning('Keyboard interruption detected!')
finally:
    IOLoop.current().stop()
    _logger.warning('Closing the database connections!')
   # Using Tornado's IOLoop throws this error for some reason:
   # tornado.util.TimeoutError: Operation timed out after None seconds
   # So, I use asyncio here.
   get_event_loop().run_until_complete(Tortoise.close_connections())

You can then do the initialization procedures you want inside the init function:

await Tortoise.init(...)

if options.mysql_sync:
    # Generate schemas
    # Init table entries
    # ...
grigi commented 4 years ago

Thanks for this, Will help in building an example/docs.

miss85246 commented 3 years ago

Hi there, I got the same error in sanic this is my Folder tree:

.
├── docs
│   └── test.md
├── src
│   ├── config.py
│   ├── __init__.py
│   ├── logs
│   │   └── blog.log
│   ├── models.py
│   ├── server.py
│   ├── tools
│   │   ├── __init__.py
│   │   ├── loggerFormatTools.py
│   │   ├── responseFormatTools.py
│   │   └── simpleTools.py
│   ├── urls.py
│   └── views
│       ├── blogViews.py
│       └── __init__.py
└── test
    ├── aaa.py
    ├── dbClientTest.py
    ├── functionTest.py
    ├── __init__.py
    └── markdownTest.py

there are some model in src/models.py

class Blog(models.Model):
    id = fields.IntField(pk=True)
    aritcle_id = fields.CharField(max_length=20, unique=True)
    titile = fields.CharField(max_length=128)
    digest = fields.CharField(max_length=255)
    cover = fields.CharField(max_length=255, null=True)
    content = fields.TextField()
    tags = fields.CharField(max_length=255)
    browse = fields.IntField(default=0)
    praise = fields.IntField(default=0)
    c_time = fields.DatetimeField(auto_now_add=True)
    u_time = fields.DatetimeField(auto_now=True)
    is_delete = fields.BooleanField(default=False)

    def __str__(self):
        return f"<BlogObj article_id={self.aritcle_id}>"

    class Meta:
        table = 'blogs'
        index = ["tags", "id_delete"]
        ordering = ["-c_time", "-u_time"]

and my config file like this:

TORTOISE_CONFIG = {
    "db_url": "mysql://admin:admin@127.0.0.1:3306/Test?maxsize=32&minsize=8&echo=True",
    "modules": {
        'models': ['models'],
    }
}

I tried got blogs in blogView.py, but It raise error

No DB associated to model

I have tried to create a models folder, and then move models.py into and create the __init__.py file. And I change the TORTOISE_CONFIG after that:

TORTOISE_CONFIG = {
    "db_url": "mysql://admin:admin@127.0.0.1:3306/Test?maxsize=32&minsize=8&echo=True",
    "modules": {
        'models': ['__main__'],
    }
}

It raise another error :

RuntimeWarning: Module "__main__" has no models
  cls._init_apps(apps_config)

what should I do ?

long2ice commented 3 years ago

Run await Tortoise.init() on app startup.

miss85246 commented 3 years ago

@long2ice Hi, Dear, I think I found the problem

在配置文件中引用models 的路径, 应该是相对于你注册tortoise的相对路径,

The path that refers to models' in the configuration file should be relative to the path where you register tortoise.

比如, 在我的问题中, 我的文件树是这个样子的:

For example, in my question, my file tree looks like this:

.
├── docs
│   └── test.md
├── src
│   ├── config.py
│   ├── __init__.py
│   ├── logs
│   │   └── blog.log
│   ├── models.py
│   ├── server.py
│   ├── tools
│   │   ├── __init__.py
│   │   ├── loggerFormatTools.py
│   │   ├── responseFormatTools.py
│   │   └── simpleTools.py
│   ├── urls.py
│   └── views
│       ├── blogViews.py
│       └── __init__.py
└── test
    ├── aaa.py
    ├── dbClientTest.py
    ├── functionTest.py
    ├── __init__.py
    └── markdownTest.py

我的 src/server.py 文件是这样的:

My src/server.py is like this:

from sanic import Sanic
from src.config import PROJECT_CONFIG, LOGGING_CONFIG, SERVER_CONFIG
from src.urls import server_bp
from tortoise.contrib.sanic import register_tortoise

server = Sanic('abcdefg', log_config=LOGGING_CONFIG)
server.config.update(PROJECT_CONFIG)
server.blueprint(server_bp)

register_tortoise(server, **server.config.TORTOISE_CONFIG)
if __name__ == '__main__':
    print(server.config.BASE_DIR)
    server.run(**SERVER_CONFIG)

我的配置文件是这样的:

and this is my config file

TORTOISE_CONFIG = {
    "db_url": "mysql://admin:admin@127.0.0.1:3306/Blog?maxsize=32&minsize=8&echo=True",
    "modules": {
        'models': ['models'],
    }
}

如果我在配置文件中想要使用 __main__ 参数来引入所有模型, 那么我的配置文件应该这样修改:

If I want to introduce all models with the __main__ parameter in my config file, my config file should be modified as follows:

TORTOISE_CONFIG = {
    "db_url": "mysql://admin:admin@127.0.0.1:3306/Blog?maxsize=32&minsize=8&echo=True",
    "modules": {
        'models': ['__main__'], # 这里做了修改(here is changed)
    }
}

然后我应该手动的在tortoise的注册页面引用所有的模型, 所以我的server.py应该这样修改

after that, I should import all models in the registration page of tortoise, so my server.py follows:

from sanic import Sanic
from src.config import PROJECT_CONFIG, LOGGING_CONFIG, SERVER_CONFIG
from src.urls import server_bp
from tortoise.contrib.sanic import register_tortoise
from models import * # 引入所有的模型(import all models)

server = Sanic('abcdefg', log_config=LOGGING_CONFIG)
server.config.update(PROJECT_CONFIG)
server.blueprint(server_bp)

register_tortoise(server, **server.config.TORTOISE_CONFIG)
if __name__ == '__main__':
    print(server.config.BASE_DIR)
    server.run(**SERVER_CONFIG)

如果我想使用 model 文件的路径来进行注册的话, 那我应该把配置文件中的models 改成 models.py 相对于 server.py的路径:

If I want to use the path of model' file for registration, I should change models' in the configuration file to the path of models.py relative to server.py:

TORTOISE_CONFIG = {
    "db_url": "mysql://admin:admin@127.0.0.1:3306/Blog?maxsize=32&minsize=8&echo=True",
    "modules": {
        'models': ['src.models'],# 这里做了修改(here is changed)
    }
}

server.py不需要做任何的改动

the server.py needn't do any change

然后 tortoise-orm 就可以正常工作了, 我想其他的框架也应该如此, 如果你也遇到了这个问题, 可以参考修改一下引用模型的相对位置.

Then tortoise-orm can work normally, and I think other frameworks should do the same. If you got the same error, you can refer to and modify the relative position of the reference model.

Hope It's useful to everyone !

long2ice commented 3 years ago

see here https://tortoise-orm.readthedocs.io/en/latest/examples/fastapi.html#