-
Notifications
You must be signed in to change notification settings - Fork 11
Implement database abstraction #42
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
Conversation
So, it turns out it became a quite big refactoring! Here is a summary of what I did:
I also took this opportunity to improve the structuration of the Pydantic models:
Admittedly, this is a quite big PR. I've tested the changes as much as I could and noticed no breaking changes. Waiting for your feedback on this :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes looks great! Significantly cleaner and will make for a much easier implementation of a client when we get to it.
Left some comments and suggested updates.
|
||
|
||
@router.post("", response_model=Datasource) | ||
@router.post("") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@router.post("") | |
@router.post("", response_model=Datasource) |
@@ -41,52 +40,22 @@ def list_supported_expectations(): | |||
return JSONResponse(status_code=status.HTTP_200_OK, content=content) | |||
|
|||
|
|||
@router.put("/{expectation_id}/enable", response_model=Expectation) | |||
@router.put("/{expectation_id}/enable") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you wanting to add response models for Expectation in another update?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is the same as stated above for Datasource
: since we actually have objects inheriting from Expectation
, we'll lose all their specific fields if we set the response_model
, the output will be "down-casted" to a base Expectation
.
@KentonParton In 18f8f89, I implemented the discriminator approach we talked about. After all, things went quite well. There is one small thing however which is surprising but actually works very well. The model we use for annotation is actually the
Hence, I named the discriminator model We are also able to get rid of swiple/backend/app/core/expectations.py Line 13 in 18f8f89
Another small refinement I made is to modify the schema so we have the actual value of swiple/backend/app/models/expectation.py Lines 41 to 45 in 18f8f89
swiple/backend/app/core/expectations.py Line 15 in 18f8f89
swiple/frontend/src/screens/dataset/components/ExpectationModal.jsx Lines 169 to 184 in 18f8f89
Let me know what. you think about it and we can go forward with the same approach for |
This is looking great @frankie567 💪 I like that:
Let's do the same for P.S. I am getting a model validation error for GET /datasets swiple_api | File "/code/./app/api/api_v1/endpoints/dataset.py", line 55, in list_datasets
swiple_api | return repository.query(query, size=1000)
swiple_api | File "/code/./app/repositories/base.py", line 27, in query
swiple_api | return [
swiple_api | File "/code/./app/repositories/base.py", line 28, in <listcomp>
swiple_api | self._get_object_from_dict(result["_source"]) for result in results
swiple_api | File "/code/./app/repositories/base.py", line 90, in _get_object_from_dict
swiple_api | return self.model_class.parse_obj(d)
swiple_api | File "/usr/local/lib/python3.9/site-packages/pydantic/main.py", line 521, in parse_obj
swiple_api | return cls(**obj)
swiple_api | File "/usr/local/lib/python3.9/site-packages/pydantic/main.py", line 341, in __init__
swiple_api | raise validation_error
swiple_api | pydantic.error_wrappers.ValidationError: 1 validation error for Dataset
swiple_api | key
swiple_api | none is not an allowed value (type=type_error.none.not_allowed)
|
Fixed! So, here we are: Datasource is also ported to the discriminator approach. Looks much much cleaner! OpenAPI is working well with proper annotations. |
Nice work, LGTM! |
Description
The goal of those changes is to implement an abstraction layer for querying the database. The objective is to always get proper Pydantic models to return to the API.
Basically, we have a
BaseRepository
class which will contain the common/generic logic for querying the database. Notice how this class expects to have the Pydantic model class and the name of the index as class variables.For each model, we'll have a dedicated repository extending
BaseRepository
. To prove the concept, I currently just implementedDatasourceRepository
.With this pattern, it's easy to add specific query or operations we want to reuse, like
get_by_name
in this example. This way, we avoid to have too much OpenAPI query leaking in every parts of the codebase: everything stay in the repository class.To instantiate those repositories, we define callable dependencies for FastAPI, like
get_datasource_repository
. It's a good pattern that may help us in the long run, especially if we want to write unit tests. For now, the underlying OpenSearch client is hard-wired but it could also be made as a dependency for convenience.Finally, we can use it in our API endpoints. By injecting the repository in the datasource endpoints, we are able to directly query the DB and get proper Pydantic objects.
For convenience, I've also implemented a generic shortcut
get_by_key_or_404
, which can get an object by key or automatically raise a 404 if not found.Please tell me now what you think about this before I implement this pattern for the other models 😄
Type of change
Checklist: