odoo 学习记录--模型搭建

本文详细介绍了Odoo中模型的构建,包括Controller部分的路由映射、请求处理、响应生成,数据加载的XML定义和模型的创建,以及安全性设置和静态文件的组织结构。同时讲解了视图展示的四种类型,如菜单、表单、列表和搜索视图。

odoo 模型:

一:Controller

    一般通过继承的形式来创建controller类,继承自odoo.http.Controller。

    以route装饰器来装饰定义的方法,提供url路由访问路径:

class MyController(odoo.http.Controller)://继承controller定义控制器
    @route('/some_url', auth='public')//装饰器路由访问方法,并指明访问方式:公开还是需要用户登陆
    def handler(self):
        return stuff()

路由映射相关

    odoo.http.route(route=None, **kw) 装饰器可以将对应方法装饰为处理对应的http请求。

    装饰器参数有:

    1)route

    字符串或数组,决定哪些http请求可以匹配所装饰的方法,可以是单个字符串、或多个字符串的数组

    2)type

    请求的类型,可以是http或json

    3)auth

    认证方法的类型,可以是以下几种:

        user - 必须是已通过登录认证的用户,才能访问该请求。如果未经过登录直接访问,则会拦截并跳转回odoo登录页面。

        public - 使用公用的认证,可以不经过登录验证直接访问。

        none - 相应的方法总是可用,一般用于框架和认证模块,对应请求没有办法访问数据库或指向数据库的设置。

    4)methods

    这个请求所应用的一系列http方法【PATCH, POST, PUT, 或者DELETE】,如果没指定则是所有方法。

 methods=['POST', 'GET']

    5)cors

    跨域资源cors参数。

    6)csrf(boolean)

    是否开启CSRF跨域保护,默认True。

        a)如果表单是用python代码生成的,可通过request.csrf_token() 获取csrf

        b)如果表单是用javascript生成的,CSRF token会自动被添加到QWEB环境变量中,通过require('web.core').csrf_token获取

        c)如果终端可从其他地方以api或webhook形式调用,需要将对应的csrf禁用,此时最好用其他方式进行验证

请求相关

    请求对象在收到请求时自动设置到odoo.http.request,可以通过该对象提前request中携带的 参数、cookie、session对象等。

   跟请求相关的类和函数有以下几种:

   1:class odoo.http.WebRequest(httprequest)

   所有odoo WEB请求的父类,一般用于进行请求对象的初始化,其构建函数的参数有:

    1)httprequest

    原始的werkzeug.wrappers.Request对象

    2)params

    请求参数的映射

    3)cr

    当前方法调用的初始游标,当使用none的认证方式时读取游标会报错

    4)context

    当前请求的上下文键值映射

    5)env

    绑定到当前请求的环境

    6)session

    储存当前请求session数据的OpenERPSession

    7)debug

    指定当前请求是否是debug模式

    8)db

    当前请求所关联的数据库,当使用none认证时为None

    9)csrf_token(time_limit=3600)

    为该请求生成并返回一个token(参数以秒计算,默认1小时,如果传None表示与当前用户session时间相同)

 

    2:class odoo.http.HttpRequest(*args)

    用于处理http类型请求的函数,匹配 路由参数、查询参数、表格参数,如果有指定文件也会传给该方法。为防止重名,路由参数优先级最高。
    该函数的返回值有三种:

    • 无效值,HTTP响应会返回一个204(没有内容)
    • 一个werkzeug 响应对象
    • 一个字符串或unicode,会被响应对象包装并使用HTML解析

    3:make_response(data, headers=None, cookies=None)

    用于生成没有HTML的响应 或 自定义响应头、cookie的html响应。
    由于处理函数只以字符串形式返回html标记内容,需要组成一个完整的响应对象,这样客户端才能解析

    参数有:

    1)data (basestring)

    响应主体。

    2)headers ([(name, value)])

    http响应头。

    3)cookies (collections.Mapping)

    发送给客户端的cookie。

    4:not_found(description=None)

    给出404 NOT FOUND响应。

    5:render(template, qcontext=None, lazy=True, **kw)

    渲染qweb模板,在调度完成后会对给定的模板进行渲染并返回给客户端。

    参数有:

    1)template (basestring)

    用于渲染的模板完整ID:模块.模版

    2)qcontext (dict)

    用于渲染的上下文环境。

    3)lazy (bool)

   渲染动作是否应该拖延到最后执行。

    4)kw

    转发到werkzeug响应对象。

    6:class odoo.http.JsonRequest(*args)

    处理通过http发来的json rpc请求。

    参数:params -- 是一个json格式对象

    返回值:一个json-rpc格式的,以JSON-RPC Response对象的组装。

