Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 93cd9cf

Browse files
committedDec 8, 2022
api: native crud module support
Adds native api support for crud module [1] to use it from a connection object. 1. github.com/tarantool/crud Closes #205
1 parent 1154b3d commit 93cd9cf

File tree

15 files changed

+2795
-1
lines changed

15 files changed

+2795
-1
lines changed
 

‎.github/workflows/packing.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ jobs:
9999
- name: Install test requirements
100100
run: pip3 install -r requirements-test.txt
101101

102+
- name: Install the crud module for testing purposes
103+
run: tarantoolctl rocks install crud
104+
102105
- name: Run tests
103106
run: make test-pure-install
104107

@@ -154,6 +157,12 @@ jobs:
154157
curl -L https://tarantool.io/release/2/installer.sh | bash -s
155158
sudo apt install -y tarantool tarantool-dev
156159
160+
- name: Setup crud module
161+
shell: wsl-bash_Ubuntu-20.04 {0}
162+
run: |
163+
sudo apt install -y unzip
164+
tarantoolctl rocks install crud
165+
157166
- name: Setup test tarantool instance
158167
shell: wsl-bash_Ubuntu-20.04 {0}
159168
run: |
@@ -162,6 +171,10 @@ jobs:
162171
touch tarantool.pid
163172
echo $TNT_PID > ./tarantool.pid
164173
174+
- name: Check crud exist
175+
shell: wsl-bash_Ubuntu-20.04 {0}
176+
run: tarantoolctl rocks list
177+
165178
- name: Run tests
166179
env:
167180
REMOTE_TARANTOOL_HOST: localhost
@@ -331,6 +344,9 @@ jobs:
331344
- name: Install test requirements
332345
run: pip3 install -r requirements-test.txt
333346

347+
- name: Install the crud module for testing purposes
348+
run: tarantoolctl rocks install crud
349+
334350
- name: Run tests
335351
run: make test-pure-install
336352

@@ -492,6 +508,9 @@ jobs:
492508
- name: Install test requirements
493509
run: pip3 install -r requirements-test.txt
494510

511+
- name: Install the crud module for testing purposes
512+
run: tarantoolctl rocks install crud
513+
495514
- name: Run tests
496515
run: make test-pure-install
497516

‎.github/workflows/reusable_testing.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,7 @@ jobs:
3939
- name: Install test requirements
4040
run: pip3 install -r requirements-test.txt
4141

42+
- name: Install the crud module for testing purposes
43+
run: tarantoolctl rocks install crud
44+
4245
- run: make test

‎.github/workflows/testing.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ jobs:
8585
- name: Install test requirements
8686
run: pip3 install -r requirements-test.txt
8787

88+
- name: Install the crud module for testing purposes
89+
run: tarantoolctl rocks install crud
90+
8891
- name: Run tests
8992
run: make test
9093

@@ -143,6 +146,11 @@ jobs:
143146
- name: Install test requirements
144147
run: pip3 install -r requirements-test.txt
145148

149+
- name: Install the crud module for testing purposes
150+
run: |
151+
source tarantool-enterprise/env.sh
152+
tarantoolctl rocks install crud
153+
146154
- name: Run tests
147155
run: |
148156
source tarantool-enterprise/env.sh
@@ -196,6 +204,9 @@ jobs:
196204
- name: Install test requirements
197205
run: pip3 install -r requirements-test.txt
198206

207+
- name: Install the crud module for testing purposes
208+
run: tarantoolctl rocks install crud
209+
199210
- name: Run tests
200211
run: make test-pure-install
201212

