GeneralManager 0.1.0__tar.gz → 0.1.2__tar.gz
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.
- {generalmanager-0.1.0 → generalmanager-0.1.2}/GeneralManager.egg-info/PKG-INFO +1 -1
- {generalmanager-0.1.0 → generalmanager-0.1.2}/GeneralManager.egg-info/SOURCES.txt +2 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/PKG-INFO +1 -1
- {generalmanager-0.1.0 → generalmanager-0.1.2}/pyproject.toml +1 -1
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/api/graphql.py +0 -3
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/auxiliary/noneToZero.py +3 -3
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/interface/databaseInterface.py +2 -2
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/manager/input.py +29 -4
- {generalmanager-0.1.0 → generalmanager-0.1.2}/tests/test_graph_ql.py +1 -1
- generalmanager-0.1.2/tests/test_input.py +165 -0
- generalmanager-0.1.2/tests/test_noneToZero.py +14 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/GeneralManager.egg-info/dependency_links.txt +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/GeneralManager.egg-info/requires.txt +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/GeneralManager.egg-info/top_level.txt +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/LICENSE +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/README.md +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/setup.cfg +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/__init__.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/api/mutation.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/api/property.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/apps.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/auxiliary/__init__.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/auxiliary/argsToKwargs.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/auxiliary/filterParser.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/cache/cacheDecorator.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/cache/cacheTracker.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/cache/dependencyIndex.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/cache/pathMapping.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/cache/signals.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/factory/__init__.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/factory/factories.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/factory/lazy_methods.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/interface/__init__.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/interface/baseInterface.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/interface/calculationInterface.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/manager/__init__.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/manager/generalManager.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/manager/groupManager.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/manager/meta.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/measurement/__init__.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/measurement/measurement.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/measurement/measurementField.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/permission/__init__.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/permission/basePermission.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/permission/fileBasedPermission.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/permission/managerBasedPermission.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/permission/permissionChecks.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/permission/permissionDataManager.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/rule/__init__.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/rule/handler.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/rule/rule.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/tests/test_argsToKwargs.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/tests/test_basePermission.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/tests/test_managerBasedPermission.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/tests/test_measurement.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/tests/test_measurement_field.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/tests/test_rules.py +0 -0
- {generalmanager-0.1.0 → generalmanager-0.1.2}/tests/test_settings.py +0 -0
@@ -47,8 +47,10 @@ src/general_manager/rule/rule.py
|
|
47
47
|
tests/test_argsToKwargs.py
|
48
48
|
tests/test_basePermission.py
|
49
49
|
tests/test_graph_ql.py
|
50
|
+
tests/test_input.py
|
50
51
|
tests/test_managerBasedPermission.py
|
51
52
|
tests/test_measurement.py
|
52
53
|
tests/test_measurement_field.py
|
54
|
+
tests/test_noneToZero.py
|
53
55
|
tests/test_rules.py
|
54
56
|
tests/test_settings.py
|
@@ -19,9 +19,6 @@ if TYPE_CHECKING:
|
|
19
19
|
class MeasurementType(graphene.ObjectType): # type: ignore
|
20
20
|
value = graphene.Float()
|
21
21
|
unit = graphene.String()
|
22
|
-
required = graphene.Boolean()
|
23
|
-
editable = graphene.Boolean()
|
24
|
-
default_value = graphene.String()
|
25
22
|
|
26
23
|
|
27
24
|
def getReadPermissionFilter(
|
@@ -1,12 +1,12 @@
|
|
1
1
|
from typing import Optional, TypeVar, Literal
|
2
2
|
from general_manager.measurement import Measurement
|
3
3
|
|
4
|
-
|
4
|
+
NUMBERVALUE = TypeVar("NUMBERVALUE", int, float, Measurement)
|
5
5
|
|
6
6
|
|
7
7
|
def noneToZero(
|
8
|
-
value: Optional[
|
9
|
-
) ->
|
8
|
+
value: Optional[NUMBERVALUE],
|
9
|
+
) -> NUMBERVALUE | Literal[0]:
|
10
10
|
if value is None:
|
11
11
|
return 0
|
12
12
|
return value
|
{generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/interface/databaseInterface.py
RENAMED
@@ -654,7 +654,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
654
654
|
kwarg_filter[key].append(value)
|
655
655
|
return kwarg_filter
|
656
656
|
|
657
|
-
def filter(self, **kwargs: Any) -> DatabaseBucket:
|
657
|
+
def filter(self, **kwargs: Any) -> DatabaseBucket[GeneralManagerType]:
|
658
658
|
merged_filter = self.__mergeFilterDefinitions(self.filters, **kwargs)
|
659
659
|
return self.__class__(
|
660
660
|
self._data.filter(**kwargs),
|
@@ -663,7 +663,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
663
663
|
self.excludes,
|
664
664
|
)
|
665
665
|
|
666
|
-
def exclude(self, **kwargs: Any) -> DatabaseBucket:
|
666
|
+
def exclude(self, **kwargs: Any) -> DatabaseBucket[GeneralManagerType]:
|
667
667
|
merged_exclude = self.__mergeFilterDefinitions(self.excludes, **kwargs)
|
668
668
|
return self.__class__(
|
669
669
|
self._data.exclude(**kwargs),
|
@@ -17,6 +17,14 @@ class Input(Generic[INPUT_TYPE]):
|
|
17
17
|
possible_values: Optional[Callable | Iterable] = None,
|
18
18
|
depends_on: Optional[List[str]] = None,
|
19
19
|
):
|
20
|
+
"""
|
21
|
+
Initializes an Input instance with type information, possible values, and dependencies.
|
22
|
+
|
23
|
+
Args:
|
24
|
+
type: The expected type for the input value.
|
25
|
+
possible_values: An optional iterable or callable that defines allowed values.
|
26
|
+
depends_on: An optional list of dependency names. If not provided and possible_values is callable, dependencies are inferred from its parameters.
|
27
|
+
"""
|
20
28
|
self.type = type
|
21
29
|
self.possible_values = possible_values
|
22
30
|
self.is_manager = issubclass(type, GeneralManager)
|
@@ -33,16 +41,33 @@ class Input(Generic[INPUT_TYPE]):
|
|
33
41
|
self.depends_on = []
|
34
42
|
|
35
43
|
def cast(self, value: Any) -> Any:
|
44
|
+
"""
|
45
|
+
Casts the input value to the type specified by this Input instance.
|
46
|
+
|
47
|
+
Handles special cases for date, datetime, GeneralManager subclasses, and Measurement types.
|
48
|
+
If the value is already of the target type, it is returned unchanged. Otherwise, attempts to
|
49
|
+
convert or construct the value as appropriate for the target type.
|
50
|
+
|
51
|
+
Args:
|
52
|
+
value: The value to be cast or converted.
|
53
|
+
|
54
|
+
Returns:
|
55
|
+
The value converted to the target type, or an instance of the target type.
|
56
|
+
"""
|
57
|
+
if self.type == date:
|
58
|
+
if isinstance(value, datetime) and type(value) is not date:
|
59
|
+
return value.date()
|
60
|
+
return date.fromisoformat(value)
|
61
|
+
if self.type == datetime:
|
62
|
+
if isinstance(value, date):
|
63
|
+
return datetime.combine(value, datetime.min.time())
|
64
|
+
return datetime.fromisoformat(value)
|
36
65
|
if isinstance(value, self.type):
|
37
66
|
return value
|
38
67
|
if issubclass(self.type, GeneralManager):
|
39
68
|
if isinstance(value, dict):
|
40
69
|
return self.type(**value) # type: ignore
|
41
70
|
return self.type(id=value) # type: ignore
|
42
|
-
if self.type == date:
|
43
|
-
return date.fromisoformat(value)
|
44
|
-
if self.type == datetime:
|
45
|
-
return datetime.fromisoformat(value)
|
46
71
|
if self.type == Measurement and isinstance(value, str):
|
47
72
|
return Measurement.from_string(value)
|
48
73
|
return self.type(value)
|
@@ -35,7 +35,7 @@ class GraphQLPropertyTests(TestCase):
|
|
35
35
|
|
36
36
|
class MeasurementTypeTests(TestCase):
|
37
37
|
def test_measurement_type_fields(self):
|
38
|
-
for field in ["value", "unit"
|
38
|
+
for field in ["value", "unit"]:
|
39
39
|
self.assertTrue(hasattr(MeasurementType, field))
|
40
40
|
|
41
41
|
|
@@ -0,0 +1,165 @@
|
|
1
|
+
from django.test import TestCase
|
2
|
+
from unittest.mock import patch
|
3
|
+
from general_manager.manager.input import Input
|
4
|
+
from general_manager.measurement import Measurement
|
5
|
+
from datetime import date, datetime
|
6
|
+
|
7
|
+
|
8
|
+
class TestInput(TestCase):
|
9
|
+
|
10
|
+
def test_simple_input_initialization(self):
|
11
|
+
# Test with a simple type
|
12
|
+
input_obj = Input(int)
|
13
|
+
self.assertEqual(input_obj.type, int)
|
14
|
+
self.assertIsNone(input_obj.possible_values)
|
15
|
+
self.assertEqual(input_obj.depends_on, [])
|
16
|
+
|
17
|
+
def test_input_initialization_with_callable_possible_values(self):
|
18
|
+
# Test with a callable for possible_values
|
19
|
+
"""
|
20
|
+
Tests that initializing Input with a callable possible_values assigns attributes correctly.
|
21
|
+
|
22
|
+
Verifies that the type is set, possible_values references the callable, and depends_on is an empty list.
|
23
|
+
"""
|
24
|
+
def possible_values_func():
|
25
|
+
return [1, 2, 3]
|
26
|
+
|
27
|
+
input_obj = Input(int, possible_values=possible_values_func)
|
28
|
+
self.assertEqual(input_obj.type, int)
|
29
|
+
self.assertEqual(input_obj.possible_values, possible_values_func)
|
30
|
+
self.assertEqual(input_obj.depends_on, [])
|
31
|
+
|
32
|
+
def test_input_initialization_with_list_depends_on(self):
|
33
|
+
# Test with a list for depends_on
|
34
|
+
"""
|
35
|
+
Tests that initializing an Input with a list for depends_on sets the attribute correctly.
|
36
|
+
|
37
|
+
Verifies that the type is set to int, possible_values is None, and depends_on matches the provided list.
|
38
|
+
"""
|
39
|
+
input_obj = Input(int, depends_on=["input1", "input2"])
|
40
|
+
self.assertEqual(input_obj.type, int)
|
41
|
+
self.assertIsNone(input_obj.possible_values)
|
42
|
+
self.assertEqual(input_obj.depends_on, ["input1", "input2"])
|
43
|
+
|
44
|
+
def test_input_initialization_with_type_not_matching_possible_values(self):
|
45
|
+
# Test with a type that doesn't match the possible_values
|
46
|
+
"""
|
47
|
+
Tests initialization of Input with a type that does not match possible_values.
|
48
|
+
|
49
|
+
Verifies that the Input object accepts possible_values of a different type than the declared type without validation, and sets attributes accordingly.
|
50
|
+
"""
|
51
|
+
input_obj = Input(str, possible_values=[1, 2, 3])
|
52
|
+
self.assertEqual(input_obj.type, str)
|
53
|
+
self.assertEqual(input_obj.possible_values, [1, 2, 3])
|
54
|
+
self.assertEqual(input_obj.depends_on, [])
|
55
|
+
|
56
|
+
def test_input_initialization_with_callable_and_list_depends_on(self):
|
57
|
+
# Test with both callable and list for depends_on
|
58
|
+
"""
|
59
|
+
Tests that an Input can be initialized with a callable for possible_values and a list for depends_on.
|
60
|
+
|
61
|
+
Verifies that the type, possible_values, and depends_on attributes are set correctly when both are provided.
|
62
|
+
"""
|
63
|
+
def possible_values_func():
|
64
|
+
return [1, 2, 3]
|
65
|
+
|
66
|
+
input_obj = Input(
|
67
|
+
int, possible_values=possible_values_func, depends_on=["input1"]
|
68
|
+
)
|
69
|
+
self.assertEqual(input_obj.type, int)
|
70
|
+
self.assertEqual(input_obj.possible_values, possible_values_func)
|
71
|
+
self.assertEqual(input_obj.depends_on, ["input1"])
|
72
|
+
|
73
|
+
def test_simple_input_casting(self):
|
74
|
+
# Test casting a value to the specified type
|
75
|
+
"""
|
76
|
+
Tests that the Input class correctly casts values to integers and raises exceptions for invalid inputs.
|
77
|
+
|
78
|
+
Verifies successful casting from strings, integers, and floats to int, and asserts that invalid strings, None, or unsupported types raise ValueError or TypeError.
|
79
|
+
"""
|
80
|
+
input_obj = Input(int)
|
81
|
+
self.assertEqual(input_obj.cast("123"), 123)
|
82
|
+
self.assertEqual(input_obj.cast(456), 456)
|
83
|
+
self.assertEqual(input_obj.cast(789.0), 789)
|
84
|
+
|
85
|
+
with self.assertRaises(ValueError):
|
86
|
+
input_obj.cast("abc")
|
87
|
+
with self.assertRaises(TypeError):
|
88
|
+
input_obj.cast(None)
|
89
|
+
with self.assertRaises(TypeError):
|
90
|
+
input_obj.cast([1, 2, 3])
|
91
|
+
|
92
|
+
def test_input_casting_with_general_manager(self):
|
93
|
+
# Test casting with a GeneralManager subclass
|
94
|
+
"""
|
95
|
+
Tests casting to a GeneralManager subclass using the Input class.
|
96
|
+
|
97
|
+
Verifies that casting a dictionary or integer to an Input expecting a GeneralManager subclass returns an instance with the correct id attribute. Uses mocking to simulate subclass checks.
|
98
|
+
"""
|
99
|
+
class MockGeneralManager:
|
100
|
+
def __init__(self, id):
|
101
|
+
self.id = id
|
102
|
+
|
103
|
+
with patch("general_manager.manager.input.issubclass", return_value=True):
|
104
|
+
input_obj = Input(MockGeneralManager)
|
105
|
+
self.assertEqual(input_obj.cast({"id": 1}).id, 1)
|
106
|
+
self.assertEqual(input_obj.cast(2).id, 2)
|
107
|
+
|
108
|
+
def test_input_casting_with_date(self):
|
109
|
+
# Test casting with date
|
110
|
+
"""
|
111
|
+
Tests that the Input class correctly casts values to date objects.
|
112
|
+
|
113
|
+
Verifies successful casting from ISO format strings and datetime objects to date,
|
114
|
+
and asserts that invalid strings, None, or unsupported types raise exceptions.
|
115
|
+
"""
|
116
|
+
input_obj = Input(date)
|
117
|
+
self.assertEqual(input_obj.cast("2023-10-01"), date(2023, 10, 1))
|
118
|
+
self.assertEqual(
|
119
|
+
input_obj.cast(datetime(2023, 10, 1, 12, 1, 5)), date(2023, 10, 1)
|
120
|
+
)
|
121
|
+
with self.assertRaises(ValueError):
|
122
|
+
input_obj.cast("invalid-date")
|
123
|
+
with self.assertRaises(TypeError):
|
124
|
+
input_obj.cast(None)
|
125
|
+
with self.assertRaises(TypeError):
|
126
|
+
input_obj.cast([1, 2, 3])
|
127
|
+
|
128
|
+
def test_input_casting_with_datetime(self):
|
129
|
+
# Test casting with datetime
|
130
|
+
"""
|
131
|
+
Tests that the Input class correctly casts values to datetime objects.
|
132
|
+
|
133
|
+
Verifies successful casting from ISO format strings and date objects to datetime,
|
134
|
+
and asserts that invalid strings, None, or unsupported types raise appropriate exceptions.
|
135
|
+
"""
|
136
|
+
input_obj = Input(datetime)
|
137
|
+
self.assertEqual(
|
138
|
+
input_obj.cast("2023-10-01T12:00:00"), datetime(2023, 10, 1, 12, 0, 0)
|
139
|
+
)
|
140
|
+
self.assertEqual(
|
141
|
+
input_obj.cast(date(2023, 10, 1)), datetime(2023, 10, 1, 0, 0, 0)
|
142
|
+
)
|
143
|
+
with self.assertRaises(ValueError):
|
144
|
+
input_obj.cast("invalid-datetime")
|
145
|
+
with self.assertRaises(TypeError):
|
146
|
+
input_obj.cast(None)
|
147
|
+
with self.assertRaises(TypeError):
|
148
|
+
input_obj.cast([1, 2, 3])
|
149
|
+
|
150
|
+
def test_input_casting_with_measurement(self):
|
151
|
+
# Test casting with Measurement
|
152
|
+
"""
|
153
|
+
Tests that the Input class correctly casts values to Measurement instances.
|
154
|
+
|
155
|
+
Verifies successful casting from valid measurement strings and Measurement objects, and asserts that invalid strings, None, or unsupported types raise appropriate exceptions.
|
156
|
+
"""
|
157
|
+
input_obj = Input(Measurement)
|
158
|
+
self.assertEqual(input_obj.cast("1.0 m"), Measurement(1.0, "m"))
|
159
|
+
self.assertEqual(input_obj.cast(Measurement(2.0, "m")), Measurement(2.0, "m"))
|
160
|
+
with self.assertRaises(ValueError):
|
161
|
+
input_obj.cast("invalid-measurement")
|
162
|
+
with self.assertRaises(TypeError):
|
163
|
+
input_obj.cast(None)
|
164
|
+
with self.assertRaises(TypeError):
|
165
|
+
input_obj.cast([1, 2, 3])
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from django.test import TestCase
|
2
|
+
from general_manager.auxiliary.noneToZero import noneToZero
|
3
|
+
|
4
|
+
|
5
|
+
class TestNoneToZero(TestCase):
|
6
|
+
|
7
|
+
def test_none_to_zero(self):
|
8
|
+
"""
|
9
|
+
Tests the noneToZero function to ensure it correctly converts None to 0.
|
10
|
+
|
11
|
+
Verifies that the function returns 0 when given None, and returns the original value for non-None inputs.
|
12
|
+
"""
|
13
|
+
self.assertEqual(noneToZero(None), 0)
|
14
|
+
self.assertEqual(noneToZero(5), 5)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/interface/baseInterface.py
RENAMED
File without changes
|
{generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/interface/calculationInterface.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/measurement/measurement.py
RENAMED
File without changes
|
{generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/measurement/measurementField.py
RENAMED
File without changes
|
File without changes
|
{generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/permission/basePermission.py
RENAMED
File without changes
|
{generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/permission/fileBasedPermission.py
RENAMED
File without changes
|
File without changes
|
{generalmanager-0.1.0 → generalmanager-0.1.2}/src/general_manager/permission/permissionChecks.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|