odoo17后台启动过程1——odoo_bin脚手架

odoo后台启动过程分析之一

1、odoo-bin

这是odoo的脚手架,支持如下子命令,我们常用的有server(默认命令),shell(odoo命令行),scaffold(新建模块)

(py311) PS D:\BaiduSyncdisk\odoo\odoo17> python odoo-bin help
Odoo CLI, use 'odoo-bin --help' for regular server options.

Available commands:
        cloc           Count lines of code per modules
        db             Create, drop, dump, load databases
        deploy         Deploy a module on an Odoo instance
        genproxytoken  Generate and (re)set proxy access token in config file
        help           Display the list of available commands
        neutralize     Neutralize a production database for testing: no emails sent, etc.
        populate       Inject fake data inside a database for testing
        scaffold       Generates an Odoo module skeleton.
        server         Start the odoo server (default command)
        shell          Start odoo in an interactive shell
        start          Quickly start the odoo server with default options
        tsconfig       Generates tsconfig files for javascript code

Use 'odoo-bin <command> --help' for individual command help.

看看odoo-bin的代码,这是一个python文件,代码很简单,做了三件事
1、设置了系统时区为UTC
2、引入odoo模块(执行了odoo下的init文件)
3、调用了odoo.cli.main() 函数

#!/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是一切的起点。 代码很简单

2、导入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, 10), "Outdated python version detected, Odoo requires Python >= 3.10 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 <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:
                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.10以上
  2. 判断是否运行在gevent模式,gevent 是一个基于协程的 Python 网络库,它提供了高性能的并发编程模型。在 Odoo 中,gevent 可以用于实现并发处理和提高系统的性能。,启用gevent模式之前,需要安装gevent包
pip install gevent

odoo配置文件中增加下列项:

workers = 0
limit_time_cpu = 600
limit_time_real = 1200

这些配置项将启用 gevent 模式,并设置了工作进程数为 0,表示使用 gevent 的协程模型。

重启 Odoo 服务器:重启 Odoo 服务器,使配置生效。

在 gevent 模式下,Odoo 服务器将使用协程来处理客户端请求,而不是传统的多线程或多进程模型。这种模式可以提高系统的并发性能和响应速度,减少资源的消耗。

需要注意的是,使用 gevent 模式可能会导致某些第三方模块或自定义模块的不兼容性问题。在启用 gevent 模式之前,建议先进行充分的测试和验证。

  1. 再次设置了时区为UTC
  2. 导入了PyPDF2和zlib, 这俩玩意干嘛的?
  3. 硬编码超级用户SUPERUSER_ID = 1
  4. 定义了一个函数registry,返回指定数据库的model 注册表,这是个核心概念,最关键的是这句return modules.registry.Registry(database_name)

然后就是导入包和模块,大概分了三类

1、odoo的核心包

2、ORM有关的,翻译有关的

3、其他的cli http, cli是跟脚手架有关的命令, http是web服务。

3、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函数了。

4、odoo.cli.main() 函数

这个函数在odoo\cli\command.py 中,他会根据脚手架传入的参数来调用不同的实现类来执行命令。

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这个脚手架是怎么实现的了。
1、odoo-bin 支持多个子命令,每个子命令都对应一实现类,他们有一个共同的父类Command,Command实现了__init_subclass__方法,那么他就成了一个元类。
2、因为Command实现了__init_subclass__方法,所以当你创建这个类的子类时,Python 会自动调用父类的 init_subclass` 方法,并将子类作为参数传递给它。而在Command中,它就所有的子类的名称以及子类作为键值对保存在Commands字典中,供后面调用。
3、main函数根据子命令从Commands中找到它的实现类,并且调用它的run方法。

明白了odoo-bin脚手架的原理,我们可以用这个思路写出其他的脚手架。
后面我们分析一下常用的两个指令 server和scaffold。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值