@@ -257,6 +268,12 @@ jobs:
257268
curl -L https://tarantool.io/release/2/installer.sh | bash -s
258269
sudo apt install -y tarantool=${{ matrix.tarantool }} tarantool-dev=${{ matrix.tarantool }}
259270
271+
- name: Setup crud module
272+
shell: wsl-bash_Ubuntu-20.04 {0}
273+
run: |
274+
sudo apt install -y unzip
275+
tarantoolctl rocks install crud
276+
260277
- name: Setup test tarantool instance
261278
shell: wsl-bash_Ubuntu-20.04 {0}
262279
run: |
@@ -265,6 +282,10 @@ jobs:
265282
touch tarantool.pid
266283
echo $TNT_PID > ./tarantool.pid
267284
285+
- name: Check crud exist
286+
shell: wsl-bash_Ubuntu-20.04 {0}
287+
run: tarantoolctl rocks list
288+
268289
- name: Run tests
269290
env:
270291
REMOTE_TARANTOOL_HOST: localhost
@@ -329,6 +350,12 @@ jobs:
329350
curl -L https://tarantool.io/release/2/installer.sh | bash -s
330351
sudo apt install -y tarantool=${{ matrix.tarantool }} tarantool-dev=${{ matrix.tarantool }}
331352
353+
- name: Setup crud module
354+
shell: wsl-bash_Ubuntu-20.04 {0}
355+
run: |
356+
sudo apt install -y unzip
357+
tarantoolctl rocks install crud
358+
332359
- name: Setup test tarantool instance
333360
shell: wsl-bash_Ubuntu-20.04 {0}
334361
run: |
@@ -337,6 +364,10 @@ jobs:
337364
touch tarantool.pid
338365
echo $TNT_PID > ./tarantool.pid
339366
367+
- name: Check crud exist
368+
shell: wsl-bash_Ubuntu-20.04 {0}
369+
run: tarantoolctl rocks list
370+
340371
- name: Run tests
341372
env:
342373
REMOTE_TARANTOOL_HOST: localhost

