Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api: native crud module support #264

Merged
merged 1 commit into from
Dec 22, 2022

Conversation

GRISHNOV
Copy link
Contributor

@GRISHNOV GRISHNOV commented Nov 15, 2022

Adds native api support for crud module to use it from a connection object.

Below there are examples of using the api.

>>> import tarantool
>>> from tarantool.error import CrudModuleError, CrudModuleManyError, DatabaseError
>>> conn = tarantool.Connection(host='localhost',port=3301)

>>> conn.crud_
    conn.crud_count(                conn.crud_insert(               conn.crud_insert_object_many(   
    conn.crud_min(                  conn.crud_replace_object(       conn.crud_stats(                
    conn.crud_unflatten_rows(       conn.crud_upsert_many(          conn.crud_delete(               
    conn.crud_insert_many(          conn.crud_len(                  conn.crud_replace(              
    conn.crud_replace_object_many(  conn.crud_storage_info(         conn.crud_update(               
    conn.crud_upsert_object(        conn.crud_get(                  conn.crud_insert_object(        
    conn.crud_max(                  conn.crud_replace_many(         conn.crud_select(               
    conn.crud_truncate(             conn.crud_upsert(               conn.crud_upsert_object_many(

Inserting data via crud:

# Insert without exception:
>>> res = conn.crud_insert('tester', (3500,300,'Rob'))
>>> res
<tarantool.crud.CrudResult object at 0x11a56e320>
>>> res.
res.metadata  res.rows
>>> res.rows
[[3500, 300, 'Rob']]
>>> res.metadata
[{'name': 'id', 'type': 'unsigned'}, {'name': 'bucket_id', 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}]

# Insert with exception (duplicate key exists):
>>> try:
...     res = conn.crud_insert('tester', (3500,300,'Rob'))
... except CrudModuleError as e:
...     exc_crud = e
... 
>>> exc_crud
CrudModuleError(0, '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"]')
>>> exc_crud.extra_info_error
<tarantool.crud.CrudError object at 0x10a276950>
>>> exc_crud.extra_info_error.
exc_crud.extra_info_error.class_name  exc_crud.extra_info_error.err         exc_crud.extra_info_error.file        exc_crud.extra_info_error.line        exc_crud.extra_info_error.str       
>>> exc_crud.extra_info_error.class_name
'InsertError'
>>> exc_crud.extra_info_error.str
'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"]'

# In case of batch operation (*_many), CrudModuleManyError exception contains both result and errors (if there is a problem with at least one row).
>>> try:
...     res = conn.crud_insert_object_many('tester', ({'id':3,'bucket_id':100,'name':'Ann'}, {'id':4,'bucket_id':100,'name':'Sam'}), {'timeout':100, 'rollback_on_error':False})
... except CrudModuleManyError as e:
...     exc_crud = e
... 
>>> exc_crud
CrudModuleManyError(0, 'Got multiple errors, see errors_list')
>>> exc_crud.success_list # some of the rows were inserted.
<tarantool.crud.CrudResult object at 0x11a56f310>
>>> exc_crud.success_list.rows
[[1, 100, 'Bob'], [2, 100, 'Rob']]
>>> exc_crud.errors_list # some of the rows were not inserted.
[<tarantool.crud.CrudError object at 0x11a56e9e0>, <tarantool.crud.CrudError object at 0x11a56f490>]
>>> exc_crud.errors_list[0].str
'CallError: Failed for 037adb3a-b9e3-4f78-a6d1-9f0cdb6cbefc: Function returned an error: Duplicate key exists in unique index "primary_index" in space "tester" with old tuple - [3500, 300, "Rob"] and new tuple - [3500, 100, "Mike"]'
>>> exc_crud.errors_list[1].str
'InsertManyError: Failed to flatten object: FlattenError: Object is specified in bad format: FlattenError: Unknown field "second_name" is specified'
    
# If there are no problems with any rows, the entire response will be contained in the res variable.
>>> res = conn.crud_insert_object_many('tester', ({'id':3,'bucket_id':100,'name':'Ann'}, {'id':4,'bucket_id':100,'name':'Sam'}), {'timeout':100, 'rollback_on_error':False})
>>> res.rows
[[3, 100, 'Ann'], [4, 100, 'Sam']]

Crud not found on the router:

>>> try:
...     res = conn.crud_insert('tester', (22221,300,'Rob'))
... except DatabaseError as e:
...     exc_db = e
... 
 >>> exc_db
DatabaseError(33, "Ensure that you're calling crud.router and user has sufficient grants")
>>> exc_db.extra_info
BoxError(type='ClientError', file='/tmp/tarantool-20221003-6335-edruh3/tarantool-2.10.3/src/box/lua/call.c', line=112, message="Procedure 'crud.insert' is not defined", errno=0, errcode=33, fields=None, prev=None)

Select and unflatten_rows via crud:

>>> try:
...     res = conn.crud_select('tester', {}, {'first':2})
... except CrudModuleError as e:
...     exc_crud = e
... 
>>> res
<tarantool.crud.CrudResult object at 0x10a276d10>
>>> res.
res.metadata  res.rows      
>>> res.rows
[[1, 100, 'Mike'], [2, 100, 'Mike']]
>>> res.metadata
[{'name': 'id', 'type': 'unsigned'}, {'name': 'bucket_id', 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}]
>>> r = conn.crud_unflatten_rows(res.rows, res.metadata)
>>> r
[{'id': 1, 'bucket_id': 100, 'name': 'Mike'}, {'id': 2, 'bucket_id': 100, 'name': 'Mike'}]

Truncate and len via crud:

>>> try:
...     res = conn.crud_len('tester')
... except CrudModuleError as e:
...     exc_crud = e
>>> res
26
>>> try:
...     res = conn.crud_truncate('tester')
... except CrudModuleError as e:
...     exc_crud = e
>>> res
True

Encoding=None in Connection:

>>> conn = tarantool.Connection(host='localhost',port=3301,encoding=None)

>>> try:
...     res = conn.crud_insert('tester', (3500,300,'Rob'))
... except CrudModuleError as e:
...     exc_crud = e
... 
>>> res
<tarantool.crud.CrudResult object at 0x1188a7fd0>
>>> res.
res.metadata  res.rows      
>>> res.metadata
[{b'name': b'id', b'type': b'unsigned'}, {b'name': b'bucket_id', b'type': b'unsigned'}, {b'name': b'name', b'type': b'string'}]
>>> res.rows
[[3500, 300, b'Rob']]

Closes #205

Copy link
Member

@DifferentialOrange DifferentialOrange left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your draft! The task it much trickier that I initially have expected. I gave it some thought (and recalled some crud behavior) and I think that we should try to rework some things. I'll summarize my comments here.

  • Try to get rid of class Crud.
  • Try to work with calls only.
  • Do not expose configuration handles.
  • Raise errors as exceptions.
  • Optimize utils usage.

I see that the first one is a requirement to be able to use conn.crud.method, but is such approach really widespread in Python? I'm not well-experienced myself, but I think that I have seen providing many handles from a single place more often than using complex hierarchy. Maybe it's worth to study this question a little by using some Python modules as an example (and if you still decide to use conn.crud.method, maybe you'll see some nice implementation approaches).

It is highly likely that I have forgot to mention something important in this review, so feel free to ask me any additional questions here or in personal chat.

tarantool/crud.py Outdated Show resolved Hide resolved
tarantool/crud.py Outdated Show resolved Hide resolved
tarantool/crud.py Outdated Show resolved Hide resolved
tarantool/crud.py Outdated Show resolved Hide resolved
tarantool/crud.py Outdated Show resolved Hide resolved
tarantool/crud.py Outdated Show resolved Hide resolved
tarantool/crud.py Outdated Show resolved Hide resolved
tarantool/crud.py Outdated Show resolved Hide resolved
tarantool/crud.py Outdated Show resolved Hide resolved
tarantool/crud.py Outdated Show resolved Hide resolved
@GRISHNOV GRISHNOV force-pushed the igrishnov/gh-205-native-crud-api-support branch from ad145c4 to 6343199 Compare November 22, 2022 18:18
@GRISHNOV
Copy link
Contributor Author

Thank you for your feedback!

I have made most of the edits (with the exception of the pairs implementation).
If the current API is suitable, I'll move on to writing documentation and tests

@DifferentialOrange
Copy link
Member

>>> exc_crud.res is None # use only in case of *_many

I don't get this comment.

@GRISHNOV
Copy link
Contributor Author

>>> exc_crud.res is None # use only in case of *_many

I don't get this comment.

I mean, in exc_crud.res, the result can only appear when the *_many() is called (when part of the batch is successful and part is not), in other cases exc_crud.res will always be None

Copy link
Member

@DifferentialOrange DifferentialOrange left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I think current API is fine and we may continue to work with it.

tarantool/connection.py Show resolved Hide resolved
tarantool/connection.py Show resolved Hide resolved
tarantool/connection.py Outdated Show resolved Hide resolved
tarantool/error.py Outdated Show resolved Hide resolved
@GRISHNOV GRISHNOV force-pushed the igrishnov/gh-205-native-crud-api-support branch 4 times, most recently from 3ed26c5 to cc04dc4 Compare November 29, 2022 12:40
@GRISHNOV GRISHNOV marked this pull request as ready for review November 29, 2022 13:34
@GRISHNOV
Copy link
Contributor Author

Thank you!
At the moment, I have implemented tests and documentation, and removed the draft status from the PR

Copy link
Member

@DifferentialOrange DifferentialOrange left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the update!

See comments below. The main drawback of the current version is that tests are actually not run on CI, so I think you would be interested to fix it first and then fix everything that fails on CI.

There are some things that maybe are not covered yet, but there should be enough work for now.

docs/source/quick-start.rst Outdated Show resolved Hide resolved
docs/source/quick-start.rst Outdated Show resolved Hide resolved
docs/source/quick-start.rst Outdated Show resolved Hide resolved
docs/source/quick-start.rst Outdated Show resolved Hide resolved
docs/source/quick-start.rst Outdated Show resolved Hide resolved
test/suites/test_crud.py Show resolved Hide resolved
test/suites/test_crud.py Show resolved Hide resolved
test/suites/test_crud.py Outdated Show resolved Hide resolved
tarantool/connection.py Show resolved Hide resolved
tarantool/connection.py Show resolved Hide resolved
@GRISHNOV GRISHNOV force-pushed the igrishnov/gh-205-native-crud-api-support branch 13 times, most recently from 20f6c01 to da98380 Compare December 5, 2022 21:49
@GRISHNOV GRISHNOV force-pushed the igrishnov/gh-205-native-crud-api-support branch 3 times, most recently from b5de7af to 1611fc9 Compare December 12, 2022 09:21
Copy link
Member

@DifferentialOrange DifferentialOrange left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the long response. I've thought that error is less trickier, I've proposed the solution.

.github/workflows/packing.yml Outdated Show resolved Hide resolved
.github/workflows/testing.yml Outdated Show resolved Hide resolved
.github/workflows/testing.yml Outdated Show resolved Hide resolved
self.errs = args[1]
else:
# Sets tarantool.crud.CrudError object.
self.err = args[1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already an exception API in the connector. Receiving a different types of exceptions may be confusing, especially if they uses the same fields with different meanings.

self.errs = args[1]
else:
# Sets tarantool.crud.CrudError object.
self.err = args[1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example

>>> c.select('myspace')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/georgymoiseev/Development/github/tarantool/tarantool-python/tarantool/connection.py", line 1873, in select
    space_name = self.schema.get_space(space_name).sid
  File "/home/georgymoiseev/Development/github/tarantool/tarantool-python/tarantool/schema.py", line 216, in get_space
    return self.fetch_space(space)
  File "/home/georgymoiseev/Development/github/tarantool/tarantool-python/tarantool/schema.py", line 243, in fetch_space
    raise SchemaError(errmsg)
tarantool.error.SchemaError: There's no space with name 'myspace'
>>> c.crud_select('myspace')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/georgymoiseev/Development/github/tarantool/tarantool-python/tarantool/connection.py", line 2550, in crud_select
    raise CrudModuleError(None, CrudError(crud_resp[1]))
tarantool.error.CrudModuleError: (None, <tarantool.crud.CrudError object at 0x7f4864a525f0>)

info = ". Ensure that you're calling crud.router and user has sufficient grants"
e.message = e.message + info
e.extra_info.message = e.extra_info.message + info
e.args = (e.args[0], e.args[1] + info)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second, let's never mess with args.

>>> class MyError(Exception):
...     def __init__(self, *args):
...             pass
... 
>>> MyError(1, 2)
MyError(1, 2)
>>> e = MyError(1, 2)
>>> e.
e.args             e.with_traceback(  
>>> e.args
(1, 2)

args exception attribute is essentially a copy of "what data was used to raise exception with".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either you forgot to commit it or lost it while doing other reworks, but it's still in call_crud

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, deleted

tarantool/crud.py Outdated Show resolved Hide resolved
tarantool/crud.py Outdated Show resolved Hide resolved
tarantool/crud.py Outdated Show resolved Hide resolved
tarantool/crud.py Show resolved Hide resolved
@GRISHNOV GRISHNOV force-pushed the igrishnov/gh-205-native-crud-api-support branch from 1611fc9 to 3455edc Compare December 19, 2022 14:16
.github/workflows/testing.yml Show resolved Hide resolved
.. _crud: https://github.com/tarantool/crud
"""

def __init__(self, *args):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there are always two positional arguments, why use *args instead of def __init__(self, success, error):

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, now using __init__(self, success, error):

"""

# Sets tarantool.crud.CrudResult object.
self.success = args[0]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no success other than nil for non-many requests, isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, deleted

self.errs = args[1]
else:
# Sets tarantool.crud.CrudError object.
self.err = args[1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not yet done: CrudModuleError inherits DatabaseError, but it never calls its __init__ (like here:

super(NetworkError, self).__init__(0, self.message)
)

self.errs = args[1]
else:
# Sets tarantool.crud.CrudError object.
self.err = args[1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same about CrudModuleManyError

"""


def wrapCrudUndefinedProcedureError(e: DatabaseError):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no wrapCrudUndefinedProcedureError now, but there is also no exceptions chaining yet and you still overwrite exception fields.

info = ". Ensure that you're calling crud.router and user has sufficient grants"
e.message = e.message + info
e.extra_info.message = e.extra_info.message + info
e.args = (e.args[0], e.args[1] + info)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either you forgot to commit it or lost it while doing other reworks, but it's still in call_crud


def crud_insert(self, space_name, values, opts={}, *, mode=Mode.ANY):
"""
Execute an CRUD_INSERT request on the pool server:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUCH_STYLE in other documentation strings refer to IPROTO_REQUEST codes (omitting the IPROTO_ part), so writing CRUD_INSERT here and below may be a bit confusing.

You may ignore this comment if you wish.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, rewritten without uppercase

@GRISHNOV GRISHNOV force-pushed the igrishnov/gh-205-native-crud-api-support branch from 3455edc to 2e88041 Compare December 21, 2022 13:56
@GRISHNOV
Copy link
Contributor Author

Thank you for your feedback! I have updated the PR description and tried to answer all the comments on the code review

Copy link
Member

@DifferentialOrange DifferentialOrange left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your updates! Let's resolve a couple of minor comments and I'll merge this one.

tarantool/crud.py Outdated Show resolved Hide resolved
tarantool/crud.py Outdated Show resolved Hide resolved
tarantool/error.py Outdated Show resolved Hide resolved
docs/source/quick-start.rst Show resolved Hide resolved
Adds native api support for crud module [1]
to use it from a connection object.

1. github.com/tarantool/crud

Closes #205
@GRISHNOV GRISHNOV force-pushed the igrishnov/gh-205-native-crud-api-support branch from 2e88041 to 832ab10 Compare December 22, 2022 08:17
@DifferentialOrange DifferentialOrange merged commit 4a791dc into master Dec 22, 2022
@DifferentialOrange DifferentialOrange deleted the igrishnov/gh-205-native-crud-api-support branch December 22, 2022 08:45
DifferentialOrange added a commit that referenced this pull request Dec 22, 2022
DifferentialOrange added a commit that referenced this pull request Dec 22, 2022
Docker debian/ubuntu images are sudoless.

Follows #264
DifferentialOrange added a commit that referenced this pull request Dec 22, 2022
Set valid package manager for fedora. Fix using apt for docker:
debian/ubuntu containers are sudoless.

Follows #264
DifferentialOrange added a commit that referenced this pull request Dec 22, 2022
Set valid package manager for fedora. Fix using apt for docker:
debian/ubuntu containers are sudoless.

Follows #264
DifferentialOrange added a commit that referenced this pull request Dec 22, 2022
Set valid package manager for fedora. Fix using apt for docker:
debian/ubuntu containers are sudoless.

Follows #264
DifferentialOrange added a commit that referenced this pull request Dec 22, 2022
Set valid package manager for fedora. Fix using apt for docker:
debian/ubuntu containers are sudoless.

Follows #264
DifferentialOrange added a commit that referenced this pull request Dec 22, 2022
Set valid package manager for fedora. Fix using apt for docker:
debian/ubuntu containers are sudoless.

Follows #264
DifferentialOrange added a commit that referenced this pull request Dec 22, 2022
Set valid package manager for fedora. Fix using apt for docker:
debian/ubuntu containers are sudoless.

Follows #264
DifferentialOrange added a commit to tarantool/doc that referenced this pull request Sep 4, 2023
tarantool-python static analysis was introduced in [1], CRUD API was
introduced in [2].

1. tarantool/tarantool-python#289
2. tarantool/tarantool-python#264
DifferentialOrange added a commit to tarantool/doc that referenced this pull request Sep 4, 2023
tarantool-python static analysis was introduced in [1], CRUD API was
introduced in [2].

1. tarantool/tarantool-python#289
2. tarantool/tarantool-python#264
p7nov pushed a commit to tarantool/doc that referenced this pull request Oct 10, 2023
* connectors: remove queue-python

tarantool/queue-python [1] last commit was in 2016. Module does not
support Tarantool 1.6+.

1. https://github.com/tarantool/queue-python

* connectors: update aiotarantool status

shveenkov/aiotarantool last commit was in 2019.

1. https://github.com/shveenkov/aiotarantool

* connectors: remove gtarantool from comparison

shveenkov/gtarantool [1] last commit was in 2016. It is useless for
modern Tarantool users.

1. https://github.com/shveenkov/gtarantool

* connectors: update comparison table

tarantool-python static analysis was introduced in [1], CRUD API was
introduced in [2].

1. tarantool/tarantool-python#289
2. tarantool/tarantool-python#264
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Convenient API for tarantool/crud
3 participants