From bdc9d9d36992d8842f53037a313268bc7019a1dc Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 6 Dec 2021 14:20:28 +0000 Subject: [PATCH] [FIX] core; base: lots of docstrings * add configuration for `flake8[flake8-rst-docstring]` * enable docstring-related checks * fix invalid docstrings in odoo's core & `base` * fix a few more bits (mostly missing or incorrect `:param:` info fields) are out of scope for the lint but my editor catches closes odoo/odoo#74604 Signed-off-by: Xavier Morel (xmo) --- odoo/addons/base/models/ir_actions.py | 25 ++- odoo/addons/base/models/ir_actions_report.py | 14 +- odoo/addons/base/models/ir_default.py | 5 + odoo/addons/base/models/ir_fields.py | 4 +- odoo/addons/base/models/ir_filters.py | 1 + odoo/addons/base/models/ir_mail_server.py | 2 + odoo/api.py | 7 +- odoo/fields.py | 9 +- odoo/http.py | 3 +- odoo/models.py | 157 ++++++++++------- odoo/modules/loading.py | 4 + odoo/modules/migration.py | 19 ++- odoo/osv/expression.py | 84 +++++---- odoo/osv/query.py | 8 +- odoo/sql_db.py | 28 +-- odoo/tests/common.py | 34 ++-- odoo/tools/appdirs.py | 170 +++++++++++-------- odoo/tools/config.py | 2 +- odoo/tools/date_utils.py | 6 +- odoo/tools/float_utils.py | 8 +- odoo/tools/func.py | 11 +- odoo/tools/image.py | 122 ++++++------- odoo/tools/js_transpiler.py | 7 +- odoo/tools/mail.py | 40 +++-- odoo/tools/misc.py | 78 ++++----- odoo/tools/populate.py | 12 +- odoo/tools/sql.py | 2 +- odoo/tools/test_reports.py | 6 +- odoo/tools/translate.py | 8 +- odoo/tools/xml_utils.py | 25 +-- setup.cfg | 33 ++++ 31 files changed, 529 insertions(+), 405 deletions(-) diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py index e87e9e45723..522c823692d 100644 --- a/odoo/addons/base/models/ir_actions.py +++ b/odoo/addons/base/models/ir_actions.py @@ -589,22 +589,21 @@ class IrActionsServer(models.Model): :samp:`_run_action_{TYPE}[_multi]` method is called. This allows easy overriding of the server actions. - The `_multi` suffix means the runner can operate on multiple records, + The ``_multi`` suffix means the runner can operate on multiple records, otherwise if there are multiple records the runner will be called once - for each + for each. - :param dict context: context should contain following keys + The call context should contain the following keys: - - active_id: id of the current object (single mode) - - active_model: current model that should equal the action's model - - The following keys are optional: - - - active_ids: ids of the current records (mass mode). If active_ids - and active_id are present, active_ids is given precedence. - - :return: an action_id to be executed, or False is finished correctly without - return action + active_id + id of the current object (single mode) + active_model + current model that should equal the action's model + active_ids (optional) + ids of the current records (mass mode). If ``active_ids`` and + ``active_id`` are present, ``active_ids`` is given precedence. + :return: an ``action_id`` to be executed, or ``False`` is finished + correctly without return action """ res = False for action in self.sudo(): diff --git a/odoo/addons/base/models/ir_actions_report.py b/odoo/addons/base/models/ir_actions_report.py index cc131032480..7f415377510 100644 --- a/odoo/addons/base/models/ir_actions_report.py +++ b/odoo/addons/base/models/ir_actions_report.py @@ -198,7 +198,6 @@ class IrActionsReport(models.Model): '''Retrieve an attachment for a specific record. :param record: The record owning of the attachment. - :param attachment_name: The optional name of the attachment. :return: A recordset of length <=1 or None ''' attachment_name = safe_eval(self.attachment, {'object': record, 'time': time}) if self.attachment else '' @@ -211,13 +210,14 @@ class IrActionsReport(models.Model): ], limit=1) def _postprocess_pdf_report(self, record, buffer): - '''Hook to handle post processing during the pdf report generation. - The basic behavior consists to create a new attachment containing the pdf - base64 encoded. + '''Hook to handle post-processing during the pdf report generation. - :param record_id: The record that will own the attachment. - :param pdf_content: The optional name content of the file to avoid reading both times. - :return: A modified buffer if the previous one has been modified, None otherwise. + The basic behavior consists of creating a new attachment containing the + pdf base64 encoded. + + :param record: The record that will own the attachment. + :param io.BytesIO buffer: The PDF content as a bytes buffer. + :return: The input buffer. ''' attachment_name = safe_eval(self.attachment, {'object': record, 'time': time}) if not attachment_name: diff --git a/odoo/addons/base/models/ir_default.py b/odoo/addons/base/models/ir_default.py index 046c4bfe552..c84df3c727a 100644 --- a/odoo/addons/base/models/ir_default.py +++ b/odoo/addons/base/models/ir_default.py @@ -43,6 +43,9 @@ class IrDefault(models.Model): scope (field, user, company) will be replaced. The value is encoded in JSON to be stored to the database. + :param model_name: + :param field_name: + :param value: :param user_id: may be ``False`` for all users, ``True`` for the current user, or any user id :param company_id: may be ``False`` for all companies, ``True`` for @@ -93,6 +96,8 @@ class IrDefault(models.Model): """ Return the default value for the given field, user and company, or ``None`` if no default is available. + :param model_name: + :param field_name: :param user_id: may be ``False`` for all users, ``True`` for the current user, or any user id :param company_id: may be ``False`` for all companies, ``True`` for diff --git a/odoo/addons/base/models/ir_fields.py b/odoo/addons/base/models/ir_fields.py index f87769b17eb..7aebaea26f2 100644 --- a/odoo/addons/base/models/ir_fields.py +++ b/odoo/addons/base/models/ir_fields.py @@ -84,6 +84,7 @@ class IrFieldsConverter(models.AbstractModel): records matching what :meth:`odoo.osv.orm.Model.write` expects. :param model: :class:`odoo.osv.orm.Model` for the conversion base + :param fromtype: :returns: a converter callable :rtype: (record: dict, logger: (field, error) -> None) -> dict """ @@ -167,11 +168,11 @@ class IrFieldsConverter(models.AbstractModel): it returns. The handling of a warning at the upper levels is the same as ``ValueError`` above. + :param model: :param field: field object to generate a value for :type field: :class:`odoo.fields.Field` :param fromtype: type to convert to something fitting for ``field`` :type fromtype: type | str - :param context: odoo request context :return: a function (fromtype -> field.write_type), if a converter is found :rtype: Callable | None """ @@ -345,7 +346,6 @@ class IrFieldsConverter(models.AbstractModel): ``id`` for an external id and ``.id`` for a database id :param value: value of the reference to match to an actual record - :param context: OpenERP request context :return: a pair of the matched database identifier (if any), the translated user-readable name for the field and the list of warnings diff --git a/odoo/addons/base/models/ir_filters.py b/odoo/addons/base/models/ir_filters.py index 7a5d9c391ae..4fa5d66b01d 100644 --- a/odoo/addons/base/models/ir_filters.py +++ b/odoo/addons/base/models/ir_filters.py @@ -57,6 +57,7 @@ class IrFilters(models.Model): def get_filters(self, model, action_id=None): """Obtain the list of filters available for the user on the given model. + :param int model: id of model to find filters for :param action_id: optional ID of action to restrict filters to this action plus global filters. If missing only global filters are returned. The action does not have to correspond to the model, it may only be diff --git a/odoo/addons/base/models/ir_mail_server.py b/odoo/addons/base/models/ir_mail_server.py index 5a1e34f4278..f0d87502f0f 100644 --- a/odoo/addons/base/models/ir_mail_server.py +++ b/odoo/addons/base/models/ir_mail_server.py @@ -351,6 +351,8 @@ class IrMailServer(models.Model): or 'html'). Default is 'plain'. :param list attachments: list of (filename, filecontents) pairs, where filecontents is a string containing the bytes of the attachment + :param message_id: + :param references: :param list email_cc: optional list of string values for CC header (to be joined with commas) :param list email_bcc: optional list of string values for BCC header (to be joined with commas) :param dict headers: optional map of headers to set on the outgoing mail (may override the diff --git a/odoo/api.py b/odoo/api.py index d0381f50ecf..b623861813e 100644 --- a/odoo/api.py +++ b/odoo/api.py @@ -717,9 +717,10 @@ class Environment(Mapping): @contextmanager def protecting(self, what, records=None): """ Prevent the invalidation or recomputation of fields on records. - The parameters are either: - - ``what`` a collection of fields and ``records`` a recordset, or - - ``what`` a collection of pairs ``(fields, records)``. + The parameters are either: + + - ``what`` a collection of fields and ``records`` a recordset, or + - ``what`` a collection of pairs ``(fields, records)``. """ protected = self._protected try: diff --git a/odoo/fields.py b/odoo/fields.py index 4622a6665e7..07d1336a83f 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -861,6 +861,8 @@ class Field(MetaField('DummyField', (object,), {})): :meth:`BaseModel.write`. If the value represents a recordset, it should be added for prefetching on ``record``. + :param value: + :param record: :param bool validate: when True, field-specific validation of ``value`` will be performed """ @@ -886,6 +888,8 @@ class Field(MetaField('DummyField', (object,), {})): """ Convert ``value`` from the record format to the format returned by method :meth:`BaseModel.read`. + :param value: + :param record: :param bool use_name_get: when True, the value's display name will be computed using :meth:`BaseModel.name_get`, if relevant for the field """ @@ -903,6 +907,8 @@ class Field(MetaField('DummyField', (object,), {})): """ Convert ``value`` from the record format to the format returned by method :meth:`BaseModel.onchange`. + :param value: + :param record: :param names: a tree of field names (for relational fields only) """ return self.convert_to_read(value, record) @@ -1048,6 +1054,7 @@ class Field(MetaField('DummyField', (object,), {})): """ Write the value of ``self`` on ``records``. This method must update the cache and prepare database updates. + :param records: :param value: a value in any format :return: the subset of `records` that have been modified """ @@ -3146,7 +3153,7 @@ class Command(enum.IntEnum): class _RelationalMulti(_Relational): - """ Abstract class for relational fields *2many. """ + r"Abstract class for relational fields \*2many." write_sequence = 20 # Important: the cache contains the ids of all the records in the relation, diff --git a/odoo/http.py b/odoo/http.py index 0f382ae4b09..a6b01e5cb8d 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -1096,8 +1096,7 @@ class OpenERPSession(sessions.Session): identifying that action. The method get_action() can be used to get back the action. - :param the_action: The action to save in the session. - :type the_action: anything + :param action: The action to save in the session. :return: A key identifying the saved action. :rtype: integer """ diff --git a/odoo/models.py b/odoo/models.py index b2be9d6a274..31a6cacd9cd 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -507,7 +507,10 @@ class BaseModel(metaclass=MetaModel): on the records of the current model using the ``child_of`` and ``parent_of`` domain operators. """ - _active_name = None #: field to use for active records + _active_name = None + """field to use for active records, automatically set to either ``"active"`` + or ``"x_active"``. + """ _date_name = 'date' #: field to use for default calendar view _fold_name = 'fold' #: field to determine folded groups in kanban views @@ -529,7 +532,9 @@ class BaseModel(metaclass=MetaModel): # default values for _transient_vacuum() _transient_max_count = lazy_classproperty(lambda _: config.get('osv_memory_count_limit')) + "maximum number of transient records, unlimited if ``0``" _transient_max_hours = lazy_classproperty(lambda _: config.get('transient_age_limit')) + "maximum idle lifetime (in hours), unlimited if ``0``" CONCURRENCY_CHECK_FIELD = '__last_update' @@ -1041,7 +1046,6 @@ class BaseModel(metaclass=MetaModel): """ Export fields for selected objects :param fields_to_export: list of fields - :param raw_data: True to return value in native Python type :rtype: dictionary with a *datas* matrix This method is used when exporting data via client menu @@ -1235,6 +1239,7 @@ class BaseModel(metaclass=MetaModel): a list of sub-records The following sub-fields may be set on the record (by key): + * None is the name_get for the record (to use with name_create/name_search) * "id" is the External ID for the record * ".id" is the Database ID for the record @@ -1302,10 +1307,10 @@ class BaseModel(metaclass=MetaModel): def _convert_records(self, records, log=lambda a: None): """ Converts records from the source iterable (recursive dicts of strings) into forms which can be written to the database (via - self.create or (ir.model.data)._update) + ``self.create`` or ``(ir.model.data)._update``) :returns: a list of triplets of (id, xid, record) - :rtype: list((int|None, str|None, dict)) + :rtype: list[(int|None, str|None, dict)] """ field_names = {name: field.string for name, field in self._fields.items()} if self.env.lang: @@ -1614,12 +1619,19 @@ class BaseModel(metaclass=MetaModel): @api.model def load_views(self, views, options=None): """ Returns the fields_views of given views, along with the fields of - the current model, and optionally its filters for the given action. + the current model, and optionally its filters for the given action. :param views: list of [view_id, view_type] - :param options['toolbar']: True to include contextual actions when loading fields_views - :param options['load_filters']: True to return the model's filters - :param options['action_id']: id of the action to get the filters + :param dict options: a dict optional boolean flags, set to enable: + + ``toolbar`` + includes contextual actions when loading fields_views + ``load_filters`` + returns the model's filters + ``action_id`` + id of the action to get the filters, otherwise loads the global + filters or the model + :return: dictionary with fields_views, fields and optionally filters """ options = options or {} @@ -1702,9 +1714,11 @@ class BaseModel(metaclass=MetaModel): :return: composition of the requested view (including inherited views and extensions) :rtype: dict :raise AttributeError: - * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace' - * if some tag other than 'position' is found in parent view - :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure + + * if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace' + * if some tag other than 'position' is found in parent view + + :raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc... defined on the structure """ self.check_access_rights('read') view = self.env['ir.ui.view'].sudo().browse(view_id) @@ -1739,7 +1753,7 @@ class BaseModel(metaclass=MetaModel): return result def get_formview_id(self, access_uid=None): - """ Return an view id to open the document ``self`` with. This method is + """ Return a view id to open the document ``self`` with. This method is meant to be overridden in addons that want to give specific view ids for example. @@ -1770,7 +1784,7 @@ class BaseModel(metaclass=MetaModel): def get_access_action(self, access_uid=None): """ Return an action to open the document. This method is meant to be overridden in addons that want to give specific access to the document. - By default it opens the formview of the document. + By default, it opens the formview of the document. An optional access_uid holds the user that will access the document that could be different from the current user. @@ -1804,7 +1818,6 @@ class BaseModel(metaclass=MetaModel): :param str order: sort string :param bool count: if True, only counts and returns the number of matching records (default: False) :returns: at most ``limit`` records matching the search criteria - :raise AccessError: * if user tries to bypass access rules for read on the requested object. """ res = self._search(args, offset=offset, limit=limit, order=order, count=count) @@ -1842,7 +1855,7 @@ class BaseModel(metaclass=MetaModel): :attr:`~.display_name` it is important that it resets to the "default" behaviour if the context keys are empty / missing. - :return: list of pairs ``(id, text_repr)`` for each records + :return: list of pairs ``(id, text_repr)`` for each record :rtype: list[(int, str)] """ result = [] @@ -1888,8 +1901,8 @@ class BaseModel(metaclass=MetaModel): matching the optional search domain (``args``). This is used for example to provide suggestions based on a partial - value for a relational field. Sometimes be seen as the inverse - function of :meth:`~.name_get`, but it is not guaranteed to be. + value for a relational field. Should usually behave as the reverse of + :meth:`~.name_get`, but that is ont guaranteed. This method is equivalent to calling :meth:`~.search` with a search domain based on ``display_name`` and then :meth:`~.name_get` on the @@ -2068,7 +2081,8 @@ class BaseModel(metaclass=MetaModel): Assume we group records by month, and we only have data for June, September and December. By default, plotting the result gives something - like: + like:: + ___ ___ | | | | ___ | | @@ -2077,7 +2091,8 @@ class BaseModel(metaclass=MetaModel): The problem is that December data immediately follow September data, which is misleading for the user. Adding explicit zeroes for missing - data gives something like: + data gives something like:: + ___ ___ | | | | ___ | | @@ -2098,7 +2113,8 @@ class BaseModel(metaclass=MetaModel): group with data. If we want to fill groups only between August (fill_from) - and October (fill_to): + and October (fill_to):: + ___ ___ | | | | ___ | | @@ -2106,9 +2122,9 @@ class BaseModel(metaclass=MetaModel): Jun Aug Sep Oct Dec We still get June and December. To filter them out, we should match - `fill_from` and `fill_to` with the domain e.g. ['&', - ('date_field', '>=', 'YYYY-08-01'), - ('date_field', '<', 'YYYY-11-01')]: + `fill_from` and `fill_to` with the domain e.g. ``['&', + ('date_field', '>=', 'YYYY-08-01'), ('date_field', '<', 'YYYY-11-01')]``:: + ___ ___ |___| ___ Aug Sep Oct @@ -2123,7 +2139,8 @@ class BaseModel(metaclass=MetaModel): existing group. If neither `fill_from` nor `fill_to` is specified, and there is no existing group, no group will be returned. - If we set min_groups = 4: + If we set min_groups = 4:: + ___ ___ |___| ___ ___ Aug Sep Oct Nov @@ -2210,12 +2227,15 @@ class BaseModel(metaclass=MetaModel): """ Prepares the GROUP BY and ORDER BY terms for the read_group method. Adds the missing JOIN clause to the query if order should be computed against m2o field. + :param orderby: the orderby definition in the form "%(field)s %(order)s" :param aggregated_fields: list of aggregated fields in the query - :param annotated_groupbys: list of dictionaries returned by _read_group_process_groupby - These dictionaries contains the qualified name of each groupby - (fully qualified SQL name for the corresponding field), - and the (non raw) field name. + :param annotated_groupbys: list of dictionaries returned by + :meth:`_read_group_process_groupby` + + These dictionaries contain the qualified name of each groupby + (fully qualified SQL name for the corresponding field), + and the (non raw) field name. :param osv.Query query: the query under construction :return: (groupby_terms, orderby_terms) """ @@ -2436,7 +2456,7 @@ class BaseModel(metaclass=MetaModel): * __range: (date/datetime only) dictionary with field names as keys mapping to a dictionary with keys: "from" (inclusive) and "to" (exclusive) mapping to a string representation of the temporal bounds of the group - :rtype: [{'field_name_1': value, ...] + :rtype: [{'field_name_1': value, ...}, ...] :raise AccessError: * if user has no read rights on the requested object * if user tries to bypass access rules for read on the requested object """ @@ -2878,10 +2898,8 @@ class BaseModel(metaclass=MetaModel): _logger.warning("parent_path field on model %r should have unaccent disabled. Add `unaccent=False` to the field definition.", self._name) def _add_sql_constraints(self): - """ - - Modify this model's database table constraints so they match the one in - _sql_constraints. + """ Modify this model's database table constraints so they match the one + in _sql_constraints. """ cr = self._cr @@ -2908,7 +2926,7 @@ class BaseModel(metaclass=MetaModel): self._cr.execute(self._sql) # - # Update objects that uses this one to update their _inherits fields + # Update objects that use this one to update their _inherits fields # @api.model @@ -3217,6 +3235,8 @@ Fields: method. In Python code, prefer :meth:`~.browse`. :param fields: list of field names to return (default is all fields) + :param load: loading mode, currently the only option is to set to + ``None`` to avoid loading the ``name_get`` of m2o fields :return: a list of dictionaries mapping field names to their values, with one dictionary per record :raise AccessError: if user has no read rights on some of the given @@ -3436,7 +3456,7 @@ Fields: """ Returns rooturl for a specific given record. - By default, it return the ir.config.parameter of base_url + By default, it returns the ir.config.parameter of base_url but it can be overridden by model. :return: the base url for this record @@ -3558,7 +3578,7 @@ Fields: the current user according to ir.rules. :param operation: one of ``write``, ``unlink`` - :raise UserError: * if current ir.rules do not permit this operation. + :raise UserError: * if current ``ir.rules`` do not permit this operation. :return: None if the operation is allowed """ if self.env.su: @@ -4503,10 +4523,10 @@ Fields: @api.model def _where_calc(self, domain, active_test=True): """Computes the WHERE clause needed to implement an OpenERP domain. - :param domain: the domain to compute - :type domain: list - :param active_test: whether the default filtering of records with ``active`` - field set to ``False`` should be applied. + + :param list domain: the domain to compute + :param bool active_test: whether the default filtering of records with + ``active`` field set to ``False`` should be applied. :return: the query expressing the given domain as provided in domain :rtype: osv.query.Query """ @@ -5088,8 +5108,9 @@ Fields: Defaults to no limit. :param order: Columns to sort result, see ``order`` parameter in :meth:`search`. Defaults to no sort. - :param read_kwargs: All read keywords arguments used to call read(..., **read_kwargs) method - E.g. you can use search_read(..., load='') in order to avoid computing name_get + :param read_kwargs: All read keywords arguments used to call + ``read(..., **read_kwargs)`` method e.g. you can use + ``search_read(..., load='')`` in order to avoid computing name_get :return: List of dictionaries containing the asked fields. :rtype: list(dict). """ @@ -5119,20 +5140,20 @@ Fields: return [index[record.id] for record in records if record.id in index] def toggle_active(self): - """ Inverse the value of the field ``(x_)active`` on the records in ``self``. """ + "Inverses the value of :attr:`active` on the records in ``self``." active_recs = self.filtered(self._active_name) active_recs[self._active_name] = False (self - active_recs)[self._active_name] = True def action_archive(self): - """ Set (x_)active=False on a recordset, by calling toggle_active to - take the corresponding actions according to the model + """Sets :attr:`active` to ``False`` on a recordset, by calling + :meth:`toggle_active` on its currently active records. """ return self.filtered(lambda record: record[self._active_name]).toggle_active() def action_unarchive(self): - """ Set (x_)active=True on a recordset, by calling toggle_active to - take the corresponding actions according to the model + """Sets :attr:`active` to ``True`` on a recordset, by calling + :meth:`toggle_active` on its currently inactive records. """ return self.filtered(lambda record: not record[self._active_name]).toggle_active() @@ -5356,7 +5377,7 @@ Fields: return self.with_context(allowed_company_ids=allowed_company_ids) def with_context(self, *args, **kwargs): - """ with_context([context][, **overrides]) -> records + """ with_context([context][, **overrides]) -> Model Returns a new version of this recordset attached to an extended context. @@ -5374,7 +5395,7 @@ Fields: .. note: The returned recordset has the same prefetch object as ``self``. - """ + """ # noqa: RST210 if (args and 'force_company' in args[0]) or 'force_company' in kwargs: _logger.warning( "Context key 'force_company' is no longer supported. " @@ -5684,9 +5705,9 @@ Fields: """ Process all the pending computations (on all models), and flush all the pending updates to the database. - :param fnames (list): list of field names to flush. If given, + :param list[str] fnames: list of field names to flush. If given, limit the processing to the given fields of the current model. - :param records (Model): if given (together with ``fnames``), limit the + :param Model records: if given (together with ``fnames``), limit the processing to the given records. """ def process(model, id_vals): @@ -6080,7 +6101,7 @@ Fields: recursively_marked = self.env.not_to_compute(field, records) self.env.add_to_compute(field, records) else: - # Dont force the recomputation of compute fields which are + # Don't force the recomputation of compute fields which are # not stored as this is not really necessary. if field.recursive: recursively_marked = records & self.env.cache.get_records(records, field) @@ -6423,7 +6444,7 @@ Fields: names.append(name) # prefetch x2many lines: this speeds up the initial snapshot by avoiding - # to compute fields on new records as much as possible, as that can be + # computing fields on new records as much as possible, as that can be # costly and is not necessary at all for name, subnames in nametree.items(): if subnames and values.get(name): @@ -6742,18 +6763,26 @@ class TransientModel(Model): """Clean the transient records. This unlinks old records from the transient model tables whenever the - "_transient_max_count" or "_max_age" conditions (if any) are reached. - Actual cleaning will happen only once every "_transient_check_time" calls. - This means this method can be called frequently called (e.g. whenever - a new record is created). + :attr:`_transient_max_count` or :attr:`_transient_max_hours` conditions + (if any) are reached. + + Actual cleaning will happen only once every 5 minutes. This means this + method can be called frequently (e.g. whenever a new record is created). + Example with both max_hours and max_count active: - Suppose max_hours = 0.2 (e.g. 12 minutes), max_count = 20, there are 55 rows in the - table, 10 created/changed in the last 5 minutes, an additional 12 created/changed between - 5 and 10 minutes ago, the rest created/changed more then 12 minutes ago. - - age based vacuum will leave the 22 rows created/changed in the last 12 minutes - - count based vacuum will wipe out another 12 rows. Not just 2, otherwise each addition - would immediately cause the maximum to be reached again. - - the 10 rows that have been created/changed the last 5 minutes will NOT be deleted + + Suppose max_hours = 0.2 (aka 12 minutes), max_count = 20, there are + 55 rows in the table, 10 created/changed in the last 5 minutes, an + additional 12 created/changed between 5 and 10 minutes ago, the rest + created/changed more than 12 minutes ago. + + - age based vacuum will leave the 22 rows created/changed in the last 12 + minutes + - count based vacuum will wipe out another 12 rows. Not just 2, + otherwise each addition would immediately cause the maximum to be + reached again. + - the 10 rows that have been created/changed the last 5 minutes will NOT + be deleted """ if self._transient_max_hours: # Age-based expiration diff --git a/odoo/modules/loading.py b/odoo/modules/loading.py index f62b7539d5b..104c269d7d2 100644 --- a/odoo/modules/loading.py +++ b/odoo/modules/loading.py @@ -123,11 +123,15 @@ def force_demo(cr): def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None, models_to_check=None): """Migrates+Updates or Installs all module nodes from ``graph`` + + :param cr: :param graph: graph of module nodes to load :param status: deprecated parameter, unused, left to avoid changing signature in 8.0 :param perform_checks: whether module descriptors should be checked for validity (prints warnings for same cases) :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped + :param report: + :param set models_to_check: :return: list of modules that were installed or updated """ if models_to_check is None: diff --git a/odoo/modules/migration.py b/odoo/modules/migration.py index 0b7ad8f736c..22ed4c2dc0f 100644 --- a/odoo/modules/migration.py +++ b/odoo/modules/migration.py @@ -27,18 +27,21 @@ def load_script(path, module_name): class MigrationManager(object): - """ - This class manage the migration of modules - Migrations files must be python files containing a `migrate(cr, installed_version)` + """ Manages the migration of modules. + + Migrations files must be python files containing a ``migrate(cr, installed_version)`` function. These files must respect a directory tree structure: A 'migrations' folder which contains a folder by version. Version can be 'module' version or 'server.module' version (in this case, the files will only be processed by this version of the server). - Python file names must start by `pre-` or `post-` and will be executed, respectively, - before and after the module initialisation. `end-` scripts are run after all modules have + Python file names must start by ``pre-`` or ``post-`` and will be executed, respectively, + before and after the module initialisation. ``end-`` scripts are run after all modules have been updated. - A special folder named `0.0.0` can contain scripts that will be run on any version change. - In `pre` stage, `0.0.0` scripts are run first, while in `post` and `end`, they are run last. - Example: + + A special folder named ``0.0.0`` can contain scripts that will be run on any version change. + In `pre` stage, ``0.0.0`` scripts are run first, while in ``post`` and ``end``, they are run last. + + Example:: + `-- migrations |-- 1.0 diff --git a/odoo/osv/expression.py b/odoo/osv/expression.py index 0cf60e93a9f..06df7f13501 100644 --- a/odoo/osv/expression.py +++ b/odoo/osv/expression.py @@ -244,6 +244,7 @@ def combine(operator, unit, zero, domains): It is guaranteed to return a normalized domain. + :param operator: :param unit: the identity element of the domains "set" with regard to the operation performed by ``operator``, i.e the domain component ``i`` which, when combined with any domain ``x`` via ``operator``, yields ``x``. @@ -371,12 +372,13 @@ def is_operator(element): def is_leaf(element, internal=False): """ Test whether an object is a valid domain term: + - is a list or tuple - with 3 elements - second element if a valid op :param tuple element: a leaf in form (left, operator, right) - :param boolean internal: allow or not the 'inselect' internal operator + :param bool internal: allow or not the 'inselect' internal operator in the term. This should be always left to False. Note: OLD TODO change the share wizard to use this function. @@ -470,48 +472,58 @@ class expression(object): def parse(self): """ Transform the leaves of the expression - The principle is to pop elements from a leaf stack one at a time. - Each leaf is processed. The processing is a if/elif list of various - cases that appear in the leafs (many2one, function fields, ...). - Three things can happen as a processing result: - - the leaf is a logic operator, and updates the result stack - accordingly; - - the leaf has been modified and/or new leafs have to be introduced - in the expression; they are pushed into the leaf stack, to be - processed right after; - - the leaf is converted to SQL and added to the result stack + The principle is to pop elements from a leaf stack one at a time. + Each leaf is processed. The processing is a if/elif list of various + cases that appear in the leafs (many2one, function fields, ...). - Here is a suggested execution: + Three things can happen as a processing result: - step stack result_stack + - the leaf is a logic operator, and updates the result stack + accordingly; + - the leaf has been modified and/or new leafs have to be introduced + in the expression; they are pushed into the leaf stack, to be + processed right after; + - the leaf is converted to SQL and added to the result stack - ['&', A, B] [] - substitute B ['&', A, B1] [] - convert B1 in SQL ['&', A] ["B1"] - substitute A ['&', '|', A1, A2] ["B1"] - convert A2 in SQL ['&', '|', A1] ["B1", "A2"] - convert A1 in SQL ['&', '|'] ["B1", "A2", "A1"] - apply operator OR ['&'] ["B1", "A1 or A2"] - apply operator AND [] ["(A1 or A2) and B1"] + Example: - Some internal var explanation: - :var list path: left operand seen as a sequence of field names - ("foo.bar" -> ["foo", "bar"]) - :var obj model: model object, model containing the field - (the name provided in the left operand) - :var obj field: the field corresponding to `path[0]` - :var obj column: the column corresponding to `path[0]` - :var obj comodel: relational model of field (field.comodel) - (res_partner.bank_ids -> res.partner.bank) + =================== =================== ===================== + step stack result_stack + =================== =================== ===================== + ['&', A, B] [] + substitute B ['&', A, B1] [] + convert B1 in SQL ['&', A] ["B1"] + substitute A ['&', '|', A1, A2] ["B1"] + convert A2 in SQL ['&', '|', A1] ["B1", "A2"] + convert A1 in SQL ['&', '|'] ["B1", "A2", "A1"] + apply operator OR ['&'] ["B1", "A1 or A2"] + apply operator AND [] ["(A1 or A2) and B1"] + =================== =================== ===================== + + Some internal var explanation: + + :var list path: left operand seen as a sequence of field names + ("foo.bar" -> ["foo", "bar"]) + :var obj model: model object, model containing the field + (the name provided in the left operand) + :var obj field: the field corresponding to `path[0]` + :var obj column: the column corresponding to `path[0]` + :var obj comodel: relational model of field (field.comodel) + (res_partner.bank_ids -> res.partner.bank) """ def to_ids(value, comodel, leaf): """ Normalize a single id or name, or a list of those, into a list of ids - :param {int,long,basestring,list,tuple} value: - if int, long -> return [value] - if basestring, convert it into a list of basestrings, then - if list of basestring -> - perform a name_search on comodel for each name - return the list of related ids + + :param comodel: + :param leaf: + :param int|str|list|tuple value: + + - if int, long -> return [value] + - if basestring, convert it into a list of basestrings, then + - if list of basestring -> + + - perform a name_search on comodel for each name + - return the list of related ids """ names = [] if isinstance(value, str): diff --git a/odoo/osv/query.py b/odoo/osv/query.py index 319abe101cd..0415704385d 100644 --- a/odoo/osv/query.py +++ b/odoo/osv/query.py @@ -22,14 +22,16 @@ def _from_table(table, alias): def _generate_table_alias(src_table_alias, link): """ Generate a standard table alias name. An alias is generated as following: + - the base is the source table name (that can already be an alias) - then, the joined table is added in the alias using a 'link field name' that is used to render unique aliases for a given path - the name is shortcut if it goes beyond PostgreSQL's identifier limits - Examples: - - src_table_alias='res_users', link='parent_id' - alias = 'res_users__parent_id' + .. code-block:: pycon + + >>> _generate_table_alias('res_users', link='parent_id') + 'res_users__parent_id' :param str src_table_alias: alias of the source table :param str link: field name diff --git a/odoo/sql_db.py b/odoo/sql_db.py index 784eb5df43f..df9f144ab6b 100644 --- a/odoo/sql_db.py +++ b/odoo/sql_db.py @@ -569,19 +569,21 @@ class TestCursor(BaseCursor): the transaction open across requests, and simulates committing, rolling back, and closing: - test cursor | queries on actual cursor - ------------------------+--------------------------------------- - cr = TestCursor(...) | SAVEPOINT test_cursor_N - | - cr.execute(query) | query - | - cr.commit() | RELEASE SAVEPOINT test_cursor_N - | SAVEPOINT test_cursor_N (lazy) - | - cr.rollback() | ROLLBACK TO SAVEPOINT test_cursor_N (if savepoint) - | - cr.close() | ROLLBACK TO SAVEPOINT test_cursor_N (if savepoint) - | RELEASE SAVEPOINT test_cursor_N (if savepoint) + +------------------------+---------------------------------------------------+ + | test cursor | queries on actual cursor | + +========================+===================================================+ + |``cr = TestCursor(...)``| SAVEPOINT test_cursor_N | + +------------------------+---------------------------------------------------+ + | ``cr.execute(query)`` | query | + +------------------------+---------------------------------------------------+ + | ``cr.commit()`` | RELEASE SAVEPOINT test_cursor_N | + | | SAVEPOINT test_cursor_N (lazy) | + +------------------------+---------------------------------------------------+ + | ``cr.rollback()`` | ROLLBACK TO SAVEPOINT test_cursor_N (if savepoint)| + +------------------------+---------------------------------------------------+ + | ``cr.close()`` | ROLLBACK TO SAVEPOINT test_cursor_N (if savepoint)| + | | RELEASE SAVEPOINT test_cursor_N (if savepoint) | + +------------------------+---------------------------------------------------+ """ _cursors_stack = [] def __init__(self, cursor, lock): diff --git a/odoo/tests/common.py b/odoo/tests/common.py index c08714cf449..96a021ea92a 100644 --- a/odoo/tests/common.py +++ b/odoo/tests/common.py @@ -305,11 +305,10 @@ class MetaCase(type): def _normalize_arch_for_assert(arch_string, parser_method="xml"): """Takes some xml and normalize it to make it comparable to other xml in particular, blank text is removed, and the output is pretty-printed - :param arch_string: the string representing an XML arch - :type arch_string: str - :param parser_method: an string representing which lxml.Parser class to use + + :param str arch_string: the string representing an XML arch + :param str parser_method: an string representing which lxml.Parser class to use when normalizing both archs. Takes either "xml" or "html" - :type parser_method: str :return: the normalized arch :rtype str: """ @@ -671,6 +670,7 @@ class BaseCase(unittest.TestCase, metaclass=MetaCase): def _assertXMLEqual(self, original, expected, parser="xml"): """Asserts that two xmls archs are equal + :param original: the xml arch to test :type original: str :param expected: the xml arch of reference @@ -981,16 +981,24 @@ class ChromeBrowser: self._logger.info('Chrome headless temporary user profile dir: %s', self.user_data_dir) def _json_command(self, command, timeout=3, get_key=None): - """ - Inspect dev tools with get + """Queries browser state using JSON + Available commands: - '' : return list of tabs with their id - list (or json/): list tabs - new : open a new tab - activate/ + an id: activate a tab - close/ + and id: close a tab - version : get chrome and dev tools version - protocol : get the full protocol + + ``''`` + return list of tabs with their id + ``list`` (or ``json/``) + list tabs + ``new`` + open a new tab + :samp:`activate/{id}` + activate a tab + :samp:`close/{id}` + close a tab + ``version`` + get chrome and dev tools version + ``protocol`` + get the full protocol """ command = os.path.join('json', command).strip('/') url = werkzeug.urls.url_join('http://%s:%s/' % (HOST, self.devtools_port), command) diff --git a/odoo/tools/appdirs.py b/odoo/tools/appdirs.py index 5f5b7bd9d7d..1bb0972c76c 100644 --- a/odoo/tools/appdirs.py +++ b/odoo/tools/appdirs.py @@ -86,20 +86,26 @@ def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): of your app to be able to run independently. If used, this would typically be ".". Only applied when appname is present. - "multipath" is an optional parameter only applicable to *nix + "multipath" is an optional parameter only applicable to \*nix which indicates that the entire list of data dirs should be returned. By default, the first item from XDG_DATA_DIRS is - returned, or '/usr/local/share/', - if XDG_DATA_DIRS is not set + returned, or :samp:`/usr/local/share/{AppName}`, + if ``XDG_DATA_DIRS`` is not set Typical user data directories are: - Mac OS X: /Library/Application Support/ - Unix: /usr/local/share/ or /usr/share/ - Win XP: C:\Documents and Settings\All Users\Application Data\\ - Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) - Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7. - For Unix, this is using the $XDG_DATA_DIRS[0] default. + Mac OS X + :samp:`/Library/Application Support/{AppName}` + Unix + :samp:`/usr/local/share/{AppName}` or :samp:`/usr/share/{AppName}` + Win XP + :samp:`C:\Documents and Settings\All Users\Application Data\{AppAuthor}\{AppName}` + Vista + Fail! "C:\ProgramData" is a hidden *system* directory on Vista. + Win 7 + :samp:`C:\ProgramData\{AppAuthor}\{AppName}` (hidden, but writeable on Win 7) + + For Unix, this is using the ``$XDG_DATA_DIRS[0]`` default. WARNING: Do not use this on Windows. See the Vista-Fail note above for why. """ @@ -136,32 +142,36 @@ def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): - r"""Return full path to the user-specific config dir for this application. + """Return full path to the user-specific config dir for this application. - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only required and used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be ".". - Only applied when appname is present. - "roaming" (boolean, default False) can be set True to use the Windows - roaming appdata directory. That means that for users on a Windows - network setup for roaming profiles, this user data will be - sync'd on login. See - - for a discussion of issues. + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only required and used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See `managing roaming user data + `_ + for a discussion of issues. Typical user data directories are: - Mac OS X: same as user_data_dir - Unix: ~/.config/ # or in $XDG_CONFIG_HOME, if defined - Win *: same as user_data_dir - For Unix, we follow the XDG spec and support $XDG_DATA_HOME. - That means, by default "~/.local/share/". + Mac OS X + same as user_data_dir + Unix + :samp:`~/.config/{AppName}` or in $XDG_CONFIG_HOME, if defined + Win * + same as user_data_dir + + For Unix, we follow the XDG spec and support ``$XDG_DATA_HOME``. + That means, by default :samp:`~/.local/share/{AppName}`. """ if sys.platform in [ "win32", "darwin" ]: path = user_data_dir(appname, appauthor, None, roaming) @@ -177,29 +187,34 @@ def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): """Return full path to the user-shared data dir for this application. - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only required and used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be ".". - Only applied when appname is present. - "multipath" is an optional parameter only applicable to *nix - which indicates that the entire list of config dirs should be - returned. By default, the first item from XDG_CONFIG_DIRS is - returned, or '/etc/xdg/', if XDG_CONFIG_DIRS is not set + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only required and used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to \*nix + which indicates that the entire list of config dirs should be + returned. By default, the first item from ``XDG_CONFIG_DIRS`` is + returned, or :samp:`/etc/xdg/{AppName}`, if ``XDG_CONFIG_DIRS`` is not set Typical user data directories are: - Mac OS X: same as site_data_dir - Unix: /etc/xdg/ or $XDG_CONFIG_DIRS[i]/ for each value in - $XDG_CONFIG_DIRS - Win *: same as site_data_dir - Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) - For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False + Mac OS X + same as site_data_dir + Unix + ``/etc/xdg/`` or ``$XDG_CONFIG_DIRS[i]/`` for each + value in ``$XDG_CONFIG_DIRS`` + Win * + same as site_data_dir + Vista + Fail! "C:\ProgramData" is a hidden *system* directory on Vista. + + For Unix, this is using the ``$XDG_CONFIG_DIRS[0]`` default, if ``multipath=False`` WARNING: Do not use this on Windows. See the Vista-Fail note above for why. """ @@ -226,34 +241,41 @@ def site_config_dir(appname=None, appauthor=None, version=None, multipath=False) def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): r"""Return full path to the user-specific cache dir for this application. - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only required and used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be ".". - Only applied when appname is present. - "opinion" (boolean) can be False to disable the appending of - "Cache" to the base app data dir for Windows. See - discussion below. + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only required and used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Cache" to the base app data dir for Windows. See + discussion below. Typical user cache directories are: - Mac OS X: ~/Library/Caches/ - Unix: ~/.cache/ (XDG default) - Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache - Vista: C:\Users\\AppData\Local\\\Cache + + Mac OS X + ~/Library/Caches/ + Unix + ~/.cache/ (XDG default) + Win XP + C:\Documents and Settings\\Local Settings\Application Data\\\Cache + Vista + C:\Users\\AppData\Local\\\Cache On Windows the only suggestion in the MSDN docs is that local settings go in - the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming - app data dir (the default returned by `user_data_dir` above). Apps typically + the ``CSIDL_LOCAL_APPDATA`` directory. This is identical to the non-roaming + app data dir (the default returned by ``user_data_dir`` above). Apps typically put cache data somewhere *under* the given dir here. Some examples: - ...\Mozilla\Firefox\Profiles\\Cache - ...\Acme\SuperApp\Cache\1.0 - OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. - This can be disabled with the `opinion=False` option. + + - ...\Mozilla\Firefox\Profiles\\Cache + - ...\Acme\SuperApp\Cache\1.0 + + OPINION: This function appends "Cache" to the ``CSIDL_LOCAL_APPDATA`` value. + This can be disabled with the ``opinion=False`` option. """ if sys.platform == "win32": if appauthor is None: diff --git a/odoo/tools/config.py b/odoo/tools/config.py index 7c320372158..354e2467d97 100644 --- a/odoo/tools/config.py +++ b/odoo/tools/config.py @@ -291,7 +291,7 @@ class configmanager(object): "[ipython|ptpython|bpython|python]") group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", my_default=False, help="stop the server after its initialization") - group.add_option("--osv-memory-count-limit", dest="osv_memory_count_limit", my_default=False, + group.add_option("--osv-memory-count-limit", dest="osv_memory_count_limit", my_default=0, help="Force a limit on the maximum number of records kept in the virtual " "osv_memory tables. By default there is no limit.", type="int") diff --git a/odoo/tools/date_utils.py b/odoo/tools/date_utils.py index 9d934448e30..ad471c2d600 100644 --- a/odoo/tools/date_utils.py +++ b/odoo/tools/date_utils.py @@ -216,9 +216,9 @@ def json_default(obj): def date_range(start, end, step=relativedelta(months=1)): """Date range generator with a step interval. - :param start datetime: beginning date of the range. - :param end datetime: ending date of the range. - :param step relativedelta: interval of the range. + :param datetime start: beginning date of the range. + :param datetime end: ending date of the range. + :param relativedelta step: interval of the range. :return: a range of datetime from start to end. :rtype: Iterator[datetime] """ diff --git a/odoo/tools/float_utils.py b/odoo/tools/float_utils.py index 6d5d14fd5fb..6528b5ade4b 100644 --- a/odoo/tools/float_utils.py +++ b/odoo/tools/float_utils.py @@ -162,13 +162,13 @@ def float_compare(value1, value2, precision_digits=None, precision_rounding=None def float_repr(value, precision_digits): """Returns a string representation of a float with the - the given number of fractional digits. This should not be + given number of fractional digits. This should not be used to perform a rounding operation (this is done via - :meth:`~.float_round`), but only to produce a suitable + :func:`~.float_round`), but only to produce a suitable string representation for a float. - :param int precision_digits: number of fractional digits to - include in the output + :param float value: + :param int precision_digits: number of fractional digits to include in the output """ # Can't use str() here because it seems to have an intrinsic # rounding to 12 significant digits, which causes a loss of diff --git a/odoo/tools/func.py b/odoo/tools/func.py index 17b5f85ab24..551c803c1fb 100644 --- a/odoo/tools/func.py +++ b/odoo/tools/func.py @@ -50,7 +50,7 @@ class lazy_classproperty(lazy_property): def conditional(condition, decorator): """ Decorator for a conditionally applied decorator. - Example: + Example:: @conditional(get_config('use_cache'), ormcache) def fn(): @@ -118,12 +118,13 @@ def classproperty(func): class lazy(object): - """ A proxy to the (memoized) result of a lazy evaluation:: + """ A proxy to the (memoized) result of a lazy evaluation: - foo = lazy(func, arg) # func(arg) is not called yet - bar = foo + 1 # eval func(arg) and add 1 - baz = foo + 2 # use result of func(arg) and add 2 + .. code-block:: + foo = lazy(func, arg) # func(arg) is not called yet + bar = foo + 1 # eval func(arg) and add 1 + baz = foo + 2 # use result of func(arg) and add 2 """ __slots__ = ['_func', '_args', '_kwargs', '_cached_value'] diff --git a/odoo/tools/image.py b/odoo/tools/image.py index 047c3784d5e..2494b6bd5ab 100644 --- a/odoo/tools/image.py +++ b/odoo/tools/image.py @@ -53,6 +53,7 @@ class ImageProcess(): """Initialize the `base64_source` image for processing. :param base64_source: the original image base64 encoded + No processing will be done if the `base64_source` is falsy or if the image is SVG. :type base64_source: string or bytes @@ -61,8 +62,6 @@ class ImageProcess(): excessive before starting to process it. The max allowed resolution is defined by `IMAGE_MAX_RESOLUTION`. :type verify_resolution: bool - - :return: self :rtype: ImageProcess :raise: ValueError if `verify_resolution` is True and the image is too large @@ -99,19 +98,16 @@ class ImageProcess(): and the `output_format` is the same as the original format and the quality is not specified. - :param quality: quality setting to apply. Default to 0. + :param int quality: quality setting to apply. Default to 0. + - for JPEG: 1 is worse, 95 is best. Values above 95 should be - avoided. Falsy values will fallback to 95, but only if the image - was changed, otherwise the original image is returned. + avoided. Falsy values will fallback to 95, but only if the image + was changed, otherwise the original image is returned. - for PNG: set falsy to prevent conversion to a WEB palette. - for other formats: no effect. - :type quality: int - - :param output_format: the output format. Can be PNG, JPEG, GIF, or ICO. + :param str output_format: the output format. Can be PNG, JPEG, GIF, or ICO. Default to the format of the original image. BMP is converted to PNG, other formats than those mentioned above are converted to JPEG. - :type output_format: string - :return: image :rtype: bytes or False """ @@ -160,19 +156,16 @@ class ImageProcess(): applied and the `output_format` is the same as the original format and the quality is not specified. - :param quality: quality setting to apply. Default to 0. + :param int quality: quality setting to apply. Default to 0. + - for JPEG: 1 is worse, 95 is best. Values above 95 should be - avoided. Falsy values will fallback to 95, but only if the image - was changed, otherwise the original image is returned. + avoided. Falsy values will fallback to 95, but only if the image + was changed, otherwise the original image is returned. - for PNG: set falsy to prevent conversion to a WEB palette. - for other formats: no effect. - :type quality: int - - :param output_format: the output format. Can be PNG, JPEG, GIF, or ICO. + :param str output_format: the output format. Can be PNG, JPEG, GIF, or ICO. Default to the format of the original image. BMP is converted to PNG, other formats than those mentioned above are converted to JPEG. - :type output_format: string - :return: image base64 encoded or False :rtype: bytes or False """ @@ -200,12 +193,8 @@ class ImageProcess(): It is currently not supported for GIF because we do not handle all the frames properly. - :param max_width: max width - :type max_width: int - - :param max_height: max height - :type max_height: int - + :param int max_width: max width + :param int max_height: max height :return: self to allow chaining :rtype: ImageProcess """ @@ -238,20 +227,12 @@ class ImageProcess(): It is currently not supported for GIF because we do not handle all the frames properly. - :param max_width: max width - :type max_width: int - - :param max_height: max height - :type max_height: int - - :param center_x: the center of the crop between 0 (left) and 1 (right) - Default to 0.5 (center). - :type center_x: float - - :param center_y: the center of the crop between 0 (top) and 1 (bottom) - Default to 0.5 (center). - :type center_y: float - + :param int max_width: max width + :param int max_height: max height + :param float center_x: the center of the crop between 0 (left) and 1 + (right). Defaults to 0.5 (center). + :param float center_y: the center of the crop between 0 (top) and 1 + (bottom). Defaults to 0.5 (center). :return: self to allow chaining :rtype: ImageProcess """ @@ -332,25 +313,30 @@ def image_process(base64_source, size=(0, 0), verify_resolution=False, quality=0 def average_dominant_color(colors, mitigate=175, max_margin=140): """This function is used to calculate the dominant colors when given a list of colors - There are 5 steps : - 1) Select dominant colors (highest count), isolate its values and remove - it from the current color set. - 2) Set margins according to the prevalence of the dominant color. - 3) Evaluate the colors. Similar colors are grouped in the dominant set - while others are put in the "remaining" list. - 4) Calculate the average color for the dominant set. This is done by - averaging each band and joining them into a tuple. - 5) Mitigate final average and convert it to hex + There are 5 steps: + + 1) Select dominant colors (highest count), isolate its values and remove + it from the current color set. + 2) Set margins according to the prevalence of the dominant color. + 3) Evaluate the colors. Similar colors are grouped in the dominant set + while others are put in the "remaining" list. + 4) Calculate the average color for the dominant set. This is done by + averaging each band and joining them into a tuple. + 5) Mitigate final average and convert it to hex :param colors: list of tuples having: - [0] color count in the image - [1] actual color: tuple(R, G, B, A) - -> these can be extracted from a PIL image using image.getcolors() + + 0. color count in the image + 1. actual color: tuple(R, G, B, A) + + -> these can be extracted from a PIL image using + :meth:`~PIL.Image.Image.getcolors` :param mitigate: maximum value a band can reach :param max_margin: maximum difference from one of the dominant values :returns: a tuple with two items: - [0] the average color of the dominant set as: tuple(R, G, B) - [1] list of remaining colors, used to evaluate subsequent dominant colors + + 0. the average color of the dominant set as: tuple(R, G, B) + 1. list of remaining colors, used to evaluate subsequent dominant colors """ dominant_color = max(colors) dominant_rgb = dominant_color[1][:3] @@ -409,11 +395,10 @@ def image_fix_orientation(image): save the complexity of removing it. :param image: the source image - :type image: PIL.Image - + :type image: ~PIL.Image.Image :return: the resulting image, copy of the source, with orientation fixed or the source image if no operation was applied - :rtype: PIL.Image + :rtype: ~PIL.Image.Image """ getexif = getattr(image, 'getexif', None) or getattr(image, '_getexif', None) # support PIL < 6.0 if getexif: @@ -431,10 +416,7 @@ def base64_to_image(base64_source): :param base64_source: the image base64 encoded :type base64_source: string or bytes - - :return: the PIL image - :rtype: PIL.Image - + :rtype: ~PIL.Image.Image :raise: UserError if the base64 is incorrect or the image can't be identified by PIL """ try: @@ -446,12 +428,9 @@ def base64_to_image(base64_source): def image_apply_opt(image, output_format, **params): """Return the given PIL `image` using `params`. - :param image: the PIL image - :type image: PIL.Image - - :param params: params to expand when calling PIL.Image.save() - :type params: dict - + :type image: ~PIL.Image.Image + :param str output_format: :meth:`~PIL.Image.Image.save`'s ``format`` parameter + :param dict params: params to expand when calling :meth:`~PIL.Image.Image.save` :return: the image formatted :rtype: bytes """ @@ -463,12 +442,9 @@ def image_apply_opt(image, output_format, **params): def image_to_base64(image, output_format, **params): """Return a base64_image from the given PIL `image` using `params`. - :param image: the PIL image - :type image: PIL.Image - - :param params: params to expand when calling PIL.Image.save() - :type params: dict - + :type image: ~PIL.Image.Image + :param str output_format: + :param dict params: params to expand when calling :meth:`~PIL.Image.Image.save` :return: the image base64 encoded :rtype: bytes """ @@ -495,9 +471,7 @@ def image_guess_size_from_field_name(field_name): If it can't be guessed, return (0, 0) instead. - :param field_name: the name of a field - :type field_name: string - + :param str field_name: the name of a field :return: the guessed size :rtype: tuple (width, height) """ diff --git a/odoo/tools/js_transpiler.py b/odoo/tools/js_transpiler.py index 88cc09d44dc..b6326d441af 100644 --- a/odoo/tools/js_transpiler.py +++ b/odoo/tools/js_transpiler.py @@ -2,7 +2,7 @@ This code is what let us use ES6-style modules in odoo. Classic Odoo modules are composed of a top-level :samp:`odoo.define({name},{body_function})` call. This processor will take files starting with an `@odoo-module` annotation (in a comment) and convert them to classic modules. -If any file has the /** odoo-module */ on top of it, it will get processed by this class. +If any file has the ``/** odoo-module */`` on top of it, it will get processed by this class. It performs several operations to get from ES6 syntax to the usual odoo one with minimal changes. This is done on the fly, this not a pre-processing tool. @@ -560,9 +560,10 @@ def remove_index(content): def relative_path_to_module_path(url, path_rel): - """ - Convert the relative path into a module path, which is more generic and fancy. + """Convert the relative path into a module path, which is more generic and + fancy. + :param str url: :param path_rel: a relative path to the current url. :return: module path (@module/...) """ diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py index 29c4fd79d78..499ca3e371d 100644 --- a/odoo/tools/mail.py +++ b/odoo/tools/mail.py @@ -358,16 +358,19 @@ def html2plaintext(html, body_id=None, encoding='utf-8'): return html.strip() -def plaintext2html(text, container_tag=False): - """ Convert plaintext into html. Content of the text is escaped to manage - html entities, using misc.html_escape(). - - all \n,\r are replaced by
- - enclose content into

- - convert url into clickable link - - 2 or more consecutive
are considered as paragraph breaks +def plaintext2html(text, container_tag=None): + r"""Convert plaintext into html. Content of the text is escaped to manage + html entities, using :func:`~odoo.tools.misc.html_escape`. - :param string container_tag: container of the html; by default the - content is embedded into a

+ - all ``\n``, ``\r`` are replaced by ``
`` + - enclose content into ``

`` + - convert url into clickable link + - 2 or more consecutive ``
`` are considered as paragraph breaks + + :param str text: plaintext to convert + :param str container_tag: container of the html; by default the content is + embedded into a ``

`` + :rtype: markupsafe.Markup """ text = misc.html_escape(ustr(text)) @@ -391,15 +394,18 @@ def plaintext2html(text, container_tag=False): final = '<%s>%s' % (container_tag, final, container_tag) return markupsafe.Markup(final) -def append_content_to_html(html, content, plaintext=True, preserve=False, container_tag=False): +def append_content_to_html(html, content, plaintext=True, preserve=False, container_tag=None): """ Append extra content at the end of an HTML snippet, trying to locate the end of the HTML document (, , or EOF), and converting the provided content in html unless ``plaintext`` is False. + Content conversion can be done in two ways: - - wrapping it into a pre (preserve=True) - - use plaintext2html (preserve=False, using container_tag to wrap the - whole content) + + - wrapping it into a pre (``preserve=True``) + - use plaintext2html (``preserve=False``, using ``container_tag`` to + wrap the whole content) + A side-effect of this method is to coerce all HTML tags to lowercase in ``html``, and strip enclosing or tags in content if ``plaintext`` is False. @@ -410,6 +416,8 @@ def append_content_to_html(html, content, plaintext=True, preserve=False, contai be wrapped in a
 tag.
         :param bool preserve: if content is plaintext, wrap it into a 
             instead of converting it into html
+        :param str container_tag: tag to wrap the content into, defaults to `div`.
+        :rtype: markupsafe.Markup
     """
     html = ustr(html)
     if plaintext and preserve:
@@ -513,7 +521,8 @@ def email_normalize(text):
 def email_domain_extract(email):
     """ Extract the company domain to be used by IAP services notably. Domain
     is extracted from email information e.g:
-        - info@proximus.be -> proximus.be
+
+    - info@proximus.be -> proximus.be
     """
     normalized_email = email_normalize(email)
     if normalized_email:
@@ -530,7 +539,8 @@ def email_domain_normalize(domain):
 def url_domain_extract(url):
     """ Extract the company domain to be used by IAP services notably. Domain
     is extracted from an URL e.g:
-        - www.info.proximus.be -> proximus.be
+
+    - www.info.proximus.be -> proximus.be
     """
     parser_results = urlparse(url)
     company_hostname = parser_results.hostname
diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py
index 9d0adefe8e5..b9cb1a4b29e 100644
--- a/odoo/tools/misc.py
+++ b/odoo/tools/misc.py
@@ -181,10 +181,10 @@ def file_open(name, mode="r", filter_ext=None):
 
     Examples::
 
-    >>> file_open('hr/static/description/icon.png')
-    >>> file_open('hr/static/description/icon.png', filter_ext=('.png', '.jpg'))
-    >>> with file_open('/opt/odoo/addons/hr/static/description/icon.png', 'rb') as f:
-    ...     contents = f.read()
+        >>> file_open('hr/static/description/icon.png')
+        >>> file_open('hr/static/description/icon.png', filter_ext=('.png', '.jpg'))
+        >>> with file_open('/opt/odoo/addons/hr/static/description/icon.png', 'rb') as f:
+        ...     contents = f.read()
 
     :param name: absolute or relative path to a file located inside an addon
     :param mode: file open mode, as for `open()`
@@ -240,24 +240,25 @@ def reverse_enumerate(l):
     """Like enumerate but in the other direction
 
     Usage::
-    >>> a = ['a', 'b', 'c']
-    >>> it = reverse_enumerate(a)
-    >>> it.next()
-    (2, 'c')
-    >>> it.next()
-    (1, 'b')
-    >>> it.next()
-    (0, 'a')
-    >>> it.next()
-    Traceback (most recent call last):
-      File "", line 1, in 
-    StopIteration
+
+        >>> a = ['a', 'b', 'c']
+        >>> it = reverse_enumerate(a)
+        >>> it.next()
+        (2, 'c')
+        >>> it.next()
+        (1, 'b')
+        >>> it.next()
+        (0, 'a')
+        >>> it.next()
+        Traceback (most recent call last):
+          File "", line 1, in 
+        StopIteration
     """
     return zip(range(len(l)-1, -1, -1), reversed(l))
 
 def partition(pred, elems):
     """ Return a pair equivalent to:
-        ``filter(pred, elems), filter(lambda x: not pred(x), elems)` """
+    ``filter(pred, elems), filter(lambda x: not pred(x), elems)`` """
     yes, nos = [], []
     for elem in elems:
         (yes if pred(elem) else nos).append(elem)
@@ -665,22 +666,6 @@ def split_every(n, iterable, piece_maker=tuple):
         yield piece
         piece = piece_maker(islice(iterator, n))
 
-def get_and_group_by_field(cr, uid, obj, ids, field, context=None):
-    """ Read the values of ``field´´ for the given ``ids´´ and group ids by value.
-
-       :param string field: name of the field we want to read and group by
-       :return: mapping of field values to the list of ids that have it
-       :rtype: dict
-    """
-    res = {}
-    for record in obj.read(cr, uid, ids, [field], context=context):
-        key = record[field]
-        res.setdefault(key[0] if isinstance(key, tuple) else key, []).append(record['id'])
-    return res
-
-def get_and_group_by_company(cr, uid, obj, ids, context=None):
-    return get_and_group_by_field(cr, uid, obj, ids, field='company_id', context=context)
-
 # port of python 2.6's attrgetter with support for dotted notation
 def resolve_attr(obj, attr):
     for name in attr.split("."):
@@ -769,7 +754,8 @@ class UnquoteEvalContext(defaultdict):
 
 class mute_logger(logging.Handler):
     """Temporary suppress the logging.
-    Can be used as context manager or decorator.
+
+    Can be used as context manager or decorator::
 
         @mute_logger('odoo.plic.ploc')
         def do_stuff():
@@ -777,7 +763,6 @@ class mute_logger(logging.Handler):
 
         with mute_logger('odoo.foo.bar'):
             do_suff()
-
     """
     def __init__(self, *loggers):
         super().__init__()
@@ -978,7 +963,9 @@ def freehash(arg):
             return id(arg)
 
 def clean_context(context):
-    """ This function take a dictionary and remove each entry with its key starting with 'default_' """
+    """ This function take a dictionary and remove each entry with its key
+    starting with ``default_``
+    """
     return {k: v for k, v in context.items() if not k.startswith('default_')}
 
 
@@ -1118,6 +1105,8 @@ class Callbacks:
     """ A simple queue of callback functions.  Upon run, every function is
     called (in addition order), and the queue is emptied.
 
+    ::
+
         callbacks = Callbacks()
 
         # add foo
@@ -1142,6 +1131,8 @@ class Callbacks:
     dictionary is automatically cleared by ``run()`` once all callback functions
     have been called.
 
+    ::
+
         # register foo to process aggregated data
         @callbacks.add
         def foo():
@@ -1258,6 +1249,8 @@ def get_lang(env, lang_code=False):
     Retrieve the first lang object installed, by checking the parameter lang_code,
     the context and then the company. If no lang is installed from those variables,
     fallback on the first lang installed in the system.
+
+    :param env:
     :param str lang_code: the locale (i.e. en_US)
     :return res.lang: the first lang found that is installed on the system.
     """
@@ -1367,10 +1360,12 @@ def parse_date(env, value, lang_code=False):
 def format_datetime(env, value, tz=False, dt_format='medium', lang_code=False):
     """ Formats the datetime in a given format.
 
-        :param {str, datetime} value: naive datetime to format either in string or in datetime
-        :param {str} tz: name of the timezone  in which the given datetime should be localized
-        :param {str} dt_format: one of “full”, “long”, “medium”, or “short”, or a custom date/time pattern compatible with `babel` lib
-        :param {str} lang_code: ISO code of the language to use to render the given datetime
+    :param env:
+    :param str|datetime value: naive datetime to format either in string or in datetime
+    :param str tz: name of the timezone  in which the given datetime should be localized
+    :param str dt_format: one of “full”, “long”, “medium”, or “short”, or a custom date/time pattern compatible with `babel` lib
+    :param str lang_code: ISO code of the language to use to render the given datetime
+    :rtype: str
     """
     if not value:
         return ''
@@ -1407,6 +1402,7 @@ def format_datetime(env, value, tz=False, dt_format='medium', lang_code=False):
 def format_time(env, value, tz=False, time_format='medium', lang_code=False):
     """ Format the given time (hour, minute and second) with the current user preference (language, format, ...)
 
+        :param env:
         :param value: the time to format
         :type value: `datetime.time` instance. Could be timezoned to display tzinfo according to format (e.i.: 'full' format)
         :param tz: name of the timezone  in which the given datetime should be localized
@@ -1604,7 +1600,7 @@ def get_diff(data_from, data_to, custom_style=False):
 
 
 def traverse_containers(val, type_):
-    """ Yields atoms filtered by specified type_ (or type tuple), traverses
+    """ Yields atoms filtered by specified ``type_`` (or type tuple), traverses
     through standard containers (non-string mappings or sequences) *unless*
     they're selected by the type filter
     """
diff --git a/odoo/tools/populate.py b/odoo/tools/populate.py
index 0ef7bdf2907..44e654fa0ee 100644
--- a/odoo/tools/populate.py
+++ b/odoo/tools/populate.py
@@ -124,7 +124,7 @@ def compute(function, seed=None):
     as ``function(values, counter, random)``, where ``values`` is the other field values,
     ``counter`` is an integer, and ``random`` is a pseudo-random number generator.
 
-    :param function function: (values, counter, random) --> field_values
+    :param callable function: (values, counter, random) --> field_values
     :param seed: optional initialization of the random number generator
     :returns: function of the form (iterator, field_name, model_name) -> values
     :rtype: function (iterator, str, str) -> dict
@@ -143,6 +143,7 @@ def randint(a, b, seed=None):
 
     :param int a: minimal random value
     :param int b: maximal random value
+    :param int seed:
     :returns: function of the form (iterator, field_name, model_name) -> values
     :rtype: function (iterator, str, str) -> dict
     """
@@ -163,12 +164,13 @@ def randdatetime(*, base_date=None, relative_before=None, relative_after=None, s
     to a random datetime between relative_before and relative_after, relatively to
     base_date
 
-    :param base_date (datetime): override the default base date if needed.
-    :param relative_after (relativedelta, timedelta): range up which we can go after the
+    :param datetime base_date: override the default base date if needed.
+    :param relativedelta|timedelta relative_after: range up which we can go after the
          base date. If not set, defaults to 0, i.e. only in the past of reference.
-    :param relative_before (relativedelta, timedelta): range up which we can go before the
+    :param relativedelta|timedelta relative_before: range up which we can go before the
          base date. If not set, defaults to 0, i.e. only in the future of reference.
-    :return (generator): iterator for random dates inside the defined range
+    :param seed:
+    :return: iterator for random dates inside the defined range
     """
     base_date = base_date or datetime(2020, 1, 1)
     seconds_before = relative_before and ((base_date + relative_before) - base_date).total_seconds() or 0
diff --git a/odoo/tools/sql.py b/odoo/tools/sql.py
index 2bc82358416..93ee6e34752 100644
--- a/odoo/tools/sql.py
+++ b/odoo/tools/sql.py
@@ -254,7 +254,7 @@ def pg_varchar(size=0):
       'infinite' VARCHAR
     * Otherwise return a VARCHAR(n)
 
-    :type int size: varchar size, optional
+    :param int size: varchar size, optional
     :rtype: str
     """
     if size:
diff --git a/odoo/tools/test_reports.py b/odoo/tools/test_reports.py
index 96d6b6fb89e..06ceccea33e 100644
--- a/odoo/tools/test_reports.py
+++ b/odoo/tools/test_reports.py
@@ -76,9 +76,12 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None,
                 context=None, our_module=None):
     """Take an ir.actions.act_window and follow it until a report is produced
 
+        :param cr:
+        :param uid:
         :param action_id: the integer id of an action, or a reference to xml id
                 of the act_window (can search [our_module.]+xml_id
-        :param active_model, active_ids: call the action as if it had been launched
+        :param active_model:
+        :param active_ids: call the action as if it had been launched
                 from that model+ids (tree/form view action)
         :param wiz_data: a dictionary of values to use in the wizard, if needed.
                 They will override (or complete) the default values of the
@@ -86,6 +89,7 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None,
         :param wiz_buttons: a list of button names, or button icon strings, which
                 should be preferred to press during the wizard.
                 Eg. 'OK' or 'fa-print'
+        :param context:
         :param our_module: the name of the calling module (string), like 'account'
     """
     if not our_module and isinstance(action_id, str):
diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py
index 3a984c10523..8a0cdc63a37 100644
--- a/odoo/tools/translate.py
+++ b/odoo/tools/translate.py
@@ -170,6 +170,7 @@ avoid_pattern = re.compile(r"\s*): tag names to be created
-    :param last_node_value (str): if specified, set the last node's text to this value
-    :returns (list): the list of created nodes
+
+    :param etree._Element first_parent_node: parent of the created tree/chain
+    :param iterable[str] nodes_list: tag names to be created
+    :param str last_node_value: if specified, set the last node's text to this value
+    :returns: the list of created nodes
+    :rtype: list[etree._Element]
     """
     res = []
     current_node = first_parent_node
@@ -77,9 +80,9 @@ def create_xml_node_chain(first_parent_node, nodes_list, last_node_value=None):
 def create_xml_node(parent_node, node_name, node_value=None):
     """Create a new node.
 
-    :param parent_node (etree._Element): parent of the created node
-    :param node_name (str): name of the created node
-    :param node_value (str): value of the created node (optional)
-    :returns (etree._Element):
+    :param etree._Element parent_node: parent of the created node
+    :param str node_name: name of the created node
+    :param str node_value: value of the created node (optional)
+    :rtype: etree._Element
     """
     return create_xml_node_chain(parent_node, [node_name], node_value)[0]
diff --git a/setup.cfg b/setup.cfg
index 78003c4d809..bb9b316ca3b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -45,3 +45,36 @@ requires =
   python3-xlwt
   python3-xlrd
   python3-zeep
+
+[flake8]
+exclude =
+  .git,
+  .tx,
+  addons,
+  debian,
+  doc,
+  setup,
+select =
+  RST
+rst-directives =
+  function,
+  attribute,
+  seealso,
+  deprecated,
+  versionadded,
+  versionchanged,
+  todo
+rst-roles =
+  ref,
+  mod,
+  class,
+  py:meth,
+  meth,
+  attr,
+  data,
+  const,
+  func,
+  exc,
+  term,
+  samp,
+  program