‎CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010
- Support custom packer and unpacker factories (#191).
1111

12+
- Support [crud module](https://github.com/tarantool/crud) native API (#205).
13+
1214
### Changed
1315

1416
### Fixed

‎docs/source/api/submodule-crud.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module :py:mod:`tarantool.crud`
2+
================================
3+
4+
.. automodule:: tarantool.crud

‎docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ API Reference
6363
api/module-tarantool.rst
6464
api/submodule-connection.rst
6565
api/submodule-connection-pool.rst
66+
api/submodule-crud.rst
6667
api/submodule-dbapi.rst
6768
api/submodule-error.rst
6869
api/submodule-mesh-connection.rst

‎docs/source/quick-start.rst

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,3 +347,140 @@ with out-of-band message processing:
347347
348348
print(callback_res)
349349
>>> [[[100, 1]], [[200, 1]], [[300, 1]]]
350+
351+
352+
Interaction with the crud module
353+
----------------------------------
354+
355+
Through the :class:`~tarantool.Connection` object, you can access
356+
`crud module <https://github.com/tarantool/crud>`_ methods:
357+
358+
.. code-block:: python
359+
360+
>>> import tarantool
361+
>>> from tarantool.error import CrudModuleError, DatabaseError
362+
>>> conn = tarantool.Connection(host='localhost',port=3301)
363+
364+
>>> conn.crud_
365+
conn.crud_count( conn.crud_insert( conn.crud_insert_object_many(
366+
conn.crud_min( conn.crud_replace_object( conn.crud_stats(
367+
conn.crud_unflatten_rows( conn.crud_upsert_many( conn.crud_delete(
368+
conn.crud_insert_many( conn.crud_len( conn.crud_replace(
369+
conn.crud_replace_object_many( conn.crud_storage_info( conn.crud_update(
370+
conn.crud_upsert_object( conn.crud_get( conn.crud_insert_object(
371+
conn.crud_max( conn.crud_replace_many( conn.crud_select(
372+
conn.crud_truncate( conn.crud_upsert( conn.crud_upsert_object_many(
373+
374+
As an example, consider :meth:`~tarantool.Connection.crud_insert` and :meth:`~tarantool.Connection.crud_insert_object_many`.
375+
It is recommended to enclose calls in the try-except construction as follows:
376+
377+
.. code-block:: python
378+
379+
# Insert without exception:
380+
>>> res = conn.crud_insert('tester', (3500,300,'Rob'))
381+
>>> res
382+
<tarantool.crud.CrudResult object at 0x11a56e320>
383+
>>> res.
384+
res.metadata res.rows # fields like in Lua implementation.
385+
>>> res.rows
386+
[[3500, 300, 'Rob']]
387+
>>> res.metadata
388+
[{'name': 'id', 'type': 'unsigned'}, {'name': 'bucket_id', 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}]
389+
390+
# Insert with exception (duplicate key exists):
391+
>>> try:
392+
... res = conn.crud_insert('tester', (3500,300,'Rob'))
393+
... except CrudModuleError as e:
394+
... exc_crud = e
395+
...
396+
>>> exc_crud
397+
CrudModuleError(None, <tarantool.crud.CrudError object at 0x10a276950>)
398+
>>> exc_crud.
399+
exc_crud.args exc_crud.err exc_crud.res exc_crud.with_traceback(
400+
>>> exc_crud.args
401+
(None, <tarantool.crud.CrudError object at 0x10a276950>)
402+
>>> exc_crud.err
403+
<tarantool.crud.CrudError object at 0x10a276950>
404+
>>> exc_crud.err. # fields like in Lua implementation.
405+
exc_crud.err.class_name exc_crud.err.err exc_crud.err.file exc_crud.err.line exc_crud.err.str
406+
>>> exc_crud.err.class_name
407+
'InsertError'
408+
>>> exc_crud.err.str
409+
'InsertError: Failed to insert: Duplicate key exists in unique index "primary_index" in space "tester" with old tuple - [3500, 300, "Rob"] and new tuple - [3500, 300, "Rob"]'
410+
>>> exc_crud.res is None # using only in case of *_many.
411+
True
412+
413+
# In case of batch operation (*_many), CrudModuleError contains both result and errors:
414+
>>> try:
415+
... res = conn.crud_insert_object_many('tester', ({'id':1800,'bucket_id':100,'name':'Mike'}, {'id':2800,'bucket_id':100,'second_name':'Bill'}, {'id':3800,'bucket_id':100,'name':'Bob'}, {'id':1,'bucket_id':100,'name':'Rob'}), {'timeout':100, 'rollback_on_error':False})
416+
... except CrudModuleError as e:
417+
... exc_crud = e
418+
...
419+
>>> exc_crud
420+
CrudModuleError(<tarantool.crud.CrudResult object at 0x11a56f310>, [<tarantool.crud.CrudError object at 0x11a56e9e0>, <tarantool.crud.CrudError object at 0x11a56f490>])
421+
>>> exc_crud.
422+
exc_crud.args exc_crud.errs exc_crud.res exc_crud.with_traceback(
423+
>>> exc_crud.res # some of the rows were inserted.
424+
<tarantool.crud.CrudResult object at 0x11a56f310>
425+
>>> exc_crud.res.rows
426+
[[1800, 100, 'Mike'], [3800, 100, 'Bob']]
427+
>>> exc_crud.errs # some of the rows were not inserted.
428+
[<tarantool.crud.CrudError object at 0x11a56e9e0>, <tarantool.crud.CrudError object at 0x11a56f490>]
429+
>>> exc_crud.errs[0].str
430+
'CallError: Failed for 56d9c861-a8ac-459c-9715-09094ebb94a7: Function returned an error: Duplicate key exists in unique index "primary_index" in space "tester" with old tuple - [1, 100, "Mike"] and new tuple - [1, 100, "Rob"]'
431+
>>> exc_crud.errs[1].str
432+
'InsertManyError: Failed to flatten object: FlattenError: Object is specified in bad format: FlattenError: Unknown field "second_name" is specified'
433+
>>> exc_crud.args
434+
(<tarantool.crud.CrudResult object at 0x11a56f310>, [<tarantool.crud.CrudError object at 0x11a56e9e0>, <tarantool.crud.CrudError object at 0x11a56f490>])
435+
436+
If module crud not found on the router:
437+
438+
.. code-block:: python
439+
440+
>>> try:
441+
... res = conn.crud_insert('tester', (22221,300,'Rob'))
442+
... except CrudModuleError as e:
443+
... exc_crud = e
444+
... except DatabaseError as e:
445+
... exc_db = e
446+
...
447+
>>> exc_db
448+
DatabaseError(33, "Procedure 'crud.insert' is not defined. Ensure that you're calling crud.router and user have enough grants")
449+
450+
Using :meth:`~tarantool.Connection.crud_select` and :meth:`~tarantool.Connection.crud_unflatten_rows`:
451+
452+
.. code-block:: python
453+
454+
>>> try:
455+
... res = conn.crud_select('tester', {}, {'first':2})
456+
... except CrudModuleError as e:
457+
... exc_crud = e
458+
...
459+
>>> res
460+
<tarantool.crud.CrudResult object at 0x10a276d10>
461+
>>> res.
462+
res.metadata res.rows
463+
>>> res.rows
464+
[[1, 100, 'Mike'], [2, 100, 'Mike']]
465+
>>> res.metadata
466+
[{'name': 'id', 'type': 'unsigned'}, {'name': 'bucket_id', 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}]
467+
>>> r = conn.crud_unflatten_rows(res.rows, res.metadata)
468+
>>> r
469+
[{'id': 1, 'bucket_id': 100, 'name': 'Mike'}, {'id': 2, 'bucket_id': 100, 'name': 'Mike'}]
470+
471+
Using :meth:`~tarantool.Connection.crud_truncate` and :meth:`~tarantool.Connection.crud_len`:
472+
473+
.. code-block:: python
474+
475+
>>> try:
476+
... res = conn.crud_len('tester')
477+
... except CrudModuleError as e:
478+
... exc_crud = e
479+
>>> res
480+
26
481+
>>> try:
482+
... res = conn.crud_truncate('tester')
483+
... except CrudModuleError as e:
484+
... exc_crud = e
485+
>>> res
486+
True

‎tarantool/connection.py

Lines changed: 977 additions & 0 deletions
Large diffs are not rendered by default.

‎tarantool/connection_pool.py

Lines changed: 635 additions & 0 deletions
Large diffs are not rendered by default.

‎tarantool/crud.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
This module provides API for interaction with the `crud`_ module.
3+
4+
.. _crud: https://github.com/tarantool/crud/
5+
"""
6+
7+
from tarantool.error import DatabaseError
8+
9+
10+
class CrudResponse(object):
11+
"""
12+
Contains response fields from the `crud`_ module that correspond
13+
to the Lua implementation.
14+
15+
.. _crud: https://github.com/tarantool/crud/
16+
"""
17+
18+
def __init__(self, response):
19+
"""
20+
Sets response fields as in Lua implementation.
21+
22+
:param response: The response object of the crud module call.
23+
:type response: :class:`~tarantool.response.Response`
24+
"""
25+
26+
if isinstance(response, dict):
27+
for response_field_name in response.keys():
28+
if isinstance(response_field_name, bytes):
29+
setattr(self, response_field_name.decode(), response[response_field_name])
30+
else:
31+
setattr(self, response_field_name, response[response_field_name])
32+
else:
33+
raise RuntimeError('Unable to decode response to object due to unknown type')
34+
35+
36+
class CrudResult(CrudResponse):
37+
"""
38+
Contains result's fields from result variable
39+
of crud module operation.
40+
"""
41+
42+
43+
class CrudError(CrudResponse):
44+
"""
45+
Contains error's fields from error variable
46+
of crud module operation.
47+
"""
48+
49+
50+
def wrapCrudUndefinedProcedureError(e: DatabaseError):
51+
"""
52+
Supplements information about an unsuccessful attempt
53+
to find the crud module procedure.
54+
55+
:param e: The error object of the crud module call.
56+
:type e: :class:`~tarantool.error.DatabaseError`
57+
"""
58+
59+
info = ". Ensure that you're calling crud.router and user has sufficient grants"
60+
e.message = e.message + info
61+
e.extra_info.message = e.extra_info.message + info
62+
e.args = (e.args[0], e.args[1] + info)
63+
64+
return e
65+
66+
def call_crud(conn, *args):
67+
"""
68+
Calls the crud via connection.call with try/except block.
69+
70+
:param conn: The connection object for the crud module call.
71+
:type conn: :class:`~tarantool.connection.Connection`
72+
73+
:param args: The arguments to pass to the crud module.
74+
:type args: :obj:`tuple`
75+
"""
76+
77+
# Response error code for case "Requested procedure is not defined".
78+
ER_NO_SUCH_PROC = 33
79+
80+
try:
81+
crud_resp = conn.call(*args)
82+
except DatabaseError as e:
83+
if e.code == ER_NO_SUCH_PROC:
84+
e = wrapCrudUndefinedProcedureError(e)
85+
raise e
86+
87+
return crud_resp

‎tarantool/error.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,33 @@ class PoolTolopogyError(DatabaseError):
330330
pass
331331

332332

333+
class CrudModuleError(DatabaseError):
334+
"""
335+
Exception raised for errors that are related to the operation result of `crud`_ module.
336+
337+
.. _crud: https://github.com/tarantool/crud
338+
"""
339+
340+
def __init__(self, *args):
341+
"""
342+
Sets fields with result and errors.
343+
344+
:param args: The tuple from the crud module with result and errors.
345+
:type args: :obj:`tuple`
346+
"""
347+
348+
# Sets tarantool.crud.CrudResult object.
349+
self.res = args[0]
350+
351+
if isinstance(args[1], (tuple, list)):
352+
# Case for crud_*_many().
353+
# Sets list of tarantool.crud.CrudError objects.
354+
self.errs = args[1]
355+
else:
356+
# Sets tarantool.crud.CrudError object.
357+
self.err = args[1]
358+
359+
333360
# always print this warnings
334361
warnings.filterwarnings("always", category=NetworkWarning)
335362

‎test/suites/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from .test_error_ext import TestSuite_ErrorExt
2424
from .test_push import TestSuite_Push
2525
from .test_connection import TestSuite_Connection
26+
from .test_crud import TestSuite_Crud
2627

2728
test_cases = (TestSuite_Schema_UnicodeConnection,
2829
TestSuite_Schema_BinaryConnection,
@@ -31,7 +32,7 @@
3132
TestSuite_Encoding, TestSuite_Pool, TestSuite_Ssl,
3233
TestSuite_Decimal, TestSuite_UUID, TestSuite_Datetime,
3334
TestSuite_Interval, TestSuite_ErrorExt, TestSuite_Push,
34-
TestSuite_Connection,)
35+
TestSuite_Connection, TestSuite_Crud,)
3536

3637
def load_tests(loader, tests, pattern):
3738
suite = unittest.TestSuite()

‎test/suites/crud_server.lua

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env tarantool
2+
3+
local crud = require('crud')
4+
local vshard = require('vshard')
5+
6+
local admin_listen = os.getenv("ADMIN")
7+
local primary_listen = os.getenv("LISTEN")
8+
9+
require('console').listen(admin_listen)
10+
box.cfg{
11+
listen = primary_listen,
12+
memtx_memory = 0.1 * 1024^3, -- 0.1 GiB
13+
pid_file = "box.pid",
14+
}
15+
16+
box.schema.user.grant(
17+
'guest',
18+
'read,write,execute',
19+
'universe'
20+
)
21+
box.schema.create_space(
22+
'tester', {
23+
format = {
24+
{name = 'id', type = 'unsigned'},
25+
{name = 'bucket_id', type = 'unsigned'},
26+
{name = 'name', type = 'string'},
27+
}
28+
})
29+
box.space.tester:create_index('primary_index', {
30+
parts = {
31+
{field = 1, type = 'unsigned'},
32+
},
33+
})
34+
box.space.tester:create_index('bucket_id', {
35+
parts = {
36+
{field = 2, type = 'unsigned'},
37+
},
38+
unique = false,
39+
})
40+
41+
-- Setup vshard.
42+
_G.vshard = vshard
43+
box.once('guest', function()
44+
box.schema.user.grant('guest', 'super')
45+
end)
46+
local uri = 'guest@0.0.0.0:' .. primary_listen
47+
local cfg = {
48+
bucket_count = 300,
49+
sharding = {
50+
[box.info().cluster.uuid] = {
51+
replicas = {
52+
[box.info().uuid] = {
53+
uri = uri,
54+
name = 'storage',
55+
master = true,
56+
},
57+
},
58+
},
59+
},
60+
}
61+
vshard.storage.cfg(cfg, box.info().uuid)
62+
vshard.router.cfg(cfg)
63+
vshard.router.bootstrap()
64+
65+
-- Initialize crud.
66+
crud.init_storage()
67+
crud.init_router()
68+
crud.cfg{stats = true}

‎test/suites/lib/tarantool_python_ci.lua

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,32 @@ clean = function()
182182
local _FUNC_LANGUAGE = 5
183183
local allowed_funcs = {
184184
['box.schema.user.info'] = true,
185+
['crud.insert'] = true,
186+
['crud.insert_object'] = true,
187+
['crud.insert_many'] = true,
188+
['crud.insert_object_many'] = true,
189+
['crud.get'] = true,
190+
['crud.update'] = true,
191+
['crud.delete'] = true,
192+
['crud.replace'] = true,
193+
['crud.replace_object'] = true,
194+
['crud.replace_many'] = true,
195+
['crud.replace_object_many'] = true,
196+
['crud.upsert'] = true,
197+
['crud.upsert_object'] = true,
198+
['crud.upsert_many'] = true,
199+
['crud.upsert_object_many'] = true,
200+
['crud.select'] = true,
201+
['crud.min'] = true,
202+
['crud.max'] = true,
203+
['crud.truncate'] = true,
204+
['crud.len'] = true,
205+
['crud.storage_info'] = true,
206+
['crud.count'] = true,
207+
['crud.stats'] = true,
208+
['crud.init_storage'] = true,
209+
['crud.init_router'] = true,
210+
['crud.cfg'] = true,
185211
}
186212
local allowed_langs = {
187213
['SQL_BUILTIN'] = true,
@@ -220,6 +246,7 @@ clean = function()
220246
-- modules
221247
bit = true,
222248
coroutine = true,
249+
crud = true,
223250
debug = true,
224251
io = true,
225252
jit = true,
@@ -289,6 +316,33 @@ clean = function()
289316
console = true,
290317
coroutine = true,
291318
crypto = true,
319+
crud = true,
320+
['crud.insert'] = true,
321+
['crud.insert_object'] = true,
322+
['crud.insert_many'] = true,
323+
['crud.insert_object_many'] = true,
324+
['crud.get'] = true,
325+
['crud.update'] = true,
326+
['crud.delete'] = true,
327+
['crud.replace'] = true,
328+
['crud.replace_object'] = true,
329+
['crud.replace_many'] = true,
330+
['crud.replace_object_many'] = true,
331+
['crud.upsert'] = true,
332+
['crud.upsert_object'] = true,
333+
['crud.upsert_many'] = true,
334+
['crud.upsert_object_many'] = true,
335+
['crud.select'] = true,
336+
['crud.min'] = true,
337+
['crud.max'] = true,
338+
['crud.truncate'] = true,
339+
['crud.len'] = true,
340+
['crud.storage_info'] = true,
341+
['crud.count'] = true,
342+
['crud.stats'] = true,
343+
['crud.init_storage'] = true,
344+
['crud.init_router'] = true,
345+
['crud.cfg'] = true,
292346
csv = true,
293347
debug = true,
294348
digest = true,

‎test/suites/test_crud.py

Lines changed: 748 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.