响应相关

    1:class odoo.http.Response(args, *kw)

    响应对象通过控制器的路由传递,在werkzeug.wrappers.Response之外,该类的构造方法会添加以下参数到qweb的渲染中:

    1)template (basestring)

    用于渲染的模板。

    2)qcontext (dict)

    用在渲染中的上下文环境。

    3)uid (int)

    用于调用ir.ui.view渲染的用户id,None时使用当前请求的id。

    上面的参数在实际渲染之前可以随时作为Response对象的属性进行修改。

    response对象可以调用以下方法:

    • render() - 渲染响应对象的模板,并返回内容。
    • flatten() - 强制渲染响应对象的模板,将结果设置为响应主体,并将模板复原。

二、data  (odoo 基础数据加载)

XML数据定义格式

    <record id="building_type0" model="building.document.folder">
        <field name="name">局集团党委文件</field>
    </record>

    <record id="activity_type1" model="building.document.folder">
        <field name="name">总支部文件</field>
    </record>

     <record id="building_type2" model="building.document.folder">
        <field name="name">支部文件</field>
    </record>
  • model里填 modelclass 的 _name 值

  • id里填外部标识(external-identifier),是odoo中用来标注某条数据库记录的唯一标示符

  • 注意:可以在web设置里查看所有的外部标识。

内部field就是定义具体记录的列名和值,可以有多个列,如下:

    <record id="documents_hr_documents_facet" model="documents.facet">
        <field name="name">Documents</field>
        <field name="sequence">6</field>
        <field name="folder_id" ref="documents_hr_folder"/>
    </record>

     <record id="documents_internal_template_facet" model="documents.facet">
        <field name="name">Templates</field>
        <field name="sequence">6</field>
        <field name="folder_id" ref="documents_internal_folder"/>
     </record>

数据文件需在__manifest__.py data或demo字段里列出,才能在模块安装更新后正确的加载

'data': [
    'security/security.xml',
    'security/ir.model.access.csv',
    'assets.xml',
    'views/views.xml',
    'views/templates.xml',
    'data/building_data.xml',
],

# 
'demo': [
    'demo/demo.xml',
],
  • demo数据只在勾选演示数据后才会加载(only loaded in demonstration mode)
  • data数据在系统启动后会自动进行加载(always loaded)

三、models

models里面是模型,ORM对象关系映射,面向对象访问数据库,不写sql。

model属性详解:
_name:模型唯一标识,类非继承父类时必须指定。
_rec_name:数据显示名称,如设置则返回其指定的字段值,不设置默认显示字段为name的字段值,如无name字段则显示"模块名,id";详见BaseModel.name_get方法。
_log_access:是否自动增加日志字段(create_uidcreate_date,write_uidwrite_date)。默认为True。
_auto:是否创建数据库对象。默认为True,详见BaseModel._auto_init方法。
_table:数据库对象名称。缺省时数据库对象名称与_name指定值相同(.替换为下划线)。
_sequence:数据库id字段的序列。默认自动创建序列。
_order:数据显示排序。所指定值为模型字段,按指定字段和方式排序结果集。

例:_order = "create_date desc":根据创建时间降序排列。可指定多个字段。

不指定desc默认升序排列;不指定_order默认id升序排列。

_constraints:自定义约束条件。模型创建/编辑数据时触发,约束未通过弹出错误提示,拒绝创建/编辑。

格式:_constraints = [(method, 'error message', [field1, ...]), ...]
method:检查方法。返回True|False
error message:不符合检查条件时(method返回False)弹出的错误信息
[field1, ...]:字段名列表,这些字段的值会出现在error message中。

_sql_constraints:数据库约束。

例:_sql_constraints = [ ('number_uniq', 'unique(number, code)', 'error message') ]
会在数据库添加约束:
CONSTRAINT number_uniq UNIQUE(number, code) 

_inherit:单一继承。值为所继承父类_name标识。如子类不定义_name属性,则在父类中增加该子类下的字段或方法,不创建新对象;如子类定义_name属性,则创建新对象,新对象拥有父类所有的字段或方法,父类不受影响。

格式:_inherit = '父类 _name'

