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

Tortoise.init() refactoring and transactions module #29

Merged
merged 60 commits into from
Aug 1, 2018

Conversation

abondar
Copy link
Member

@abondar abondar commented Jul 17, 2018

#14
This seems to be biggest change that occurred to tortoise and first one to bring so much backward incompatible changes.

  • Tortoise.init() is now coroutine that handles all connections and models discovery.
  • New transactions that finally work without explicit connection passing and also can be used for easily changing db when working with models.

This is still work in progress, I want to make few more changes, update all documentation and may be some more tests.

@coveralls
Copy link

coveralls commented Jul 17, 2018

Pull Request Test Coverage Report for Build 108

  • 354 of 582 (60.82%) changed or added relevant lines in 21 files are covered.
  • 105 unchanged lines in 5 files lost coverage.
  • Overall coverage increased (+2.2%) to 90.653%

Changes Missing Coverage Covered Lines Changed/Added Lines %
tortoise/transactions.py 33 34 97.06%
tortoise/backends/asyncpg/init.py 0 2 0.0%
tortoise/queryset.py 21 23 91.3%
tortoise/backends/mysql/init.py 0 2 0.0%
tortoise/contrib/test/init.py 44 46 95.65%
tortoise/backends/base/client.py 8 11 72.73%
tortoise/init.py 118 121 97.52%
tortoise/models.py 44 48 91.67%
tortoise/backends/mysql/schema_generator.py 0 13 0.0%
tortoise/backends/mysql/executor.py 0 29 0.0%
Files with Coverage Reduction New Missed Lines %
tortoise/backends/base/executor.py 1 95.56%
tortoise/backends/base/client.py 6 90.74%
tortoise/backends/asyncpg/schema_generator.py 8 100.0%
tortoise/backends/asyncpg/executor.py 13 100.0%
tortoise/backends/asyncpg/client.py 77 83.01%
Totals Coverage Status
Change from base Build 63: 2.2%
Covered Lines: 1986
Relevant Lines: 2134

💛 - Coveralls

await generate_schema(client)

tournament = Tournament(name='New Tournament')
await Tortoise.init(config_file='config.json')
Copy link
Member

Choose a reason for hiding this comment

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

Could we please have this split up?
I mean Tortoise.init() should instead recieve configuration in a dict, and for cases where we want to have support of config files or enviromment variables should be done as a layer above that.

This will make it easier to wrap the ORM in another framework.

Copy link
Member Author

Choose a reason for hiding this comment

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

Well - it already receives config as dict as main method of init - look for postgres example.
config_file is just alternative option for those who use tortoise directly

Copy link
Member

Choose a reason for hiding this comment

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

👍

'port': '54325',
'user': 'tortoise',
'password': 'qwerty123',
'database': 'test',
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to use the db_url stuff I did?
It is a lot more concise, and since we changed over to DB-URLS at work it really made configuration much simpler, as e.g. with Docker you can easily inject environment variables, so we use that for configuration.

Copy link
Member Author

@abondar abondar Jul 17, 2018

Choose a reason for hiding this comment

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

Yes, it's possible. I refactored db_url to config_generator - you can see how config generated for it in test_db_url

Copy link
Member

Choose a reason for hiding this comment

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

I feel we should make the examples use that, as it is more concise.
Or just document both, a "shorthand" that is fine for most use cases, and an "advanced"?

saved_event = await Event.filter(name='Updated name').first()
print(saved_event)

@atomic()
Copy link
Member

Choose a reason for hiding this comment

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

YAY!
I had lots of reservations about how transactions worked, as they were very non-intuive.

Is it possible to do with atomic:?

Copy link
Member Author

Choose a reason for hiding this comment

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

You can do it with async with in_transaction(): without calling it from connection. I separated methods because I am not sure it's intuitive to have function overloaded with two separate effects.

Copy link
Member

Choose a reason for hiding this comment

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

It makes sense to me to use the same word.
The decorator wraps the whole function in a transaction.
The with statement wraps the whole block in a transaction.

A different question, I wanted to do a test case where I do the following (but could not get it working with the previous transaction system):

  1. Enter transaction
  2. make change 1
  3. Test change 1
  4. Commit
  5. Test change 1
  6. make change 2
  7. test change 2
  8. rollback
  9. test change 2 fail

Could you add such a test case (and any variations you can think of)?

