odoo后台启动过程分析之一
1、odoo-bin
#!/usr/bin/env python3# set server timezone in UTC before time module imported
__import__('os').environ['TZ'] = 'UTC'
import odooif __name__ == "__main__":odoo.cli.main()
这个odoo-bin是一切的起点。 代码很简单
第一步:先设置了时区
熟悉odoo的都知道,odoo的运行环境都是UTC,也就是0时区
第二步: 引入odoo包
odoo包
这里看上去只是导入了一个包,但是干了不少事,import odoo就会去执行odoo目录下的init文件,看看这个文件都干了哪些事
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.""" OpenERP core library."""#----------------------------------------------------------
# odoo must be a namespace package for odoo.addons to become one too
# https://packaging.python.org/guides/packaging-namespace-packages/
#----------------------------------------------------------
import pkgutil
import os.path
__path__ = [os.path.abspath(path)for path in pkgutil.extend_path(__path__, __name__)
]import sys
assert sys.version_info > (3, 7), "Outdated python version detected, Odoo requires Python >= 3.7 to run."#----------------------------------------------------------
# Running mode flags (gevent, prefork)
#----------------------------------------------------------
# Is the server running with gevent.
evented = False
if len(sys.argv) > 1 and sys.argv[1] == 'gevent':sys.argv.remove('gevent')import gevent.monkeyimport psycopg2from gevent.socket import wait_read, wait_writegevent.monkey.patch_all()def gevent_wait_callback(conn, timeout=None):"""A wait callback useful to allow gevent to work with Psycopg."""# Copyright (C) 2010-2012 Daniele Varrazzo <daniele.varrazzo@gmail.com># This function is borrowed from psycogreen module which is licensed# under the BSD license (see in odoo/debian/copyright)while 1:state = conn.poll()if state == psycopg2.extensions.POLL_OK:breakelif state == psycopg2.extensions.POLL_READ:wait_read(conn.fileno(), timeout=timeout)elif state == psycopg2.extensions.POLL_WRITE:wait_write(conn.fileno(), timeout=timeout)else:raise psycopg2.OperationalError("Bad result from poll: %r" % state)psycopg2.extensions.set_wait_callback(gevent_wait_callback)evented = True# Is the server running in prefork mode (e.g. behind Gunicorn).
# If this is True, the processes have to communicate some events,
# e.g. database update or cache invalidation. Each process has also
# its own copy of the data structure and we don't need to care about
# locks between threads.
multi_process = False#----------------------------------------------------------
# libc UTC hack
#----------------------------------------------------------
# Make sure the OpenERP server runs in UTC.
import os
os.environ['TZ'] = 'UTC' # Set the timezone
import time
if hasattr(time, 'tzset'):time.tzset()#----------------------------------------------------------
# PyPDF2 hack
# ensure that zlib does not throw error -5 when decompressing
# because some pdf won't fit into allocated memory
# https://docs.python.org/3/library/zlib.html#zlib.decompressobj
# ----------------------------------------------------------
import PyPDF2try:import zlibdef _decompress(data):zobj = zlib.decompressobj()return zobj.decompress(data)PyPDF2.filters.decompress = _decompress
except ImportError:pass # no fix required#----------------------------------------------------------
# Shortcuts
#----------------------------------------------------------
# The hard-coded super-user id (a.k.a. administrator, or root user).
SUPERUSER_ID = 1def registry(database_name=None):"""Return the model registry for the given database, or the database mentionedon the current thread. If the registry does not exist yet, it is created onthe fly."""if database_name is None:import threadingdatabase_name = threading.current_thread().dbnamereturn modules.registry.Registry(database_name)#----------------------------------------------------------
# Imports
#----------------------------------------------------------
from . import upgrade # this namespace must be imported first
from . import addons
from . import conf
from . import loglevels
from . import modules
from . import netsvc
from . import osv
from . import release
from . import service
from . import sql_db
from . import tools#----------------------------------------------------------
# Model classes, fields, api decorators, and translations
#----------------------------------------------------------
from . import models
from . import fields
from . import api
from odoo.tools.translate import _, _lt
from odoo.fields import Command#----------------------------------------------------------
# Other imports, which may require stuff from above
#----------------------------------------------------------
from . import cli
from . import http
从这段代码中可以得出几点:
- python版本必须要3.7以上
- 判断是否运行在gevent模式 这是啥模式?
- 再次设置了时区为UTC
- 导入了PyPDF2和zlib, 这俩玩意干嘛的?
- 硬编码超级用户SUPERUSER_ID = 1
- 定义了一个函数registry,返回指定数据库的model 注册表,这是个核心概念,最关键的是这句return modules.registry.Registry(database_name)
然后就是导入包和模块,大概分了三类
1、odoo的核心包
2、ORM有关的,翻译有关的
3、其他的cli http, cli是跟脚手架有关的命令, http是web服务。
cli包
我们来关注一下cli这个包,这个包包含了脚手架的所有命令。
#cli/__init__.py
import logging
import sys
import osimport odoofrom .command import Command, mainfrom . import cloc
from . import deploy
from . import scaffold
from . import server
from . import shell
from . import start
from . import populate
from . import tsconfig
from . import neutralize
from . import genproxytoken
from . import db
注意这一句
from .command import Command, main
cloc、deploy、scaffold…这些类都继承了Command这个类, 而这个Command类有点意思,看看它的定义
class Command:name = Nonedef __init_subclass__(cls):cls.name = cls.name or cls.__name__.lower()commands[cls.name] = cls
# __init_subclass__ 这个系统函数是从python3.6才有的
# 所有继承这个类的子类在定义的时候都会执行这个函数,而这个Command类就干了两件事,定义了一个name属性,以及收集所有的子类放到commands这个字典中去。
# 字典的key是小写的子类名,而value是子类对象。
当导入了所有的Command子类后, 也就等于收集了所有odoo-bin 支持的指令,现在可以执行main函数了。
第三步:执行了odoo.cli.main() 函数
def main():args = sys.argv[1:]# The only shared option is '--addons-path=' needed to discover additional# commands from modulesif len(args) > 1 and args[0].startswith('--addons-path=') and not args[1].startswith("-"):# parse only the addons-path, do not setup the logger...odoo.tools.config._parse_config([args[0]])args = args[1:]# Default legacy commandcommand = "server"# TODO: find a way to properly discover addons subcommands without importing the world# Subcommand discoveryif len(args) and not args[0].startswith("-"):logging.disable(logging.CRITICAL)initialize_sys_path()for module in get_modules():if (Path(get_module_path(module)) / 'cli').is_dir():__import__('odoo.addons.' + module)logging.disable(logging.NOTSET)command = args[0]args = args[1:]if command in commands:o = commands[command]()o.run(args)else:sys.exit('Unknown command %r' % (command,))
第一行先把odoo-bin本身从参数列表中去掉了,然后判断第二个参数是不是–addons-path ,如果是的话调用config相关函数处理
然后设置默认的指令是server, 当然这个值可能会被覆盖
如果接下来的参数不以-开头,说明command是有指定的,
command = args[0] # 取了第一个参数覆盖了之前设置的serverargs = args[1:] # 剩下的就是指令的参数了
如果指令在上文收集的命令字典中, 那么就new一个子类对象o,然后调用子类的run函数,并且把args作为参数传过去。否则程序退出。
if command in commands:o = commands[command]()o.run(args)else:sys.exit('Unknown command %r' % (command,))
分析到这里,我们看到了odoo-bin这个脚手架是怎么实现的了。 重点是
Command这个父类,以及它里面的__init_subclass__这个函数。
后面我们分析一下常用的两个指令 server和scaffold。