_inherits:多重继承。子类通过关联字段与父类关联,子类不拥有父类的字段或方法,但是可以直接操作父类的字段或方法。

格式:_inherits = {'父类 _name': '关联字段'}

注意:

在models类中若使用_inherit继承表结构时,继承类中_name写与不写的作用不同:

在继承类中写_name时:继承表结构,不继承表数据,不在原表中修改

在继承类中不写_name时:继承表结构,继承表数据,在原表中修改

说明:

  1. 一个类就是一张表
  2. 继承,models.Model

类变量: 

表名“classroom_teacher”,规则,前面是这个模块名,后面是自定义 
_name = "classroom.teacher"  

常用字段,需要引入 from odoo import  fields

定义对象类型
基础类型:char, text, boolean, integer, float, date, time, datetime, binary;
复杂类型:selection
关系类型:Many2many,Many2one,One2many。

基础型

复杂型

selelction下拉框:

hobby = fields.selection([('0','火影'),('1','海贼'),('2','钢炼')],default=‘0’,string='爱好')

关系型

many2many 多对多

One2many 一对多

Many2one 多对一

name = fields.Char()

required=True 必填

string 界面label的值 字段标签

help 界面tooltip

index 是否创建数据库索引

简单字段类型Boolean, Integer,Float,Monetary, Char,Text,Html,Date,Datetime,Binary,Selection,Reference 不可再分 保存单一数据

保留字段 id, create_date, create_uid, write_date, write_uid 会随模型一起在表中建立(请勿重复定义)

四、security (权限安全)

security/模型名_security.xml
security/ir.model.access.csv

(1)建立安全组(security/activity_security.xml)

字段类型说明
name用户组组名
category_id关联应用,这是一个关联字段,因此使用了 ref 属性来通过 XML ID 连接已创建的分类
implied_ids这是一个one-to-many关联字段,包含一系列组来对组内用户生效
model_id模块id,对应ir.model.access.csv文件中定义的model_id
domain_forc域表达式。[(字段名,操作符,值), (字段名,操作符,值)]
groups用户组id。base.group_user,是系统内置的员工组的外部id
<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <data noupdate="0">
        <record model="ir.module.category" id="module_category_activity">
            <field name="name"> 活动管理 </field>
        </record>
        
        #普通用户
        <record model="res.groups" id="group_activity_user">
            <field name="name"> 用户 </field>
            <field name="category_id" ref="module_category_activity"/>
        </record>
        
        #管理员
        <record model="res.groups" id="group_activity_manager">
            <field name="name"> 管理 </field>
            <field name="implied_ids" eval="[(4, ref('group_activity_user'))]"/>
            <field name="category_id" ref="module_category_activity"/>
        </record>
    </data>
</odoo>

(2)定义组的权限(security/ir.model.access.csv)

字段类型说明
id权限的ID,自定义,不能重复
name权限名称,自定义
model_id:idmodel_模型名称。(注意把“.”全部换成“_”,否则会报错)
perm_read,perm_write,perm_create,perm_unlink读、写、增加、删除权限,1是有权限,0是无权限,具体根据需要来设置权限
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink

access_activity_user,access_activity_user,model_activity_event,group_activity_user,1,0,0,0
access_activity_manager,access_activity_manager,model_activity_event,group_activity_manager,1,1,1,1

五、static (静态文件)

文件层级:

static/src/js/XXXXX.js   编写js文件

static/src/img/XXXXX.png   放置图片

static/src/description/XXXXX.png   一般用来放置模型封面图片

static/src/scss/XXXX.scss   编写scss文件

static/src/xml/XXXX.xml   编写模板文件

例:

六、views  (视图【页面】展示)

主要有四个视图:

菜单视图:把 数据模型——菜单——视图(tree、form) 连接起来

<act_window id="动作id"
                name="XX"
                res_model="模型名称(表名)"
                view_mode="tree,form"/>

<menuitem id="菜单id" name="XX" action="菜单绑定动作id" parent="父级菜单"/>

