crate 2.2.0__tar.gz → 2.2.1__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.
- {crate-2.2.0 → crate-2.2.1}/CHANGES.rst +8 -0
- {crate-2.2.0 → crate-2.2.1}/PKG-INFO +1 -1
- {crate-2.2.0 → crate-2.2.1}/docs/by-example/cursor.rst +31 -0
- {crate-2.2.0 → crate-2.2.1}/docs/query.rst +57 -10
- {crate-2.2.0 → crate-2.2.1}/tests/client/test_cursor.py +168 -0
- {crate-2.2.0 → crate-2.2.1}/.gitignore +0 -0
- {crate-2.2.0 → crate-2.2.1}/CONTRIBUTING.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/DEVELOP.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/LICENSE +0 -0
- {crate-2.2.0 → crate-2.2.1}/NOTICE +0 -0
- {crate-2.2.0 → crate-2.2.1}/README.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/.gitignore +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/Makefile +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/_extra/robots.txt +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/blobs.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/build.json +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/by-example/blob.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/by-example/client.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/by-example/connection.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/by-example/http.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/by-example/https.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/by-example/index.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/conf.py +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/connect.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/data-types.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/docutils.conf +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/getting-started.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/index-all.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/index.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/other-options.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/docs/requirements.txt +0 -0
- {crate-2.2.0 → crate-2.2.1}/examples/README.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/pyproject.toml +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/__init__.py +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/assets/import/test_a.json +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/assets/mappings/locations.sql +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/assets/pki/cacert_invalid.pem +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/assets/pki/cacert_valid.pem +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/assets/pki/client_invalid.pem +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/assets/pki/client_valid.pem +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/assets/pki/readme.rst +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/assets/pki/server_valid.pem +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/assets/settings/test_a.json +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/client/__init__.py +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/client/settings.py +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/client/test_blob.py +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/client/test_connection.py +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/client/test_exceptions.py +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/client/test_http.py +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/client/test_serialization.py +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/client/test_utils.py +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/conftest.py +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/test_docs.py +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/testing/__init__.py +0 -0
- {crate-2.2.0 → crate-2.2.1}/tests/testing/test_layer.py +0 -0
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
Changes for crate
|
|
3
3
|
=================
|
|
4
4
|
|
|
5
|
+
2026/06/17 2.2.1
|
|
6
|
+
================
|
|
7
|
+
|
|
8
|
+
- Fixed ``cursor.execute()`` with ``bulk_parameters`` and pyformat SQL: when
|
|
9
|
+
rows are dicts, both the SQL template and the rows are now converted to
|
|
10
|
+
positional format before sending to CrateDB. Positional-list rows
|
|
11
|
+
continue to work as before.
|
|
12
|
+
|
|
5
13
|
2026/06/04 2.2.0
|
|
6
14
|
==========
|
|
7
15
|
- Added JSON serialization support for Python's ``datetime.time`` type,
|
|
@@ -266,6 +266,37 @@ For completeness' sake the cursor description is updated nonetheless:
|
|
|
266
266
|
>>> [ desc[0] for desc in cursor.description ]
|
|
267
267
|
['name', 'position']
|
|
268
268
|
|
|
269
|
+
executemany() with named parameters
|
|
270
|
+
====================================
|
|
271
|
+
|
|
272
|
+
``executemany()`` also accepts a :class:`py:list` of :class:`py:dict` when
|
|
273
|
+
the SQL statement contains ``%(name)s`` placeholders. The client converts both the SQL
|
|
274
|
+
template and all rows to positional format before sending them to CrateDB:
|
|
275
|
+
|
|
276
|
+
.. Hidden: set up mocked response
|
|
277
|
+
|
|
278
|
+
>>> connection.client.set_next_response({
|
|
279
|
+
... "results": [
|
|
280
|
+
... {"rowcount": 1},
|
|
281
|
+
... {"rowcount": 1}
|
|
282
|
+
... ],
|
|
283
|
+
... "duration": 123,
|
|
284
|
+
... "cols": [],
|
|
285
|
+
... })
|
|
286
|
+
|
|
287
|
+
>>> cursor = connection.cursor()
|
|
288
|
+
|
|
289
|
+
>>> cursor.executemany(
|
|
290
|
+
... "INSERT INTO t (id, val) VALUES (%(id)s, %(val)s)",
|
|
291
|
+
... [{"id": 1, "val": "foo"}, {"id": 2, "val": "bar"}])
|
|
292
|
+
[{'rowcount': 1}, {'rowcount': 1}]
|
|
293
|
+
|
|
294
|
+
>>> cursor.rowcount
|
|
295
|
+
2
|
|
296
|
+
|
|
297
|
+
>>> cursor.duration
|
|
298
|
+
123
|
|
299
|
+
|
|
269
300
|
>>> connection.client.set_next_response({
|
|
270
301
|
... "rows":[ [ "North West Ripple", 1 ], [ "Arkintoofle Minor", 3 ], [ "Alpha Centauri", 3 ] ],
|
|
271
302
|
... "cols":[ "name", "position" ],
|
|
@@ -72,20 +72,19 @@ The same parameter name may appear multiple times in the query:
|
|
|
72
72
|
... "SELECT * FROM locations WHERE name = %(q)s OR kind = %(q)s",
|
|
73
73
|
... {"q": "Quasar"})
|
|
74
74
|
|
|
75
|
-
The client converts the ``%(name)s`` placeholders to positional
|
|
76
|
-
before sending the query to CrateDB
|
|
77
|
-
|
|
78
|
-
.. NOTE::
|
|
79
|
-
|
|
80
|
-
Named parameters are not yet supported by ``executemany()``. Use
|
|
81
|
-
positional ``?`` placeholders with a :class:`py:list` of tuples for bulk
|
|
82
|
-
operations.
|
|
75
|
+
The client converts the ``%(name)s`` placeholders to ``$N`` positional
|
|
76
|
+
markers before sending the query to CrateDB.
|
|
83
77
|
|
|
84
78
|
Bulk inserts
|
|
85
79
|
------------
|
|
86
80
|
|
|
87
81
|
:ref:`Bulk inserts <crate-reference:http-bulk-ops>` are possible with the
|
|
88
|
-
``executemany()`` method
|
|
82
|
+
``executemany()`` method.
|
|
83
|
+
|
|
84
|
+
Positional parameters
|
|
85
|
+
.....................
|
|
86
|
+
|
|
87
|
+
Pass a :class:`py:list` of sequences using ``?`` placeholders:
|
|
89
88
|
|
|
90
89
|
>>> cursor.executemany(
|
|
91
90
|
... "INSERT INTO locations (name, date, kind, position) VALUES (?, ?, ?, ?)",
|
|
@@ -94,10 +93,58 @@ Bulk inserts
|
|
|
94
93
|
[{'rowcount': 1}, {'rowcount': 1}]
|
|
95
94
|
|
|
96
95
|
The ``executemany()`` method returns a result :class:`dictionary <py:dict>`
|
|
97
|
-
for every
|
|
96
|
+
for every row. This dictionary always has a ``rowcount`` key, indicating
|
|
98
97
|
how many rows were inserted. If an error occurs, the ``rowcount`` value is
|
|
99
98
|
``-2``, and the dictionary may additionally have an ``error_message`` key.
|
|
100
99
|
|
|
100
|
+
Named parameters
|
|
101
|
+
................
|
|
102
|
+
|
|
103
|
+
``executemany()`` also accepts a :class:`py:list` of :class:`py:dict` using
|
|
104
|
+
``%(name)s`` placeholders. The client converts both the SQL template and all
|
|
105
|
+
rows to positional format before sending to CrateDB:
|
|
106
|
+
|
|
107
|
+
>>> cursor.executemany(
|
|
108
|
+
... "INSERT INTO locations (name, date, kind, position) "
|
|
109
|
+
... "VALUES (%(name)s, %(date)s, %(kind)s, %(pos)s)",
|
|
110
|
+
... [{"name": "Cloverleaf", "date": "2007-03-11", "kind": "Quasar", "pos": 7},
|
|
111
|
+
... {"name": "Old Faithful", "date": "2007-03-11", "kind": "Quasar", "pos": 8}])
|
|
112
|
+
[{'rowcount': 1}, {'rowcount': 1}]
|
|
113
|
+
|
|
114
|
+
Using ``bulk_parameters`` directly
|
|
115
|
+
...................................
|
|
116
|
+
|
|
117
|
+
``execute()`` accepts a ``bulk_parameters`` keyword argument directly:
|
|
118
|
+
|
|
119
|
+
.. NOTE::
|
|
120
|
+
Please prefer ``executemany()`` for bulk inserts, it is the standard DB-API 2.0
|
|
121
|
+
interface. The ``bulk_parameters`` argument is a lower-level alternative.
|
|
122
|
+
|
|
123
|
+
>>> cursor.execute(
|
|
124
|
+
... "INSERT INTO locations (name, kind, position) VALUES (?, ?, ?)",
|
|
125
|
+
... bulk_parameters=[('Cloverleaf', 'Quasar', 7),
|
|
126
|
+
... ('Old Faithful', 'Quasar', 8)])
|
|
127
|
+
|
|
128
|
+
Named ``%(name)s`` placeholders are also supported. When the rows are
|
|
129
|
+
:class:`py:dict` objects the SQL template and rows are fully converted,
|
|
130
|
+
identical to the ``executemany()`` path:
|
|
131
|
+
|
|
132
|
+
>>> cursor.execute(
|
|
133
|
+
... "INSERT INTO locations (name, kind, position) "
|
|
134
|
+
... "VALUES (%(name)s, %(kind)s, %(pos)s)",
|
|
135
|
+
... bulk_parameters=[{"name": "Cloverleaf", "kind": "Quasar", "pos": 7},
|
|
136
|
+
... {"name": "Old Faithful", "kind": "Quasar", "pos": 8}])
|
|
137
|
+
|
|
138
|
+
When the rows are already positional lists (e.g. data coming from a
|
|
139
|
+
DataFrame), only the SQL template is rewritten. In this case the caller must
|
|
140
|
+
ensure the value order in each row matches the placeholder order in the SQL:
|
|
141
|
+
|
|
142
|
+
>>> cursor.execute(
|
|
143
|
+
... "INSERT INTO locations (name, kind, position) "
|
|
144
|
+
... "VALUES (%(name)s, %(kind)s, %(pos)s)",
|
|
145
|
+
... bulk_parameters=[['Cloverleaf', 'Quasar', 7],
|
|
146
|
+
... ['Old Faithful', 'Quasar', 8]])
|
|
147
|
+
|
|
101
148
|
.. _selects:
|
|
102
149
|
|
|
103
150
|
Selecting data
|
|
@@ -125,6 +125,94 @@ def test_cursor_executemany(mocked_connection):
|
|
|
125
125
|
assert response["results"] == result
|
|
126
126
|
|
|
127
127
|
|
|
128
|
+
def test_executemany_with_named_params(mocked_connection):
|
|
129
|
+
"""
|
|
130
|
+
Verify that executemany() translates pyformat %(name)s placeholders to
|
|
131
|
+
positional $N markers and converts each dict row to a positional list.
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
response = {
|
|
135
|
+
"col_types": [],
|
|
136
|
+
"cols": [],
|
|
137
|
+
"duration": 123,
|
|
138
|
+
"results": [{"rowcount": 1}, {"rowcount": 1}],
|
|
139
|
+
}
|
|
140
|
+
with mock.patch.object(
|
|
141
|
+
mocked_connection.client, "sql", return_value=response
|
|
142
|
+
):
|
|
143
|
+
cursor = mocked_connection.cursor()
|
|
144
|
+
cursor.executemany(
|
|
145
|
+
"INSERT INTO characters (name, age) VALUES (%(name)s, %(age)s)",
|
|
146
|
+
[
|
|
147
|
+
{"name": "Arthur", "age": 42},
|
|
148
|
+
{"name": "Bill", "age": 35},
|
|
149
|
+
],
|
|
150
|
+
)
|
|
151
|
+
sql, _params, bulk_args = mocked_connection.client.sql.call_args[0]
|
|
152
|
+
assert sql == "INSERT INTO characters (name, age) VALUES ($1, $2)"
|
|
153
|
+
assert bulk_args == [["Arthur", 42], ["Bill", 35]]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_executemany_with_named_params_missing_key(mocked_connection):
|
|
157
|
+
"""
|
|
158
|
+
Verify that executemany() raises ProgrammingError when a row is missing a
|
|
159
|
+
key that appears as a placeholder in the SQL.
|
|
160
|
+
"""
|
|
161
|
+
cursor = mocked_connection.cursor()
|
|
162
|
+
with pytest.raises(
|
|
163
|
+
ProgrammingError, match="Named parameter 'age' not found"
|
|
164
|
+
):
|
|
165
|
+
cursor.executemany(
|
|
166
|
+
"INSERT INTO characters (name, age) VALUES (%(name)s, %(age)s)",
|
|
167
|
+
[
|
|
168
|
+
{"name": "Arthur", "age": 42},
|
|
169
|
+
{"name": "Bill"}, # missing 'age'
|
|
170
|
+
],
|
|
171
|
+
)
|
|
172
|
+
mocked_connection.client.sql.assert_not_called()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_executemany_with_named_params_repeated(mocked_connection):
|
|
176
|
+
"""
|
|
177
|
+
Verify that a placeholder name used multiple times in the SQL maps to the
|
|
178
|
+
same $N position in every occurrence, and the value appears only once in
|
|
179
|
+
each row's positional list.
|
|
180
|
+
"""
|
|
181
|
+
response = {
|
|
182
|
+
"col_types": [],
|
|
183
|
+
"cols": [],
|
|
184
|
+
"duration": 123,
|
|
185
|
+
"results": [{"rowcount": 1}, {"rowcount": 1}],
|
|
186
|
+
}
|
|
187
|
+
with mock.patch.object(
|
|
188
|
+
mocked_connection.client, "sql", return_value=response
|
|
189
|
+
):
|
|
190
|
+
cursor = mocked_connection.cursor()
|
|
191
|
+
cursor.executemany(
|
|
192
|
+
"INSERT INTO t (a, b) VALUES (%(x)s, %(x)s)",
|
|
193
|
+
[{"x": 1}, {"x": 2}],
|
|
194
|
+
)
|
|
195
|
+
sql, _params, bulk_args = mocked_connection.client.sql.call_args[0]
|
|
196
|
+
assert sql == "INSERT INTO t (a, b) VALUES ($1, $1)"
|
|
197
|
+
assert bulk_args == [[1], [2]]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_executemany_with_mixed_param_types(mocked_connection):
|
|
201
|
+
"""
|
|
202
|
+
Verify that executemany() raises a clear ProgrammingError when the
|
|
203
|
+
parameter sequence mixes dicts and non-dicts while the SQL uses pyformat.
|
|
204
|
+
"""
|
|
205
|
+
cursor = mocked_connection.cursor()
|
|
206
|
+
with pytest.raises(
|
|
207
|
+
ProgrammingError, match="All bulk parameter rows must be dicts"
|
|
208
|
+
):
|
|
209
|
+
cursor.executemany(
|
|
210
|
+
"INSERT INTO characters (name) VALUES (%(name)s)",
|
|
211
|
+
[{"name": "Arthur"}, ["Trillian"]], # second row is a list
|
|
212
|
+
)
|
|
213
|
+
mocked_connection.client.sql.assert_not_called()
|
|
214
|
+
|
|
215
|
+
|
|
128
216
|
def test_create_with_timezone_as_datetime_object(mocked_connection):
|
|
129
217
|
"""
|
|
130
218
|
The cursor can return timezone-aware `datetime` objects when requested.
|
|
@@ -243,6 +331,68 @@ def test_execute_with_bulk_args(mocked_connection):
|
|
|
243
331
|
mocked_connection.client.sql.assert_called_once_with(statement, None, [[1]])
|
|
244
332
|
|
|
245
333
|
|
|
334
|
+
def test_execute_with_pyformat_sql_and_bulk_parameters(mocked_connection):
|
|
335
|
+
"""
|
|
336
|
+
cursor.execute() converts %(name)s SQL to $N when bulk_parameters is
|
|
337
|
+
provided. Rows are already positional; only the SQL needs conversion.
|
|
338
|
+
"""
|
|
339
|
+
cursor = mocked_connection.cursor()
|
|
340
|
+
sql = "INSERT INTO t (id, val) VALUES (%(id)s, %(val)s)"
|
|
341
|
+
bulk = [[1, "hello"], [2, "world"]]
|
|
342
|
+
cursor.execute(sql, bulk_parameters=bulk)
|
|
343
|
+
mocked_connection.client.sql.assert_called_once_with(
|
|
344
|
+
"INSERT INTO t (id, val) VALUES ($1, $2)", None, bulk
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def test_execute_with_pyformat_sql_and_dict_bulk_parameters(mocked_connection):
|
|
349
|
+
"""
|
|
350
|
+
cursor.execute() with pyformat SQL and dict-format bulk_parameters converts
|
|
351
|
+
both the SQL template (%(x)s → $N) and the rows (dicts → positional lists).
|
|
352
|
+
"""
|
|
353
|
+
cursor = mocked_connection.cursor()
|
|
354
|
+
sql = "INSERT INTO t (id, val) VALUES (%(id)s, %(val)s)"
|
|
355
|
+
bulk = [{"id": 1, "val": "hello"}, {"id": 2, "val": "world"}]
|
|
356
|
+
cursor.execute(sql, bulk_parameters=bulk)
|
|
357
|
+
mocked_connection.client.sql.assert_called_once_with(
|
|
358
|
+
"INSERT INTO t (id, val) VALUES ($1, $2)",
|
|
359
|
+
None,
|
|
360
|
+
[[1, "hello"], [2, "world"]],
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def test_execute_with_dict_bulk_parameters_mixed_types_raises(
|
|
365
|
+
mocked_connection,
|
|
366
|
+
):
|
|
367
|
+
"""
|
|
368
|
+
cursor.execute() raises ProgrammingError when bulk_parameters mixes
|
|
369
|
+
dict and non-dict rows with pyformat SQL.
|
|
370
|
+
"""
|
|
371
|
+
cursor = mocked_connection.cursor()
|
|
372
|
+
with pytest.raises(
|
|
373
|
+
ProgrammingError, match="All bulk parameter rows must be dicts"
|
|
374
|
+
):
|
|
375
|
+
cursor.execute(
|
|
376
|
+
"INSERT INTO t (id) VALUES (%(id)s)",
|
|
377
|
+
bulk_parameters=[{"id": 1}, [2]],
|
|
378
|
+
)
|
|
379
|
+
mocked_connection.client.sql.assert_not_called()
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def test_execute_with_pyformat_sql_and_bulk_parameters_no_placeholders(
|
|
383
|
+
mocked_connection,
|
|
384
|
+
):
|
|
385
|
+
"""
|
|
386
|
+
SQL without %(name)s placeholders is passed through unchanged
|
|
387
|
+
even when bulk_parameters is provided.
|
|
388
|
+
"""
|
|
389
|
+
cursor = mocked_connection.cursor()
|
|
390
|
+
sql = "INSERT INTO t (id, val) VALUES (?, ?)"
|
|
391
|
+
bulk = [[1, "hello"], [2, "world"]]
|
|
392
|
+
cursor.execute(sql, bulk_parameters=bulk)
|
|
393
|
+
mocked_connection.client.sql.assert_called_once_with(sql, None, bulk)
|
|
394
|
+
|
|
395
|
+
|
|
246
396
|
def test_execute_custom_converter(mocked_connection):
|
|
247
397
|
"""
|
|
248
398
|
Verify that a custom converter is correctly applied when passed to a cursor.
|
|
@@ -565,6 +715,24 @@ def test_execute_with_named_params_missing(mocked_connection):
|
|
|
565
715
|
mocked_connection.client.sql.assert_not_called()
|
|
566
716
|
|
|
567
717
|
|
|
718
|
+
def test_execute_with_named_params_non_identifier_keys(mocked_connection):
|
|
719
|
+
"""
|
|
720
|
+
Verify that %(name)s placeholders whose name contains characters outside
|
|
721
|
+
[a-zA-Z0-9_] are still converted to positional $N markers.
|
|
722
|
+
|
|
723
|
+
"""
|
|
724
|
+
cursor = mocked_connection.cursor()
|
|
725
|
+
|
|
726
|
+
cursor.execute(
|
|
727
|
+
"UPDATE characters SET data['x'] = %(data['x'])s WHERE name = %(name)s",
|
|
728
|
+
{"data['x']": 42, "name": "Berlin"},
|
|
729
|
+
)
|
|
730
|
+
sql, args, _ = mocked_connection.client.sql.call_args[0]
|
|
731
|
+
assert "%" not in sql
|
|
732
|
+
assert sql == "UPDATE characters SET data['x'] = $1 WHERE name = $2"
|
|
733
|
+
assert args == [42, "Berlin"]
|
|
734
|
+
|
|
735
|
+
|
|
568
736
|
def test_cursor_close(mocked_connection):
|
|
569
737
|
"""
|
|
570
738
|
Verify that a cursor is not closed if not specifically closed.
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|