ormlambda 3.11.1__py3-none-any.whl → 3.12.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. ormlambda/__init__.py +1 -1
  2. ormlambda/caster/caster.py +1 -1
  3. ormlambda/common/abstract_classes/clause_info_converter.py +73 -0
  4. ormlambda/common/abstract_classes/decomposition_query.py +12 -68
  5. ormlambda/common/abstract_classes/non_query_base.py +2 -2
  6. ormlambda/common/interfaces/ICustomAlias.py +1 -1
  7. ormlambda/components/delete/abstract_delete.py +2 -2
  8. ormlambda/components/join/__init__.py +1 -0
  9. ormlambda/databases/my_sql/clauses/delete.py +0 -1
  10. ormlambda/databases/my_sql/clauses/drop_table.py +8 -5
  11. ormlambda/databases/my_sql/clauses/group_by.py +1 -2
  12. ormlambda/databases/my_sql/clauses/select.py +2 -0
  13. ormlambda/databases/my_sql/clauses/upsert.py +8 -4
  14. ormlambda/databases/my_sql/query_builder.py +158 -0
  15. ormlambda/databases/my_sql/statements.py +19 -156
  16. ormlambda/engine/__init__.py +1 -1
  17. ormlambda/engine/url.py +4 -1
  18. ormlambda/sql/clause_info/__init__.py +2 -1
  19. ormlambda/sql/clause_info/aggregate_function_base.py +86 -0
  20. ormlambda/sql/clause_info/clause_info.py +1 -98
  21. ormlambda/sql/clause_info/interface/IClauseInfo.py +37 -0
  22. ormlambda/sql/clause_info/interface/__init__.py +1 -0
  23. ormlambda/sql/column.py +3 -0
  24. ormlambda/statements/base_statement.py +6 -2
  25. ormlambda/statements/interfaces/IStatements.py +25 -19
  26. ormlambda/utils/module_tree/dynamic_module.py +3 -2
  27. {ormlambda-3.11.1.dist-info → ormlambda-3.12.2.dist-info}/METADATA +66 -16
  28. {ormlambda-3.11.1.dist-info → ormlambda-3.12.2.dist-info}/RECORD +31 -28
  29. {ormlambda-3.11.1.dist-info → ormlambda-3.12.2.dist-info}/WHEEL +1 -1
  30. ormlambda/databases/my_sql/caster/read.py +0 -39
  31. ormlambda/databases/my_sql/caster/write.py +0 -37
  32. /ormlambda/{databases/my_sql → components/join}/join_context.py +0 -0
  33. {ormlambda-3.11.1.dist-info → ormlambda-3.12.2.dist-info}/LICENSE +0 -0
ormlambda/__init__.py CHANGED
@@ -21,4 +21,4 @@ from .model.base_model import (
21
21
  ORM as ORM,
22
22
  ) # COMMENT: to avoid relative import we need to import BaseModel after import Table,Column, ForeignKey, IRepositoryBase and Disassembler
23
23
 
24
- from .engine import create_engine, URL # noqa: F401
24
+ from .engine import create_engine, URL, make_url # noqa: F401
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
15
15
  PLACEHOLDER: str = "%s"
16
16
 
17
17
 
18
- class Caster[TRepo]:
18
+ class Caster:
19
19
  def __init__(self, repository: IRepositoryBase):
20
20
  self._repository: IRepositoryBase = repository