表单视图:创建、编辑数据模型所用视图。

    <record id="视图id" model="ir.ui.view">
        <field name="name">详情</field>
        <field name="model">模型名称(表名)</field>
        <field name="arch" type="xml">
            <form>
                <header>
                    <button type="object" name="issue" string="确认"/>
                </header>
                <sheet>
                    <group>
                        <group>
                            <field name="materialName"/>
                            <field name="rackNumber"/>
                            <field name="responsibilityWorkshop"/>
                            <field name="timeAllocation"/>
                            <field name="allocationTime"/>
                        </group>
                        <group>
                            <field name="unit"/>
                        </group>
                        <group>
                            <field name="completionDate"/>
                            <field name="materialCode"/>
                            <field name="amount"/>
                            <field name="remarks"/>
                        </group>
                        <group>
                            <field name="process_picture"/>
                        </group>
                    </group>
                </sheet>
            </form>
        </field>
    </record>

列表视图:展示数据模型(显示数据)时使用。

<record id="视图id" model="ir.ui.view">
        <field name="name">列表</field>
        <field name="model">模型名称(表名)</field>
        <field name="arch" type="xml">
            <tree>
                <field name="materialName"/>
                <field name="format"/>
                <field name="materialCode"/>
                <field name="completionDate"/>
                <field name="unit"/>
                <field name="amount"/>
                <field name="rackNumber"/>
                <field name="responsibilityWorkshop"/>
                <field name="remarks" optional="hidden"/>
            </tree>
        </field>
    </record>

搜索视图:制定odoo右上角对于当前数据模型的可搜索字段以及可用过滤器。

<record id="视图id" model="ir.ui.view">
        <field name="name">搜索视图</field>
        <field name="model">模型名称(表名)</field>
        <field name="arch" type="xml">
            <search>
<!-- 搜索筛选字段 -->
                <filter string="字段展示名称" name="字段" domain="[('筛选条件','=/in/!=', 值)]"/>  
<!-- 搜索默认分组字段 -->
                <field name="字段"/>
                <group expand="0" string="Group By">
                    <filter string="名称" name="group_by_字段"
                            context="{'group_by': '字段'}"/>
                </group>
            </search>
        </field>
    </record>

_manifest__.py

{
    'name': '模型名称',

    'summary': """
        模型介绍""",

    'description': """
        模型介绍
    """,
    'author': "创建者",
    'website': "todo",
    'category': '模型类别',
    'version': '版本',
    'depends': [依赖区],
    'data': [
            权限及视图加载区
    ],
    'qweb': [qweb模板加载区],
    'application': True, 是否是应用
    'installable': True, 是否可网页安装
}

