Repository navigation
Expand file tree
/
Copy pathutils.py
More file actions
149 lines (118 loc) · 4.66 KB
/
Copy pathutils.py
File metadata and controls
149 lines (118 loc) · 4.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import re
import warnings
from collections.abc import Set as AbstractSet
import dateutil.relativedelta
from odoo.exceptions import AccessError, ValidationError
from odoo.tools import SQL
regex_alphanumeric = re.compile(r'^[a-z0-9_]+$')
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
regex_pg_name = re.compile(r'^[a-z_][a-z0-9_$]*$', re.IGNORECASE)
# match private methods, to prevent their remote invocation
regex_private = re.compile(r'^(_.*|init)$')
# types handled as collections
COLLECTION_TYPES = (list, tuple, AbstractSet)
# The hard-coded super-user id (a.k.a. root user, or OdooBot).
SUPERUSER_ID = 1
# _read_group stuff
READ_GROUP_TIME_GRANULARITY = {
'hour': dateutil.relativedelta.relativedelta(hours=1),
'day': dateutil.relativedelta.relativedelta(days=1),
'week': dateutil.relativedelta.relativedelta(days=7),
'month': dateutil.relativedelta.relativedelta(months=1),
'quarter': dateutil.relativedelta.relativedelta(months=3),
'year': dateutil.relativedelta.relativedelta(years=1)
}
READ_GROUP_NUMBER_GRANULARITY = {
'year_number': 'year',
'quarter_number': 'quarter',
'month_number': 'month',
'iso_week_number': 'week', # ISO week number because anything else than ISO is nonsense
'day_of_year': 'doy',
'day_of_month': 'day',
'day_of_week': 'dow',
'hour_number': 'hour',
'minute_number': 'minute',
'second_number': 'second',
}
READ_GROUP_ALL_TIME_GRANULARITY = READ_GROUP_TIME_GRANULARITY | READ_GROUP_NUMBER_GRANULARITY
# SQL operators with spaces around them
# hardcoded to avoid changing SQL injection linting
SQL_OPERATORS = {
"=": SQL(" = "),
"!=": SQL(" != "),
"in": SQL(" IN "),
"not in": SQL(" NOT IN "),
"<": SQL(" < "),
">": SQL(" > "),
"<=": SQL(" <= "),
">=": SQL(" >= "),
"like": SQL(" LIKE "),
"ilike": SQL(" ILIKE "),
"=like": SQL(" LIKE "),
"=ilike": SQL(" ILIKE "),
"not like": SQL(" NOT LIKE "),
"not ilike": SQL(" NOT ILIKE "),
"not =like": SQL(" NOT LIKE "),
"not =ilike": SQL(" NOT ILIKE "),
}
def check_method_name(name):
""" Raise an ``AccessError`` if ``name`` is a private method name. """
warnings.warn("Since 19.0, use odoo.service.model.get_public_method", DeprecationWarning)
if regex_private.match(name):
raise AccessError('Private methods (such as %s) cannot be called remotely.' % name)
def check_object_name(name):
""" Check if the given name is a valid model name.
The _name attribute in osv and osv_memory object is subject to
some restrictions. This function returns True or False whether
the given name is allowed or not.
TODO: this is an approximation. The goal in this approximation
is to disallow uppercase characters (in some places, we quote
table/column names and in other not, which leads to this kind
of errors:
psycopg2.ProgrammingError: relation "xxx" does not exist).
The same restriction should apply to both osv and osv_memory
objects for consistency.
"""
return regex_object_name.match(name) is not None
def check_pg_name(name):
""" Check whether the given name is a valid PostgreSQL identifier name. """
if not regex_pg_name.match(name):
raise ValidationError("Invalid characters in table name %r" % name)
if len(name) > 63:
raise ValidationError("Table name %r is too long" % name)
def parse_field_expr(field_expr: str) -> tuple[str, str | None]:
if (property_index := field_expr.find(".")) >= 0:
property_name = field_expr[property_index + 1:]
field_expr = field_expr[:property_index]
else:
property_name = None
if not field_expr:
raise ValueError(f"Invalid field expression {field_expr!r}")
return field_expr, property_name
def expand_ids(id0, ids):
""" Return an iterator of unique ids from the concatenation of ``[id0]`` and
``ids``, and of the same kind (all real or all new).
"""
yield id0
seen = {id0}
kind = bool(id0)
for id_ in ids:
if id_ not in seen and bool(id_) == kind:
yield id_
seen.add(id_)
class OriginIds:
""" A reversible iterable returning the origin ids of a collection of ``ids``.
Actual ids are returned as is, and ids without origin are not returned.
"""
__slots__ = ['ids']
def __init__(self, ids):
self.ids = ids
def __iter__(self):
for id_ in self.ids:
if id_ := id_ or getattr(id_, 'origin', None):
yield id_
def __reversed__(self):
for id_ in reversed(self.ids):
if id_ := id_ or getattr(id_, 'origin', None):
yield id_
origin_ids = OriginIds