21
21
  self._caster = RepositoryTemplateDict().get(repository).caster
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+ import typing as tp
3
+ from ormlambda import Table
4
+
5
+ from ormlambda.sql.clause_info import ClauseInfo, AggregateFunctionBase
6
+ from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
7
+ from ormlambda import ForeignKey
8
+
9
+ from ormlambda.sql.types import AliasType, TableType, ColumnType
10
+
11
+
12
+ class ClauseInfoConverter[T, TProp](tp.Protocol):
13
+ @classmethod
14
+ def convert(cls, data: T, alias_table: AliasType[ColumnType[TProp]] = "{table}", context: ClauseContextType = None, **kwargs) -> list[ClauseInfo[T]]: ...
15
+
16
+
17
+ class ConvertFromAnyType(ClauseInfoConverter[None, None]):
18
+ @classmethod
19
+ def convert(cls, data: tp.Any, alias_table: AliasType = "{table}", context: ClauseContextType = None, **kwargs) -> list[ClauseInfo[None]]:
20
+ return [
21
+ ClauseInfo[None](
22
+ table=None,
23
+ column=data,
24
+ alias_table=alias_table,
25
+ alias_clause=kwargs.get("alias", None),
26
+ context=context,
27
+ )
28
+ ]
29
+
30
+
31
+ class ConvertFromForeignKey[LT: Table, RT: Table](ClauseInfoConverter[RT, None]):
32
+ @classmethod
33
+ def convert(cls, data: ForeignKey[LT, RT], alias_table=None, context: ClauseContextType = None, **kwargs) -> list[ClauseInfo[RT]]:
34
+ return ConvertFromTable[RT].convert(data.tright, data.alias, context, **kwargs)
35
+
36
+
37
+ class ConvertFromColumn[TProp](ClauseInfoConverter[None, TProp]):
38
+ @classmethod
39
+ def convert(cls, data: ColumnType[TProp], alias_table: AliasType[ColumnType[TProp]] = "{table}", context: ClauseContextType = None, **kwargs) -> list[ClauseInfo[None]]:
40
+ # COMMENT: if the property belongs to the main class, the columnn name in not prefixed. This only done if the property comes from any join.
41
+ attributes = {
42
+ "table": data.table,
43
+ "column": data,
44
+ "alias_table": alias_table,
45
+ "alias_clause": "{table}_{column}",
46
+ "context": context,
47
+ **kwargs,
48
+ }
49
+ clause_info = ClauseInfo[None](**attributes)
50
+ return [clause_info]
51
+
52
+
53
+ class ConvertFromIAggregate(ClauseInfoConverter[None, None]):
54
+ @classmethod
55
+ def convert(cls, data: AggregateFunctionBase, alias_table=None, context: ClauseContextType = None, **kwargs) -> list[ClauseInfo[None]]:
56
+ return [data]
57
+
58
+
59
+ class ConvertFromTable[T: Table](ClauseInfoConverter[T, None]):
60
+ @classmethod
61
+ def convert(cls, data: T, alias_table: AliasType[ColumnType] = "{table}", context: ClauseContextType = None, **kwargs) -> list[ClauseInfo[T]]:
62
+ """
63
+ if the data is Table, means that we want to retrieve all columns
64
+ """
65
+ return cls._extract_all_clauses(data, alias_table, context, **kwargs)
66
+
67
+ @staticmethod
68
+ def _extract_all_clauses(table: TableType[T], alias_table: AliasType[ColumnType], context: ClauseContextType = None, **kwargs) -> list[ClauseInfo[TableType[T]]]:
69
+ # all columns
70
+ column_clauses = []
71
+ for column in table.get_columns():
72
+ column_clauses.extend(ConvertFromColumn.convert(column, alias_table=alias_table, context=context, **kwargs))
73
+ return column_clauses
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
  import typing as tp
3
- import abc
4
3
  from ormlambda import Table, Column
5
4
 
6
5
  from ormlambda.common.interfaces import IDecompositionQuery, ICustomAlias
@@ -10,8 +9,15 @@ from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, Cla
10
9
  from ormlambda import ForeignKey
11
10
  from ormlambda.common.global_checker import GlobalChecker
12
11
 
13
- from ormlambda.sql.types import AliasType, TableType, ColumnType
14
-
12
+ from ormlambda.sql.types import TableType, ColumnType
13
+ from .clause_info_converter import (
14
+ ClauseInfoConverter,
15
+ ConvertFromAnyType,
16
+ ConvertFromForeignKey,
17
+ ConvertFromColumn,
18
+ ConvertFromIAggregate,
19
+ ConvertFromTable,
20
+ )
15
21
 
16
22
  type TableTupleType[T, *Ts] = tuple[T:TableType, *Ts]
