This repository was archived by the owner on Dec 20, 2023. It is now read-only.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This is a (relatively) minor refactor of the contexts dependency injection system I implemented in the recent large refactor. It's still the same idea, but the developer ergonomics are much improved and the weird application/auth duplication (which led to me calling the application implementation "frame" instead of complex) is removed. The library functions for this system now live in their own package, while the context.py in the auth and application packages only define the Contexts class and the stubs for all context functions.
Since this system wasn't explained in detail in #59, I'll try to give a short overview. I've also added better documentation in the docs.
We've had different versions of how database functions could be called:
mocker.patch
solution had the function location as a string. Each time the function name changed, the refactoring often didn't change this. Plus, there was no real warning or error given if things didn't match.data
module (so not importing them as local variables and usingdata.<module>.<function>
)mocker.patch
now worked without that big class. This was Db refactor #56. The ergonomics when writing new routers was very good here, as you simply called the function you wanted, as long as you imported it in a specific way. However, it still suffered from the fact thatmocker.patch
requires a string.mocker.patch
was too fragile and that a changing implementation was too hard to deal with. So instead I moved to a dependency injection system, which was the current solution before this PR. It basically mixes the top-level and big class solution by creating a big class which contains only stubs (so the function name and parameters but not implementation, kinda like a header file) and then you define the implementation as a top-level function. By using decorators you collect all these implementations and then at application startup you replace the stubs with the implementations. However, the big problem was that you had to call the stubs on the big class object in the consuming application code. But if you then want to look up the implementation, you would only get to these stubs. There was no easy link to the actual main implementation as IDE's cannot follow the decorator magic. This was really bad ergonomics.This PR solves this latter problem. There is still the stub class, but instead of calling . you can now call the top-level function that contains the main implementation directly. However, you provide the Context class as the first argument, which the decorator uses to lookup the desired implementation and replaces the function with that. The only downside is that each function now has this Context variable as its first arg, even though it appears unused in the actual implementation. This isn't a big deal though and might be fixable as IDE's improve.
So a short guide:
Each function that you want to be able to easily replace in test should live in a file in the
context
module of the package's respectivedata
module.register
function to then register your function using a decorator. Provide the registry type (the actual class, not an instance) as an argument. This is necessary so we later know to what global container to add it.include_registry
function for your global Contexts class on the local container ContextRegistry. All functions will be added to the correct global container contexts (the stub classes will get their implementation).The only added work you are doing, considering usually a file and registration already exist, is adding the decorator and adding a stub. This is only a small amount of extra work. In the test you need to provide a mock implementation, but this was always necessary. If the implementation changes, the program will error if the stub does not match. Once the stub is fixed, the test implementation will now complain if it is no longer in sync. This way, it is easy to keep everything synced.
Unfortunately, as these functions were used quite a lot, especially in the auth package, this does lead to quite a few changed lines and files. This is really is a great DX improvement, though, so definitely worth it!