D:\rujian\pc\python.exe D:/desktop/人/main.py 2025-10-09 00:21:31.659255: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'cudart64_110.dll'; dlerror: cudart64_110.dll not found 2025-10-09 00:21:31.660169: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine. C:\Users\xu\AppData\Roaming\Python\Python38\site-packages\tensorflow\python\framework\dtypes.py:585: FutureWarning: In the future `np.object` will be defined as the corresponding NumPy scalar. np.object, Traceback (most recent call last): File "D:/desktop/人/main.py", line 6, in <module> import mediapipe as mp File "D:\rujian\pc\lib\site-packages\mediapipe\__init__.py", line 17, in <module> import mediapipe.tasks.python as tasks File "D:\rujian\pc\lib\site-packages\mediapipe\tasks\python\__init__.py", line 17, in <module> from . import audio File "D:\rujian\pc\lib\site-packages\mediapipe\tasks\python\audio\__init__.py", line 18, in <module> import mediapipe.tasks.python.audio.audio_classifier File "D:\rujian\pc\lib\site-packages\mediapipe\tasks\python\audio\audio_classifier.py", line 26, in <module> from mediapipe.tasks.python.audio.core import base_audio_task_api File "D:\rujian\pc\lib\site-packages\mediapipe\tasks\python\audio\core\base_audio_task_api.py", line 25, in <module> from mediapipe.tasks.python.core.optional_dependencies import doc_controls File "D:\rujian\pc\lib\site-packages\mediapipe\tasks\python\core\optional_dependencies.py", line 20, in <module> from tensorflow.tools.docs import doc_controls File "C:\Users\xu\AppData\Roaming\Python\Python38\site-packages\tensorflow\__init__.py", line 41, in <module> from tensorflow.python.tools import module_util as _module_util File "C:\Users\xu\AppData\Roaming\Python\Python38\site-packages\tensorflow\python\__init__.py", line 46, in <module> from tensorflow.python import data File "C:\Users\xu\AppData\Roaming\Python\Python38\site-packages\tensorflow\python\data\__init__.py", line 25, in <module> from tensorflow.python.data import experimental File "C:\Users\xu\AppData\Roaming\Python\Python38\site-packages\tensorflow\python\data\experimental\__init__.py", line 97, in <module> from tensorflow.python.data.experimental import service File "C:\Users\xu\AppData\Roaming\Python\Python38\site-packages\tensorflow\python\data\experimental\service\__init__.py", line 353, in <module> from tensorflow.python.data.experimental.ops.data_service_ops import distribute File "C:\Users\xu\AppData\Roaming\Python\Python38\site-packages\tensorflow\python\data\experimental\ops\data_service_ops.py", line 26, in <module> from tensorflow.python.data.experimental.ops import compression_ops File "C:\Users\xu\AppData\Roaming\Python\Python38\site-packages\tensorflow\python\data\experimental\ops\compression_ops.py", line 20, in <module> from tensorflow.python.data.util import structure File "C:\Users\xu\AppData\Roaming\Python\Python38\site-packages\tensorflow\python\data\util\structure.py", line 26, in <module> from tensorflow.python.data.util import nest File "C:\Users\xu\AppData\Roaming\Python\Python38\site-packages\tensorflow\python\data\util\nest.py", line 40, in <module> from tensorflow.python.framework import sparse_tensor as _sparse_tensor File "C:\Users\xu\AppData\Roaming\Python\Python38\site-packages\tensorflow\python\framework\sparse_tensor.py", line 28, in <module> from tensorflow.python.framework import constant_op File "C:\Users\xu\AppData\Roaming\Python\Python38\site-packages\tensorflow\python\framework\constant_op.py", line 29, in <module> from tensorflow.python.eager import execute File "C:\Users\xu\AppData\Roaming\Python\Python38\site-packages\tensorflow\python\eager\execute.py", line 27, in <module> from tensorflow.python.framework import dtypes File "C:\Users\xu\AppData\Roaming\Python\Python38\site-packages\tensorflow\python\framework\dtypes.py", line 585, in <module> np.object, File "D:\rujian\pc\lib\site-packages\numpy\__init__.py", line 305, in __getattr__ raise AttributeError(__former_attrs__[attr]) AttributeError: module 'numpy' has no attribute 'object'. `np.object` was a deprecated alias for the builtin `object`. To avoid this error in existing code, use `object` by itself. Doing this will not modify any behavior and is safe. The aliases was originally deprecated in NumPy 1.20; for more details and guidance see the original release note at: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
10-10
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-1-232abbca6849> in <module> 1 import os ----> 2 from keras.datasets import mnist 3 (train_images,train_labels),(test_images,test_labels)=mnist.load_data((os.getcwd())+'/mnist.npz') 4 print(type(train_images)) 5 print(type(train_labels)) D:\rujian\pc\lib\site-packages\keras\__init__.py in <module> 18 [keras.io](https://keras.io). 19 """ ---> 20 from keras import distribute 21 from keras import models 22 from keras.engine.input_layer import Input D:\rujian\pc\lib\site-packages\keras\distribute\__init__.py in <module> 16 17 ---> 18 from keras.distribute import sidecar_evaluator D:\rujian\pc\lib\site-packages\keras\distribute\sidecar_evaluator.py in <module> 15 """Python module for evaluation loop.""" 16 ---> 17 import tensorflow.compat.v2 as tf 18 19 # isort: off D:\rujian\pc\lib\site-packages\tensorflow\__init__.py in <module> 35 import typing as _typing 36 ---> 37 from tensorflow.python.tools import module_util as _module_util 38 from tensorflow.python.util.lazy_loader import LazyLoader as _LazyLoader 39 D:\rujian\pc\lib\site-packages\tensorflow\python\__init__.py in <module> 43 from tensorflow.python import distribute 44 # from tensorflow.python import keras ---> 45 from tensorflow.python.feature_column import feature_column_lib as feature_column 46 # from tensorflow.python.layers import layers 47 from tensorflow.python.module import module D:\rujian\pc\lib\site-packages\tensorflow\python\feature_column\feature_column_lib.py in <module> 16 17 # pylint: disable=unused-import,line-too-long,wildcard-import,g-bad-import-order ---> 18 from tensorflow.python.feature_column.feature_column import * 19 from tensorflow.python.feature_column.feature_column_v2 import * 20 from tensorflow.python.feature_column.sequence_feature_column import * D:\rujian\pc\lib\site-packages\tensorflow\python\feature_column\feature_column.py in <module> 141 from tensorflow.python.framework import sparse_tensor as sparse_tensor_lib 142 from tensorflow.python.framework import tensor_shape --> 143 from tensorflow.python.layers import base 144 from tensorflow.python.ops import array_ops 145 from tensorflow.python.ops import check_ops D:\rujian\pc\lib\site-packages\tensorflow\python\layers\base.py in <module> 14 # ============================================================================= 15 """Contains the base Layer class, from which all layers inherit.""" ---> 16 from tensorflow.python.keras.legacy_tf_layers import base 17 18 InputSpec = base.InputSpec D:\rujian\pc\lib\site-packages\tensorflow\python\keras\__init__.py in <module> 23 24 # See b/110718070#comment18 for more details about this import. ---> 25 from tensorflow.python.keras import models 26 27 from tensorflow.python.keras.engine.input_layer import Input D:\rujian\pc\lib\site-packages\tensorflow\python\keras\models.py in <module> 20 from tensorflow.python.keras import metrics as metrics_module 21 from tensorflow.python.keras import optimizer_v1 ---> 22 from tensorflow.python.keras.engine import functional 23 from tensorflow.python.keras.engine import sequential 24 from tensorflow.python.keras.engine import training D:\rujian\pc\lib\site-packages\tensorflow\python\keras\engine\functional.py in <module> 30 from tensorflow.python.keras.engine import input_spec 31 from tensorflow.python.keras.engine import node as node_module ---> 32 from tensorflow.python.keras.engine import training as training_lib 33 from tensorflow.python.keras.engine import training_utils 34 from tensorflow.python.keras.saving.saved_model import network_serialization D:\rujian\pc\lib\site-packages\tensorflow\python\keras\engine\training.py in <module> 52 from tensorflow.python.keras.mixed_precision import loss_scale_optimizer as lso 53 from tensorflow.python.keras.mixed_precision import policy ---> 54 from tensorflow.python.keras.saving import hdf5_format 55 from tensorflow.python.keras.saving import save 56 from tensorflow.python.keras.saving import saving_utils D:\rujian\pc\lib\site-packages\tensorflow\python\keras\saving\hdf5_format.py in <module> 35 # pylint: disable=g-import-not-at-top 36 try: ---> 37 import h5py 38 HDF5_OBJECT_HEADER_LIMIT = 64512 39 except ImportError: D:\rujian\pc\lib\site-packages\h5py\__init__.py in <module> 44 _errors.silence_errors() 45 ---> 46 from ._conv import register_converters as _register_converters 47 _register_converters() 48 h5py\h5t.pxd in init h5py._conv() h5py\h5t.pyx in init h5py.h5t() ~\AppData\Roaming\Python\Python38\site-packages\numpy\__init__.py in __getattr__(attr) 318 return Tester 319 --> 320 raise AttributeError("module {!r} has no attribute " 321 "{!r}".format(__name__, attr)) 322 AttributeError: module 'numpy' has no attribute 'typeDict'
最新发布
10-24
完整代码怎么修改,import contextlib import copy import functools import pprint import textwrap import typing import blinker # 新版本 blinker 中的等效调用 from blinker import saferef # 替代原 _saferef from seleniumwire.thirdparty.mitmproxy import exceptions from seleniumwire.thirdparty.mitmproxy.utils import typecheck """ The base implementation for Options. """ unset = object() class _Option: __slots__ = ("name", "typespec", "value", "_default", "choices", "help") def __init__( self, name: str, typespec: typing.Union[type, object], # object for Optional[x], which is not a type. default: typing.Any, help: str, choices: typing.Optional[typing.Sequence[str]] ) -> None: typecheck.check_option_type(name, default, typespec) self.name = name self.typespec = typespec self._default = default self.value = unset self.help = textwrap.dedent(help).strip().replace("\n", " ") self.choices = choices def __repr__(self): return "{value} [{type}]".format(value=self.current(), type=self.typespec) @property def default(self): return copy.deepcopy(self._default) def current(self) -> typing.Any: if self.value is unset: v = self.default else: v = self.value return copy.deepcopy(v) def set(self, value: typing.Any) -> None: typecheck.check_option_type(self.name, value, self.typespec) self.value = value def reset(self) -> None: self.value = unset def has_changed(self) -> bool: return self.current() != self.default def __eq__(self, other) -> bool: for i in self.__slots__: if getattr(self, i) != getattr(other, i): return False return True def __deepcopy__(self, _): o = _Option( self.name, self.typespec, self.default, self.help, self.choices ) if self.has_changed(): o.value = self.current() return o class OptManager: """ OptManager is the base class from which Options objects are derived. .changed is a blinker Signal that triggers whenever options are updated. If any handler in the chain raises an exceptions.OptionsError exception, all changes are rolled back, the exception is suppressed, and the .errored signal is notified. Optmanager always returns a deep copy of options to ensure that mutation doesn't change the option state inadvertently. """ def __init__(self): self.deferred: typing.Dict[str, str] = {} self.changed = blinker.Signal() self.errored = blinker.Signal() # Options must be the last attribute here - after that, we raise an # error for attribute assigment to unknown options. self._options: typing.Dict[str, typing.Any] = {} def add_option( self, name: str, typespec: typing.Union[type, object], default: typing.Any, help: str, choices: typing.Optional[typing.Sequence[str]] = None ) -> None: self._options[name] = _Option(name, typespec, default, help, choices) self.changed.send(self, updated={name}) @contextlib.contextmanager def rollback(self, updated, reraise=False): old = copy.deepcopy(self._options) try: yield except exceptions.OptionsError as e: # Notify error handlers self.errored.send(self, exc=e) # Rollback self.__dict__["_options"] = old self.changed.send(self, updated=updated) if reraise: raise e def subscribe(self, func, opts): """ Subscribe a callable to the .changed signal, but only for a specified list of options. The callable should accept arguments (options, updated), and may raise an OptionsError. The event will automatically be unsubscribed if the callable goes out of scope. """ for i in opts: if i not in self._options: raise exceptions.OptionsError("No such option: %s" % i) # We reuse blinker's safe reference functionality to cope with weakrefs # to bound methods. func = blinker._saferef.safe_ref(func) @functools.wraps(func) def _call(options, updated): if updated.intersection(set(opts)): f = func() if f: f(options, updated) else: self.changed.disconnect(_call) # Our wrapper function goes out of scope immediately, so we have to set # weakrefs to false. This means we need to keep our own weakref, and # clean up the hook when it's gone. self.changed.connect(_call, weak=False) def __eq__(self, other): if isinstance(other, OptManager): return self._options == other._options return False def __deepcopy__(self, memodict = None): o = OptManager() o.__dict__["_options"] = copy.deepcopy(self._options, memodict) return o __copy__ = __deepcopy__ def __getattr__(self, attr): if attr in self._options: return self._options[attr].current() else: raise AttributeError("No such option: %s" % attr) def __setattr__(self, attr, value): # This is slightly tricky. We allow attributes to be set on the instance # until we have an _options attribute. After that, assignment is sent to # the update function, and will raise an error for unknown options. opts = self.__dict__.get("_options") if not opts: super().__setattr__(attr, value) else: self.update(**{attr: value}) def keys(self): return set(self._options.keys()) def items(self): return self._options.items() def __contains__(self, k): return k in self._options def reset(self): """ Restore defaults for all options. """ for o in self._options.values(): o.reset() self.changed.send(self, updated=set(self._options.keys())) def update_known(self, **kwargs): """ Update and set all known options from kwargs. Returns a dictionary of unknown options. """ known, unknown = {}, {} for k, v in kwargs.items(): if k in self._options: known[k] = v else: unknown[k] = v updated = set(known.keys()) if updated: with self.rollback(updated, reraise=True): for k, v in known.items(): self._options[k].set(v) self.changed.send(self, updated=updated) return unknown def update_defer(self, **kwargs): unknown = self.update_known(**kwargs) self.deferred.update(unknown) def update(self, **kwargs): u = self.update_known(**kwargs) if u: raise KeyError("Unknown options: %s" % ", ".join(u.keys())) def setter(self, attr): """ Generate a setter for a given attribute. This returns a callable taking a single argument. """ if attr not in self._options: raise KeyError("No such option: %s" % attr) def setter(x): setattr(self, attr, x) return setter def toggler(self, attr): """ Generate a toggler for a boolean attribute. This returns a callable that takes no arguments. """ if attr not in self._options: raise KeyError("No such option: %s" % attr) o = self._options[attr] if o.typespec != bool: raise ValueError("Toggler can only be used with boolean options") def toggle(): setattr(self, attr, not getattr(self, attr)) return toggle def default(self, option: str) -> typing.Any: return self._options[option].default def has_changed(self, option): """ Has the option changed from the default? """ return self._options[option].has_changed() def merge(self, opts): """ Merge a dict of options into this object. Options that have None value are ignored. Lists and tuples are appended to the current option value. """ toset = {} for k, v in opts.items(): if v is not None: if isinstance(v, (list, tuple)): toset[k] = getattr(self, k) + v else: toset[k] = v self.update(**toset) def __repr__(self): options = pprint.pformat(self._options, indent=4).strip(" {}") if "\n" in options: options = "\n " + options + "\n" return "{mod}.{cls}({{{options}}})".format( mod=type(self).__module__, cls=type(self).__name__, options=options ) def set(self, *spec, defer=False): """ Takes a list of set specification in standard form (option=value). Options that are known are updated immediately. If defer is true, options that are not known are deferred, and will be set once they are added. """ vals = {} unknown = {} for i in spec: parts = i.split("=", maxsplit=1) if len(parts) == 1: optname, optval = parts[0], None else: optname, optval = parts[0], parts[1] if optname in self._options: vals[optname] = self.parse_setval(self._options[optname], optval) else: unknown[optname] = optval if defer: self.deferred.update(unknown) elif unknown: raise exceptions.OptionsError("Unknown options: %s" % ", ".join(unknown.keys())) self.update(**vals) def process_deferred(self): """ Processes options that were deferred in previous calls to set, and have since been added. """ update = {} for optname, optval in self.deferred.items(): if optname in self._options: optval = self.parse_setval(self._options[optname], optval) update[optname] = optval self.update(**update) for k in update.keys(): del self.deferred[k] def parse_setval(self, o: _Option, optstr: typing.Optional[str]) -> typing.Any: """ Convert a string to a value appropriate for the option type. """ if o.typespec in (str, typing.Optional[str]): return optstr elif o.typespec in (int, typing.Optional[int]): if optstr: try: return int(optstr) except ValueError: raise exceptions.OptionsError("Not an integer: %s" % optstr) elif o.typespec == int: raise exceptions.OptionsError("Option is required: %s" % o.name) else: return None elif o.typespec == bool: if optstr == "toggle": return not o.current() if not optstr or optstr == "true": return True elif optstr == "false": return False else: raise exceptions.OptionsError( "Boolean must be \"true\", \"false\", or have the value " "omitted (a synonym for \"true\")." ) elif o.typespec == typing.Sequence[str]: if not optstr: return [] else: return getattr(self, o.name) + [optstr] raise NotImplementedError("Unsupported option type: %s", o.typespec) def make_parser(self, parser, optname, metavar=None, short=None): """ Auto-Create a command-line parser entry for a named option. If the option does not exist, it is ignored. """ if optname not in self._options: return o = self._options[optname] def mkf(l, s): l = l.replace("_", "-") f = ["--%s" % l] if s: f.append("-" + s) return f flags = mkf(optname, short) if o.typespec == bool: g = parser.add_mutually_exclusive_group(required=False) onf = mkf(optname, None) offf = mkf("no-" + optname, None) # The short option for a bool goes to whatever is NOT the default if short: if o.default: offf = mkf("no-" + optname, short) else: onf = mkf(optname, short) g.add_argument( *offf, action="store_false", dest=optname, ) g.add_argument( *onf, action="store_true", dest=optname, help=o.help ) parser.set_defaults(**{optname: None}) elif o.typespec in (int, typing.Optional[int]): parser.add_argument( *flags, action="store", type=int, dest=optname, help=o.help, metavar=metavar, ) elif o.typespec in (str, typing.Optional[str]): parser.add_argument( *flags, action="store", type=str, dest=optname, help=o.help, metavar=metavar, choices=o.choices ) elif o.typespec == typing.Sequence[str]: parser.add_argument( *flags, action="append", type=str, dest=optname, help=o.help + " May be passed multiple times.", metavar=metavar, choices=o.choices, ) else: raise ValueError("Unsupported option type: %s", o.typespec) def dump_dicts(opts, keys: typing.List[str]=None): """ Dumps the options into a list of dict object. Return: A list like: { "anticache": { type: "bool", default: false, value: true, help: "help text"} } """ options_dict = {} keys = keys if keys else opts.keys() for k in sorted(keys): o = opts._options[k] t = typecheck.typespec_to_str(o.typespec) option = { 'type': t, 'default': o.default, 'value': o.current(), 'help': o.help, 'choices': o.choices } options_dict[k] = option return options_dict
08-19
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值