17
23
  type ValueType = tp.Union[
@@ -22,69 +28,6 @@ type ValueType = tp.Union[
22
28
  ]
23
29
 
24
30
 
25
- class ClauseInfoConverter[T, TProp](abc.ABC):
26
- @classmethod
27
- @abc.abstractmethod
28
- def convert(cls, data: T, alias_table: AliasType[ColumnType[TProp]] = "{table}", context: ClauseContextType = None) -> list[ClauseInfo[T]]: ...
29
-
30
-
31
- class ConvertFromAnyType(ClauseInfoConverter[None, None]):
32
- @classmethod
33
- def convert(cls, data: tp.Any, alias_table: AliasType = "{table}", context: ClauseContextType = None) -> list[ClauseInfo[None]]:
34
- return [
35
- ClauseInfo[None](
36
- table=None,
37
- column=data,
38
- alias_table=alias_table,
39
- alias_clause=None,
40
- context=context,
41
- )
42
- ]
43
-
44
-
45
- class ConvertFromForeignKey[LT: Table, RT: Table](ClauseInfoConverter[RT, None]):
46
- @classmethod
47
- def convert(cls, data: ForeignKey[LT, RT], alias_table=None, context: ClauseContextType = None) -> list[ClauseInfo[RT]]:
48
- return ConvertFromTable[RT].convert(data.tright, data.alias, context)
49
-
50
-
51
- class ConvertFromColumn[TProp](ClauseInfoConverter[None, TProp]):
52
- @classmethod
53
- def convert(cls, data: ColumnType[TProp], alias_table: AliasType[ColumnType[TProp]] = "{table}", context: ClauseContextType = None) -> list[ClauseInfo[None]]:
54
- # COMMENT: if the property belongs to the main class, the columnn name in not prefixed. This only done if the property comes from any join.
55
- clause_info = ClauseInfo[None](
56
- table=data.table,
57
- column=data,
58
- alias_table=alias_table,
59
- alias_clause="{table}_{column}",
60
- context=context,
61
- )
62
- return [clause_info]
63
-
64
-
65
- class ConvertFromIAggregate(ClauseInfoConverter[None, None]):
66
- @classmethod
67
- def convert(cls, data: AggregateFunctionBase, alias_table=None, context: ClauseContextType = None) -> list[ClauseInfo[None]]:
68
- return [data]
69
-
70
-
71
- class ConvertFromTable[T: Table](ClauseInfoConverter[T, None]):
72
- @classmethod
73
- def convert(cls, data: T, alias_table: AliasType[ColumnType] = "{table}", context: ClauseContextType = None) -> list[ClauseInfo[T]]:
74
- """
75
- if the data is Table, means that we want to retrieve all columns
76
- """
77
- return cls._extract_all_clauses(data, alias_table, context)
78
-
79
- @staticmethod
80
- def _extract_all_clauses(table: TableType[T], alias_table: AliasType[ColumnType], context: ClauseContextType = None) -> list[ClauseInfo[TableType[T]]]:
81
- # all columns
82
- column_clauses = []
83
- for column in table.get_columns():
84
- column_clauses.extend(ConvertFromColumn.convert(column, alias_table=alias_table, context=context))
85
- return column_clauses
86
-
87
-
88
31
  class DecompositionQueryBase[T: Table, *Ts](IDecompositionQuery[T, *Ts]):
89
32
  @tp.overload
90
33
  def __init__(self, tables: tuple[TableType[T]], columns: tuple[ColumnType]) -> None: ...
@@ -93,7 +36,8 @@ class DecompositionQueryBase[T: Table, *Ts](IDecompositionQuery[T, *Ts]):
93
36
  @tp.overload
94
37
  def __init__(self, tables: tuple[TableType[T]], columns: tuple[ColumnType], alias_table: str, context: ClauseContextType = ...) -> None: ...
95
38
 
96
- def __init__(self, tables: tuple[TableType[T]], columns: tuple[ColumnType], alias_table: str = "{table}", *, context: ClauseContextType = ClauseInfoContext()) -> None:
39
+ def __init__(self, tables: tuple[TableType[T]], columns: tuple[ColumnType], alias_table: str = "{table}", *, context: ClauseContextType = ClauseInfoContext(), **kwargs) -> None:
40
+ self.kwargs = kwargs
97
41
  self._tables: tuple[TableType[T]] = tables if isinstance(tables, tp.Iterable) else (tables,)
98
42
 
99
43
  self._columns: tp.Callable[[T], tuple] = columns
@@ -145,7 +89,7 @@ class DecompositionQueryBase[T: Table, *Ts](IDecompositionQuery[T, *Ts]):
145
89
  }
