[docs]@classmethoddefget_connection(cls,connection_name:str)->BaseDBAsyncClient:""" Returns the connection by name. :raises ConfigurationError: If connection name does not exist. .. warning:: This is deprecated and will be removed in a future release. Please use :meth:`connections.get<tortoise.connection.ConnectionHandler.get>` instead. """returnconnections.get(connection_name)
[docs]@classmethoddefdescribe_model(cls,model:Type["Model"],serializable:bool=True)->dict:# pragma: nocoverage""" Describes the given list of models or ALL registered models. :param model: The Model to describe :param serializable: ``False`` if you want raw python objects, ``True`` for JSON-serializable data. (Defaults to ``True``) See :meth:`tortoise.models.Model.describe` .. warning:: This is deprecated, please use :meth:`tortoise.models.Model.describe` instead """warnings.warn("Tortoise.describe_model(<MODEL>) is deprecated, please use <MODEL>.describe() instead",DeprecationWarning,stacklevel=2,)returnmodel.describe(serializable=serializable)
[docs]@classmethoddefdescribe_models(cls,models:Optional[List[Type["Model"]]]=None,serializable:bool=True)->Dict[str,dict]:""" Describes the given list of models or ALL registered models. :param models: List of models to describe, if not provided then describes ALL registered models :param serializable: ``False`` if you want raw python objects, ``True`` for JSON-serializable data. (Defaults to ``True``) :return: A dictionary containing the model qualifier as key, and the same output as ``describe_model(...)`` as value: .. code-block:: python3 { "models.User": {...}, "models.Permission": {...} } """ifnotmodels:models=[]forappincls.apps.values():formodelinapp.values():models.append(model)return{f"{model._meta.app}.{model.__name__}":model.describe(serializable)formodelinmodels}
@classmethoddef_init_relations(cls)->None:defget_related_model(related_app_name:str,related_model_name:str)->Type["Model"]:""" Test, if app and model really exist. Throws a ConfigurationError with a hopefully helpful message. If successful, returns the requested model. :raises ConfigurationError: If no such app exists. """try:returncls.apps[related_app_name][related_model_name]exceptKeyError:ifrelated_app_namenotincls.apps:raiseConfigurationError(f"No app with name '{related_app_name}' registered."f" Please check your model names in ForeignKeyFields"f" and configurations.")raiseConfigurationError(f"No model with name '{related_model_name}' registered in"f" app '{related_app_name}'.")defsplit_reference(reference:str)->Tuple[str,str]:""" Validate, if reference follow the official naming conventions. Throws a ConfigurationError with a hopefully helpful message. If successful, returns the app and the model name. :raises ConfigurationError: If reference is invalid. """iflen(items:=reference.split("."))!=2:# pragma: nocoverageraiseConfigurationError(f"'{reference}' is not a valid model reference Bad Reference."" Should be something like '<appname>.<modelname>'.")returnitems[0],items[1]definit_fk_o2o_field(model:Type["Model"],field:str,is_o2o=False)->None:ifis_o2o:fk_object:Union[OneToOneFieldInstance,ForeignKeyFieldInstance]=cast(OneToOneFieldInstance,model._meta.fields_map[field])else:fk_object=cast(ForeignKeyFieldInstance,model._meta.fields_map[field])related_app_name,related_model_name=split_reference(fk_object.model_name)related_model=get_related_model(related_app_name,related_model_name)ifto_field:=fk_object.to_field:related_field=related_model._meta.fields_map.get(to_field)ifnotrelated_field:raiseConfigurationError(f'there is no field named "{to_field}" in model "{related_model_name}"')ifnotrelated_field.unique:raiseConfigurationError(f'field "{to_field}" in model "{related_model_name}" is not unique')else:fk_object.to_field=related_model._meta.pk_attrrelated_field=related_model._meta.pkkey_fk_object=deepcopy(related_field)fk_object.to_field_instance=related_field# type:ignore[arg-type,call-overload]fk_object.field_type=fk_object.to_field_instance.field_typekey_field=f"{field}_id"key_fk_object.reference=fk_objectkey_fk_object.source_field=fk_object.source_fieldorkey_fieldforattrin("index","default","null","generated","description"):setattr(key_fk_object,attr,getattr(fk_object,attr))ifis_o2o:key_fk_object.pk=fk_object.pkkey_fk_object.unique=fk_object.uniqueelse:key_fk_object.pk=Falsekey_fk_object.unique=Falsemodel._meta.add_field(key_field,key_fk_object)fk_object.related_model=related_modelfk_object.source_field=key_fieldif(backward_relation_name:=fk_object.related_name)isnotFalse:ifnotbackward_relation_name:backward_relation_name=f"{model._meta.db_table}s"ifbackward_relation_nameinrelated_model._meta.fields:raiseConfigurationError(f'backward relation "{backward_relation_name}" duplicates in'f" model {related_model_name}")ifis_o2o:fk_relation:Union[BackwardOneToOneRelation,BackwardFKRelation]=(BackwardOneToOneRelation(model,key_field,key_fk_object.source_field,null=True,description=fk_object.description,))else:fk_relation=BackwardFKRelation(model,key_field,key_fk_object.source_field,null=fk_object.null,description=fk_object.description,)fk_relation.to_field_instance=fk_object.to_field_instance# type:ignorerelated_model._meta.add_field(backward_relation_name,fk_relation)ifis_o2oandfk_object.pk:model._meta.pk_attr=key_fieldforapp_name,appincls.apps.items():formodel_name,modelinapp.items():ifmodel._meta._inited:continuemodel._meta._inited=Trueifnotmodel._meta.db_table:model._meta.db_table=model.__name__.lower()forfieldinsorted(model._meta.fk_fields):init_fk_o2o_field(model,field)forfieldinmodel._meta.o2o_fields:init_fk_o2o_field(model,field,is_o2o=True)forfieldinlist(model._meta.m2m_fields):m2m_object=cast(ManyToManyFieldInstance,model._meta.fields_map[field])ifm2m_object._generated:continuebackward_key=m2m_object.backward_keyifnotbackward_key:backward_key=f"{model._meta.db_table}_id"ifbackward_key==m2m_object.forward_key:backward_key=f"{model._meta.db_table}_rel_id"m2m_object.backward_key=backward_keyreference=m2m_object.model_namerelated_app_name,related_model_name=split_reference(reference)related_model=get_related_model(related_app_name,related_model_name)m2m_object.related_model=related_modelbackward_relation_name=m2m_object.related_nameifnotbackward_relation_name:backward_relation_name=m2m_object.related_name=(f"{model._meta.db_table}s")ifbackward_relation_nameinrelated_model._meta.fields:raiseConfigurationError(f'backward relation "{backward_relation_name}" duplicates in'f" model {related_model_name}")ifnotm2m_object.through:related_model_table_name=(related_model._meta.db_tableorrelated_model.__name__.lower())m2m_object.through=f"{model._meta.db_table}_{related_model_table_name}"m2m_relation=ManyToManyFieldInstance(f"{app_name}.{model_name}",m2m_object.through,forward_key=m2m_object.backward_key,backward_key=m2m_object.forward_key,related_name=field,field_type=model,description=m2m_object.description,)m2m_relation._generated=Truemodel._meta.filters.update(get_m2m_filters(field,m2m_object))related_model._meta.add_field(backward_relation_name,m2m_relation)@classmethoddef_discover_models(cls,models_path:Union[ModuleType,str],app_label:str)->List[Type["Model"]]:ifisinstance(models_path,ModuleType):module=models_pathelse:try:module=importlib.import_module(models_path)exceptImportError:raiseConfigurationError(f'Module "{models_path}" not found')discovered_models=[]possible_models=getattr(module,"__models__",None)try:possible_models=[*possible_models]# type:ignoreexceptTypeError:possible_models=Noneifnotpossible_models:possible_models=[getattr(module,attr_name)forattr_nameindir(module)]forattrinpossible_models:ifisclass(attr)andissubclass(attr,Model)andnotattr._meta.abstract:ifattr._meta.appandattr._meta.app!=app_label:continueattr._meta.app=app_labeldiscovered_models.append(attr)ifnotdiscovered_models:warnings.warn(f'Module "{models_path}" has no models',RuntimeWarning,stacklevel=4)returndiscovered_models
[docs]@classmethoddefinit_models(cls,models_paths:Iterable[Union[ModuleType,str]],app_label:str,_init_relations:bool=True,)->None:""" Early initialisation of Tortoise ORM Models. Initialise the relationships between Models. This does not initialise any database connection. :param models_paths: Models paths to initialise :param app_label: The app label, e.g. 'models' :param _init_relations: Whether to init relations or not :raises ConfigurationError: If models are invalid. """app_models:List[Type[Model]]=[]formodels_pathinmodels_paths:app_models+=cls._discover_models(models_path,app_label)cls.apps[app_label]={model.__name__:modelformodelinapp_models}if_init_relations:cls._init_relations()
@classmethoddef_init_apps(cls,apps_config:dict)->None:forname,infoinapps_config.items():try:connections.get(info.get("default_connection","default"))exceptKeyError:raiseConfigurationError('Unknown connection "{}" for app "{}"'.format(info.get("default_connection","default"),name))cls.init_models(info["models"],name,_init_relations=False)formodelincls.apps[name].values():model._meta.default_connection=info.get("default_connection","default")cls._init_relations()cls._build_initial_querysets()@classmethoddef_get_config_from_config_file(cls,config_file:str)->dict:_,extension=os.path.splitext(config_file)ifextensionin(".yml",".yaml"):importyaml# pylint: disable=C0415withopen(config_file,"r")asf:config=yaml.safe_load(f)elifextension==".json":withopen(config_file,"r")asf:config=json.load(f)else:raiseConfigurationError(f"Unknown config extension {extension}, only .yml and .json are supported")returnconfig@classmethoddef_build_initial_querysets(cls)->None:forappincls.apps.values():formodelinapp.values():model._meta.finalise_model()model._meta.basetable=Table(name=model._meta.db_table,schema=model._meta.schema)model._meta.basequery=model._meta.db.query_class.from_(model._meta.basetable)model._meta.basequery_all_fields=model._meta.basequery.select(*model._meta.db_fields)
[docs]@classmethodasyncdefinit(cls,config:Optional[dict]=None,config_file:Optional[str]=None,_create_db:bool=False,db_url:Optional[str]=None,modules:Optional[Dict[str,Iterable[Union[str,ModuleType]]]]=None,use_tz:bool=False,timezone:str="UTC",routers:Optional[List[Union[str,Type]]]=None,)->None:""" Sets up Tortoise-ORM. You can configure using only one of ``config``, ``config_file`` and ``(db_url, modules)``. :param config: Dict containing config: .. admonition:: Example .. code-block:: python3 { 'connections': { # Dict format for connection 'default': { 'engine': 'tortoise.backends.asyncpg', 'credentials': { 'host': 'localhost', 'port': '5432', 'user': 'tortoise', 'password': 'qwerty123', 'database': 'test', } }, # Using a DB_URL string 'default': 'postgres://postgres:qwerty123@localhost:5432/test' }, 'apps': { 'my_app': { 'models': ['__main__'], # If no default_connection specified, defaults to 'default' 'default_connection': 'default', } }, 'routers': ['path.router1', 'path.router2'], 'use_tz': False, 'timezone': 'UTC' } :param config_file: Path to .json or .yml (if PyYAML installed) file containing config with same format as above. :param db_url: Use a DB_URL string. See :ref:`db_url` :param modules: Dictionary of ``key``: [``list_of_modules``] that defined "apps" and modules that should be discovered for models. :param _create_db: If ``True`` tries to create database for specified connections, could be used for testing purposes. :param use_tz: A boolean that specifies if datetime will be timezone-aware by default or not. :param timezone: Timezone to use, default is UTC. :param routers: A list of db routers str path or module. :raises ConfigurationError: For any configuration error """ifcls._inited:awaitconnections.close_all(discard=True)ifint(bool(config)+bool(config_file)+bool(db_url))!=1:raiseConfigurationError('You should init either from "config", "config_file" or "db_url"')ifconfig_file:config=cls._get_config_from_config_file(config_file)ifdb_url:ifnotmodules:raiseConfigurationError('You must specify "db_url" and "modules" together')config=generate_config(db_url,modules)try:connections_config=config["connections"]# type: ignoreexceptKeyError:raiseConfigurationError('Config must define "connections" section')try:apps_config=config["apps"]# type: ignoreexceptKeyError:raiseConfigurationError('Config must define "apps" section')use_tz=config.get("use_tz",use_tz)# type: ignoretimezone=config.get("timezone",timezone)# type: ignorerouters=config.get("routers",routers)# type: ignore# Mask passwords in logs outputpasswords=[]forname,infoinconnections_config.items():ifisinstance(info,str):info=expand_db_url(info)password=info.get("credentials",{}).get("password")ifpassword:passwords.append(password)str_connection_config=str(connections_config)forpasswordinpasswords:str_connection_config=str_connection_config.replace(password,# Show one third of the password at beginning (may be better for debugging purposes)f"{password[0:len(password)//3]}***",)logger.debug("Tortoise-ORM startup\n connections: %s\n apps: %s",str_connection_config,str(apps_config),)cls._init_timezone(use_tz,timezone)awaitconnections._init(connections_config,_create_db)cls._init_apps(apps_config)cls._init_routers(routers)cls._inited=True
@classmethoddef_init_routers(cls,routers:Optional[List[Union[str,type]]]=None):fromtortoise.routerimportrouterrouters=routersor[]router_cls=[]forrinrouters:ifisinstance(r,str):try:module_name,class_name=r.rsplit(".",1)router_cls.append(getattr(importlib.import_module(module_name),class_name))exceptException:raiseConfigurationError(f"Can't import router from `{r}`")elifisinstance(r,type):router_cls.append(r)else:raiseConfigurationError("Router must be either str or type")router.init_routers(router_cls)
[docs]@classmethodasyncdefclose_connections(cls)->None:""" Close all connections cleanly. It is required for this to be called on exit, else your event loop may never complete as it is waiting for the connections to die. .. warning:: This is deprecated and will be removed in a future release. Please use :meth:`connections.close_all<tortoise.connection.ConnectionHandler.close_all>` instead. """awaitconnections.close_all()logger.info("Tortoise-ORM shutdown")
[docs]@classmethodasyncdefgenerate_schemas(cls,safe:bool=True)->None:""" Generate schemas according to models provided to ``.init()`` method. Will fail if schemas already exists, so it's not recommended to be used as part of application workflow :param safe: When set to true, creates the table only when it does not already exist. :raises ConfigurationError: When ``.init()`` has not been called. """ifnotcls._inited:raiseConfigurationError("You have to call .init() first before generating schemas")forconnectioninconnections.all():awaitgenerate_schema_for_client(connection,safe)
@classmethodasyncdef_drop_databases(cls)->None:""" Tries to drop all databases provided in config passed to ``.init()`` method. Normally should be used only for testing purposes. :raises ConfigurationError: When ``.init()`` has not been called. """ifnotcls._inited:raiseConfigurationError("You have to call .init() first before deleting schemas")# this closes any existing connections/pool if any and clears# the storageawaitconnections.close_all(discard=False)forconninconnections.all():awaitconn.db_delete()connections.discard(conn.connection_name)awaitcls._reset_apps()@classmethoddef_init_timezone(cls,use_tz:bool,timezone:str)->None:os.environ["USE_TZ"]=str(use_tz)os.environ["TIMEZONE"]=timezone
defrun_async(coro:Coroutine)->None:""" Simple async runner that cleans up DB connections on exit. This is meant for simple scripts. Usage:: from tortoise import Tortoise, run_async async def do_stuff(): await Tortoise.init( db_url='sqlite://db.sqlite3', models={'models': ['app.models']} ) ... run_async(do_stuff()) """loop=asyncio.get_event_loop()try:loop.run_until_complete(coro)finally:loop.run_until_complete(connections.close_all(discard=True))__version__=importlib_metadata.version("tortoise-orm")__all__=["Model","Tortoise","BaseDBAsyncClient","__version__","connections",]