Copy link
Member

@grigi grigi left a comment

Choose a reason for hiding this comment

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

Looking good so far

)

related_model._meta.backward_fk_fields.add(backward_relation_name)
related_model._meta.fetch_fields.add(backward_relation_name)
related_model._meta.fields_map[backward_relation_name] = fk_relation
related_model._meta.fields_map[backward_relation_name] = relation
Copy link
Member

Choose a reason for hiding this comment

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

The reason I split relation into fk_relation and m2m_relation was because these two variables are actually different types, and the type checker got really badly confused.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, it's just my bad, I merged it badly today, will revert back



def expand_db_url(db_url: str, testing: bool = False) -> dict:
def generate_config(
Copy link
Member

Choose a reason for hiding this comment

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

Not sure I like this.
Please try to keep to the single responsibility principal where you can.
e.g.
One function to resolve the {engine-credentials} dict, and another to generate the config per app.
Mostly to keep it easier to test, but also minimises function complexity, so makes it less invasive when one inevitably wants to refactor something.

An asyncio capable test class that will ensure that an partially isolated test db
is available for each test.
An asyncio capable test class that will ensure that each test will be run at
separate transaction that will rollback on finish.
Copy link
Member

Choose a reason for hiding this comment

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

👍

tox.ini Outdated
@@ -1,5 +1,5 @@
[tox]
envlist = py{35,36}-{sqlite,postgres}
envlist = py{36,37}-{sqlite,postgres}
Copy link
Member

Choose a reason for hiding this comment

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

You can leave it as py{35,36,37}... and then just set:
skip_missing_interpreters = True
To skip that which you don't have installed.

This was referenced Jul 17, 2018
@grigi
Copy link
Member

grigi commented Jul 20, 2018

I have been playing with the test runner, there is a lot of things not quite right for the test runner.

I have added initialiser and finaliser so that Testcase cleans up after itself, and does the migrations exactly once per test process.
There is issues with transactions on SQLite, and also the _drop_databases() command doesn't seem to actually drop anything?

Anyways I'm just stating that I'm refactoring the test suite again.

abondar and others added 2 commits July 25, 2018 12:01
If a pk isn't defined, we create a pk called id for that model.
Also added Model method tests.
@grigi
Copy link
Member

grigi commented Jul 26, 2018

I have been building tests for init, and actually now have feedback to give.
There is some inconsistencies in how it appears to work.
I'll get to it the weekend, and it may be me missing some minor point.

grigi and others added 6 commits July 29, 2018 07:49
Update 1:
* Fixed resource leak in init_tests.py

Update 2:
* Restored bad merge for fk_relation and m2m_relation in Tortoise.init()
* More typing annotations
* Tortoise.init() directly supports generate_config parameters
* Break up generate_config to use generate_db_url again
* Fix Model.get_or_create to return actual instance, not awaitable
* get_backward_fk_filters was never called
* Changed generate_config to take a dict of app: [model modules]
* Changed generate_config to call DB config "default" by default.

Update 3:
* Fix inconsistency in shorthand format
Added more Tortoise.init() tests
@grigi
Copy link
Member

grigi commented Jul 31, 2018

@Zeliboba5 At this stage I'm happy with this PR, and I'm giving the 👍 to merge it to master.
Then we can look at #37 a bit more leisurely.

@grigi
Copy link
Member

grigi commented Jul 31, 2018

And great working with you 🙇‍♂️

Copy link
Member

@grigi grigi left a comment

Choose a reason for hiding this comment

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

👍

@abondar
Copy link
Member Author

abondar commented Jul 31, 2018

@grigi Do you think we should do anything special about this PR, considering it will be backward incompatible? I don't want to publish it as 1.0 already, so may be we should just make some special mark in changelog?

@grigi
Copy link
Member

grigi commented Jul 31, 2018

@Zeliboba5: v0.10.0 + a mention that it has breaking changes re init should be enough, I think.
Maybe a short little blurb with examples in the CHANGELOG.rst file?

@abondar
Copy link
Member Author

abondar commented Aug 1, 2018

I am really glad that we managed to make this release.
@grigi @etzelwu Thanks for great work 👍

@abondar abondar merged commit 7a3fa6d into master Aug 1, 2018
@abondar abondar deleted the feature/init-refactoring branch November 23, 2018 08:23
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.

None yet

4 participants