146
90
  classConverter = next((converter for obj, converter in VALUE_TYPE_MAPPED.items() if validation(data, obj)), ConvertFromAnyType)
147
91
 
148
- return classConverter.convert(data, alias_table=self._alias_table, context=self._context)
92
+ return classConverter.convert(data, alias_table=self._alias_table, context=self._context, **self.kwargs)
149
93
 
150
94
  def __add_clause[TTable: TableType](self, clauses: list[ClauseInfo[TTable]] | ClauseInfo[TTable]) -> None:
151
95
  if not isinstance(clauses, tp.Iterable):
@@ -5,11 +5,11 @@ from typing import Any, Optional, Type, override, TYPE_CHECKING
5
5
  from ormlambda.common.interfaces.INonQueryCommand import INonQueryCommand
6
6
 
7
7
  if TYPE_CHECKING:
8
- from ormlambda.repository import IRepositoryBase
8
+ from ormlambda.repository import BaseRepository
9
9
  from ormlambda import Table
10
10
 
11
11
 
12
- class NonQueryBase[T: Type[Table], TRepo: IRepositoryBase](INonQueryCommand):
12
+ class NonQueryBase[T: Type[Table], TRepo: BaseRepository](INonQueryCommand):
13
13
  __slots__: tuple[str, ...] = ("_model", "_repository", "_values", "_query")
14
14
 
15
15
  def __init__(self, model: T, repository: TRepo) -> None:
@@ -1,4 +1,4 @@
1
- from ormlambda.common.interfaces import IDecompositionQuery
1
+ # from ormlambda.common.interfaces import IDecompositionQuery
2
2
 
3
3
 
4
4
  class ICustomAlias[T, *Ts]: ...
@@ -3,13 +3,13 @@ from abc import abstractmethod
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  if TYPE_CHECKING:
6
- from ormlambda import Table, IRepositoryBase
6
+ from ormlambda import Table, BaseRepository
7
7
  from ormlambda.common.abstract_classes import NonQueryBase
8
8
 
9
9
  from .IDelete import IDelete
10
10
 
11
11
 
12
- class DeleteQueryBase[T: Table, TRepo: IRepositoryBase](NonQueryBase[T, TRepo], IDelete[T]):
12
+ class DeleteQueryBase[T: Table, TRepo: BaseRepository](NonQueryBase[T, TRepo], IDelete[T]):
13
13
  def __init__(self, model: T, repository: TRepo) -> None:
14
14
  super().__init__(model, repository)
15
15
 
@@ -0,0 +1 @@
1
+ from .join_context import JoinContext, TupleJoinType # noqa: F401
@@ -6,7 +6,6 @@ if TYPE_CHECKING:
6
6
  from ormlambda import Table
7
7
  from ormlambda.repository import IRepositoryBase
8
8
  from ormlambda.components.delete import DeleteQueryBase
9
- from mysql.connector import MySQLConnection
10
9
 
11
10
 
12
11
  class DeleteQuery[T: Table](DeleteQueryBase[T, IRepositoryBase]):
@@ -1,15 +1,18 @@
1
- from typing import Literal, override
1
+ from __future__ import annotations
2
+ from typing import Literal, override, TYPE_CHECKING
2
3
 
3
- from ormlambda.repository import IRepositoryBase
4
+ if TYPE_CHECKING:
5
+ from mysql.connector import MySQLConnection
6
+
7
+ from ormlambda.repository import BaseRepository
4
8
 
5
- from mysql.connector import MySQLConnection
6
9
 
7
10
  TypeExists = Literal["fail", "replace", "append"]
8
11
 
9
12
 
10
13
  class DropTable:
11
- def __init__(self, repository: IRepositoryBase) -> None:
12
- self._repository: IRepositoryBase = repository
14
+ def __init__(self, repository: BaseRepository[MySQLConnection]) -> None:
15
+ self._repository: BaseRepository[MySQLConnection] = repository
13
16
 
