-
Notifications
You must be signed in to change notification settings - Fork 16
TinyDB query api / Should we change the query API ? #15
Comments
First of all, thank you for your comprehensive issue. Your English is not bad at all (not that I am the best judge for that ;). I've never heard about TinyDB before, but the query language really reminds me what is done within SQLAlchemy. This is a different way to express queries, with its pros and cons comparing to Django's way to do it (which, by the way, I've never seen elsewhere). I really appreciate the readability of this: manager.filter(q.employee.age > 37) Versus this: manager.filter(employee__age=lifter.gt(37)) Using explicit query objects everywhere also prevent some issues. Using lifter, I don't know if's even possible to query properly an attribute that has an underscore in it like this (and I don't wanna know): # 3 underscores. Ugly!
manager.filter(employee___private_attribute=42) However, this is a little more verbose: import lifter
lifter.load(data).filter(lifter.q.field == value) Than: import lifter
lifter.load(data).filter(field=value) There is a decision to make here:
To be honest, I don't think we should offer more than one way to make queries in lifter. Maintaining multiple ways to do the same things means we have to test everything twice, hard more issues, more work, more of everything. This is a point I'm ready to discuss, but at least you have my current point of view (it can evolve). However, since the project is still really young and in alpha-state, it's the perfect time to make hard, backward-compatibility breaking decisions. If there is a moment when we should choose to switch from one way to do queries to another, it's now. SQLAlchemy is also a popular ORM out there, so is TinyDB and maybe a lot of other implementions that use a similar query API. There is no reason to stick do Django's way to do things if a technically better option is available and if more developpers have already used a similar API. I'm glad you shared your working example here, because it raises those two interesting questions, for which I have absolutely no fixed answer. I'd really appreciate if other people could share there thoughts about this. Meanwhile, @angru, would you consider continue the work on this API, and especially unittesting your proposal? I just want to be sure that besides the change to the way we express queries, their effect is absolutely the same. This would mean forking lifter, replacing lifter query module with your own, and updating existing unittest accordingly. There is no urge though, and I would totally understand if you prefer to wait until a decision is made before continuing the work on this. Anyway thank you for your contribution! |
I tried your code and everything works as expected. The only part that you do not handle is querying against iterables (see #7 for more information). I must admit I'm really tempted to bundle the whole thing under a Do you think it is a good idea ? |
Okay, @angru don't bother writing unittests for it, I've made a whole new branch using your code and updated the whole test suite to use this query API: https://github.com/EliotBerriot/lifter/tree/feature/tiny. I had to make little changes, for example to make the queryset store its data after it has been evaluated for the first time. Tests pass on Python 3.4 and 3.5 and fail on previous versions, I need to look why. Feel free to send pull requests to the feature/tiny branch. |
I've pushed a few commits that implements some changes to the way queries are made using the API. In your code, almost everything was inheriting from I don't think it was entirely relevant since a I also didn't like having a single instance of By thinking about these two points, I've found another way to achieve the same thing, which seems more elegant to me, and similar to what is done in SQLAlchemy: from lifter import tiny
# We create a Model class for handling our data
User = tiny.Model('User')
manager = User.load(values)
# Filtering
manager.filter(User.name == 'Gengis')
# Ordering (I got rid of the Order.ASC and Order.DESC here), that's still one less import
manager.order_by(User.age, reverse=True)
# Aggregating
manager.aggregate((User.age, sum), (User.age, mean)) What I like with this approach is that we keep imports to a bare minimum (no need to import If we want shorter code, we can still do: from lifter import tiny
M = tiny.Model('User')
manager = M.load(values)
manager.filter(M.name == 'Gengis') Having an explicitely declared model also enable some advanced feature such as default ordering or annotating: M = tiny.Model('User', ordering='-age')
manager = M.load(values)
# Because we use an intermediate model, we can store additional data on model instances
# without touching the data we load in the manager
manager.annotate(name_length=lambda user: len(user.name)).order_by(User.name_length) What do you think? |
(Sorry about the spam). I've continued the work on the API, and I've decided that for now, we will keep both API available. It's not that hard to maintain both, since internally all django-like lookups are converted to the new one. All tests pass (at least under Python3), which means that the change is fully backward compatible. EDIT: okay, now everything pass under all versions of Python |
0.2 (2016-2-23) --------------- This is quite an important release: * A whole new API is now available to make queries, see #15 for more information [angru, eliotberriot] * Querysets are now lazy * A brand new documentation is now available at http://lifter.readthedocs.org * Splitted some huge files into submodules for more clarity Considering the new query API, we basically switched from django's ORM-style (keyword engine) to SQLAlchemy style (explicit engine). The keyword engine is still available and will continue to work as before. It is neither under depreciation at the moment, but using the new engine is recommended.
Hi, sorry about the silence, I was in three-day hike. I was going to continue work on this issue(and unittests of course) but I didn't expect you will be so intersted and I'm especially didn't expect that you will accept these backward-compatibility breaking changes. It's great news and I agree with your corrections. Though there is some thing not fully clear to me. Do you agree with pretty ugly syntax of complex queries? # OR query(brackets requred)
young_and_old_users = manager.filter((User.age < 14) | (User.age > 60))
# of course we can use chaining but it's not perfect too
young_and_old_users = manager.filter(User.age < 14).filter(User.age >60) |
At the moment, the change is not a backward-compatibility breaker. Internally, all keyword-queries are converted to these new, explicit queries. See http://lifter.readthedocs.org/en/latest/overview.html#supported-queries for more informations. Regarding complex queries, I made the problem disapear for # Internaly, the engine cast this to (User.age < 14) & (User.is_active == True)
young_and_active_users = manager.filter(User.age < 14, User.is_active == True) For Also, the fact is, using the keyword engine, there was no way to perform these I hope you had a good hike! I'm sorry I didn't wait for your answer, but I had time to work on this, and since it was blocking pretty much anything else, I decided it was a top priority. At least now, we have a fully working engine we can start building more complex and efficient queries on. |
Hi, first of all, please exuce my poor English.
I was looking at TinyDB query api and I think it looks pretty nice. So i trying to reproduce their api in terms of lifter. WIP you can find here. It is Python 3.5 only yet but it is not so hard to port to older versions. It is also lazy(almost). I understand it is very different from lifter queries and not perfect in some cases(brackets in complex queries, q/p/a objects) but it will be great to know your thoughts about this. Or maybe you can find some inspration in this.
In my mind it looks something like this:
usage
Please note that q/p/a are important entities that using in different methods
filter
get
exclude
order_by
count
exists
# I have some doubts about this method
first
last
values
values_list
distinct
spanning lookups
complex lookups
aggregations
So what do you think?
The text was updated successfully, but these errors were encountered: