odoo后台启动过程分析之一

odoo后台启动过程分析之一

1、odoo-bin

#!/usr/bin/env python3

# set server timezone in UTC before time module imported
__import__('os').environ['TZ'] = 'UTC'
import odoo

if __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.monkey
    import psycopg2
    from gevent.socket import wait_read, wait_write
    gevent.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 
        # 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:
                break
            elif 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 PyPDF2

try:
    import zlib

    def _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 = 1


def registry(database_name=None):
    """
    Return the model registry for the given database, or the database mentioned
    on the current thread. If the registry does not exist yet, it is created on
    the fly.
    """
    if database_name is None:
        import threading
        database_name = threading.current_thread().dbname
    return 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

从这段代码中可以得出几点:

  1. python版本必须要3.7以上
  2. 判断是否运行在gevent模式 这是啥模式?
  3. 再次设置了时区为UTC
  4. 导入了PyPDF2和zlib, 这俩玩意干嘛的?
  5. 硬编码超级用户SUPERUSER_ID = 1
  6. 定义了一个函数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 os

import odoo

from .command import Command, main

from . 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 = None
    def __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 modules
    if 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 command
    command = "server"

    # TODO: find a way to properly discover addons subcommands without importing the world
    # Subcommand discovery
    if 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]    # 取了第一个参数覆盖了之前设置的server
        args = 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。

你可能感兴趣的:(odoo启动过程分析,python,odoo)