14
17
  @override
15
18
  def execute(self, name: str = None) -> None:
@@ -1,7 +1,6 @@
1
1
  import typing as tp
2
2
  from ormlambda import Table
3
- from ormlambda.sql.clause_info.clause_info import AggregateFunctionBase
4
- from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
3
+ from ormlambda.sql.clause_info import AggregateFunctionBase, ClauseInfoContext
5
4
  from ormlambda.sql.types import ColumnType
6
5
 
7
6
 
@@ -21,11 +21,13 @@ class Select[T: Type[Table], *Ts](DecompositionQueryBase[T, *Ts], ISelect):
21
21
  *,
22
22
  alias_table: AliasType[ClauseInfo] = "{table}",
23
23
  context: Optional[ClauseInfoContext] = None,
24
+ **kwargs,
24
25
  ) -> None:
25
26
  super().__init__(
26
27
  tables,
27
28
  columns,
28
29
  context=context,
30
+ **kwargs,
29
31
  )
30
32
  self._alias_table = alias_table
31
33
  # We always need to add the self alias of the Select
@@ -1,15 +1,19 @@
1
- from typing import override, Any
1
+ from __future__ import annotations
2
+ from typing import override, TYPE_CHECKING
2
3
 
3
4
  from ormlambda import Table
4
5
  from ormlambda.components.upsert import UpsertQueryBase
5
- from ormlambda.repository import IRepositoryBase
6
- from mysql.connector import MySQLConnection
6
+ from ormlambda.repository import IRepositoryBase, BaseRepository
7
+
7
8
 
8
9
  from .insert import InsertQuery
9
10
 
11
+ if TYPE_CHECKING:
12
+ from mysql.connector import MySQLConnection
13
+
10
14
 
11
15
  class UpsertQuery[T: Table](UpsertQueryBase[T, IRepositoryBase]):
12
- def __init__(self, model: T, repository: Any) -> None:
16
+ def __init__(self, model: T, repository: BaseRepository[MySQLConnection]) -> None:
13
17
  super().__init__(model, repository)
14
18
 
15
19
  @override
@@ -0,0 +1,158 @@
1
+ from __future__ import annotations
2
+ from typing import Iterable, TypedDict, Optional
3
+
4
+ from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
5
+ from ormlambda.databases.my_sql.clauses.joins import JoinSelector
6
+ from ormlambda import ForeignKey
7
+
8
+ from ormlambda.common.interfaces import IQuery
9
+
10
+
11
+ from ormlambda.sql.clause_info import ClauseInfo
12
+ from .clauses import Limit
13
+ from .clauses import Offset
14
+ from .clauses import Order
15
+ from .clauses import Select
16
+
17
+ from .clauses import Where
18
+ from .clauses import Having
19
+ from .clauses import GroupBy
20
+
21
+
22
+ from ormlambda.common.enums import JoinType
23
+
24
+
25
+ class OrderType(TypedDict):
26
+ Select: Select
27
+ JoinSelector: JoinSelector
28
+ Where: Where
29
+ Order: Order
30
+ GroupBy: GroupBy
31
+ Having: Having
32
+ Limit: Limit
33
+ Offset: Offset
34
+
35
+
36
+ class QueryBuilder(IQuery):
37
+ __order__: tuple[str, ...] = ("Select", "JoinSelector", "Where", "GroupBy", "Having", "Order", "Limit", "Offset")
38
+
39
+ def __init__(self, by: JoinType = JoinType.INNER_JOIN):
40
+ self._context = ClauseInfoContext()
41
+ self._query_list: OrderType = {}
42
+ self._by = by
43
+
44
+ self._joins: Optional[IQuery] = None
45
+ self._select: Optional[IQuery] = None
46
+ self._where: Optional[IQuery] = None
47
+ self._order: Optional[IQuery] = None
48
+ self._group_by: Optional[IQuery] = None
49
+ self._limit: Optional[IQuery] = None
50
+ self._offset: Optional[IQuery] = None
51
+
52
+ def add_statement[T](self, clause: ClauseInfo[T]):
53
+ self.update_context(clause)
54
+ self._query_list[type(clause).__name__] = clause
55
+
56
+ @property
57
+ def by(self) -> JoinType:
58
+ return self._by
59
+
60
+ @by.setter
61
+ def by(self, value: JoinType) -> None:
62
+ self._by = value
63
+
64
+ @property
65
+ def JOINS(self) -> Optional[set[JoinSelector]]:
66
+ return self._joins
67
+
68
+ @property
69
+ def SELECT(self) -> IQuery:
70
+ return self._query_list.get("Select", None)
71
+
72
+ @property
73
+ def WHERE(self) -> IQuery:
74
+ where = self._query_list.get("Where", None)
75
+ if not isinstance(where, Iterable):
76
+ if not where:
77
+ return ()
78
+ return (where,)
79
+ return where
80
+
81
+ @property
82
+ def ORDER(self) -> IQuery:
83
+ return self._query_list.get("Order", None)
84
+
85
+ @property
86
+ def GROUP_BY(self) -> IQuery:
87
+ return self._query_list.get("GroupBy", None)
88
+
89
+ @property
90
+ def HAVING(self) -> IQuery:
91
+ where = self._query_list.get("Having", None)
92
+ if not isinstance(where, Iterable):
93
+ if not where:
94
+ return ()
95
+ return (where,)
96
+ return where
97
+
98
+ @property
99
+ def LIMIT(self) -> IQuery:
100
+ return self._query_list.get("Limit", None)
101
+
102
+ @property
103
+ def OFFSET(self) -> IQuery:
104
+ return self._query_list.get("Offset", None)
105
+
106
+ @property
107
+ def query(self) -> str:
108
+ # COMMENT: (select.query, query)We must first create an alias for 'FROM' and then define all the remaining clauses.
109
+ # This order is mandatory because it adds the clause name to the context when accessing the .query property of 'FROM'
110
+
111
+ extract_joins = self.pop_tables_and_create_joins_from_ForeignKey(self._by)
112
+
113
+ JOINS = self.stringify_foreign_key(extract_joins, " ")
114
+ query_list: tuple[str, ...] = (
115
+ self.SELECT.query,
116
+ JOINS,
117
+ Where.join_condition(self.WHERE, True, self._context) if self.WHERE else None,
118
+ self.GROUP_BY.query if self.GROUP_BY else None,
119
+ Having.join_condition(self.HAVING, True, self._context) if self.HAVING else None,
120
+ self.ORDER.query if self.ORDER else None,
121
+ self.LIMIT.query if self.LIMIT else None,
122
+ self.OFFSET.query if self.OFFSET else None,
123
+ )
124
+ return " ".join([x for x in query_list if x])
125
+
126
+ def stringify_foreign_key(self, joins: set[JoinSelector], sep: str = "\n") -> Optional[str]:
127
+ if not joins:
128
+ return None
129
+ sorted_joins = JoinSelector.sort_join_selectors(joins)
130
+ return f"{sep}".join([join.query for join in sorted_joins])
131
+
132
+ def pop_tables_and_create_joins_from_ForeignKey(self, by: JoinType = JoinType.INNER_JOIN) -> set[JoinSelector]:
133
+ # When we applied filters in any table that we wont select any column, we need to add manually all neccessary joins to achieve positive result.
134
+ if not ForeignKey.stored_calls:
135
+ return None
136
+
137
+ joins = set()
138
+ # Always it's gonna be a set of two
139
+ # FIXME [x]: Resolved when we get Compare object instead ClauseInfo. For instance, when we have multiples condition using '&' or '|'
140
+ for fk in ForeignKey.stored_calls.copy():
141
+ fk = ForeignKey.stored_calls.pop(fk)
142
+ self._context._add_table_alias(fk.tright, fk.alias)
143
+ join = JoinSelector(fk.resolved_function(self._context), by, context=self._context, alias=fk.alias)
144
+ joins.add(join)
145
+
146
+ return joins
147
+
148
+ def clear(self) -> None:
149
+ self.__init__()
150
+ return None
151
+
152
+ def update_context(self, clause: ClauseInfo) -> None:
153
+ if not hasattr(clause, "context"):
154
+ return None
155
+
156
+ if clause.context is not None:
157
+ self._context.update(clause.context)
158
+ clause.context = self._context