-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
APIv3: add more generic fields #11205
Conversation
Add `aliases` field to the `VersionSerializer` that's used to serialize `Version` objects. This field is a list of versions the current version "points to". Example `latest` version "points to" `origin/main`, and `stable` version "points to" `v3.12`. This is known in the code as "original stable/latest version". Besides, this PR adds `versions.stable` and `versions.latest` fields to the addons API which will be useful to solve front-end issues in a more generic way.
This comment was marked as off-topic.
This comment was marked as off-topic.
It seems I ended up wanting the |
This is the pattern I want to have here, serializing the full object instead of a few small set of fields. This is a lot more generic, simplifies the interaction between the API and JS Addons. Besides, it exposes the same objects as the regular APIv3 which won't change over time.
I ended up implementing exactly what I want here: However, this shows the problem we have been having with the resolver for the URLs which makes a lot of queries to the DB:
I'd like to work on the performance of this because it's currently unacceptable for project with ~30 versions. Related: |
I was able to optimize this a lot 😄 . I remove the I found it's pretty expensive for us to resolve a Version URL. Mainly because it needs to check if the project as a custom domain, and also if it's from a subproject. So, for each version we want to resolve the documentation URL, we need to perform these queries:
They could be cached (and probably the DB is already doing this but we are still hitting the DB, tho) since it's based on the project and not the version and it will always return the same for all the versions of the project. I will take a deeper look at the code and see if I can optimize them. |
Yeap, basically those extra ~80 queries are because of this resolve: readthedocs.org/readthedocs/api/v3/serializers.py Lines 325 to 329 in 869f953
|
I was able to share a There is only one that I had to increase, the test for translations. This is because I'm not sharing the resolver with |
…thedocs/readthedocs.org into humitos/addons-api-versions-aliases
A better way of resolving this issue will be done in #10456
We are sorting the translations in the front end
Follow the changes done in readthedocs/readthedocs.org#11205
It would be awesome to get a review on this PR. It will unblock a few other PRs that are related to addons. Ideally, I'd like to deploy all of this next week. |
Implements the changes in the API made in readthedocs/readthedocs.org#11205. We need to merge that PR and deploy addons together with the changes in this PR. In this PR there are mainly no changes in the logic. The most important thing to mention here is that we are standardizing the usage of the API response and pinning to `v1` now (we were using `v0-alpha`). This means: - we will be exposing the `v1` to users via the js custom event (see #64) - we are using standard/shared APIv3 serializers for resources for this `/addons/` endpoint - any breaking change we have to do in the future it will increase the API response version I'm not sure there are too much to review here, but I'd appreciate at least a quick look at these changes. I'm happy to answer some questions you may have here or on the underlying PR about the API response changes. ### Related: * Closes #132 * Unblocks #265 * Unblocks theme integration (our theme and CPython's one) * Requires readthedocs/readthedocs.org#11205 --------- Co-authored-by: Anthony <aj@ohess.org>
@@ -322,13 +323,15 @@ class VersionURLsSerializer(BaseLinksSerializer, serializers.Serializer): | |||
dashboard = VersionDashboardURLsSerializer(source="*") | |||
|
|||
def get_documentation(self, obj): | |||
return Resolver().resolve_version( | |||
resolver = getattr(self.parent, "resolver", Resolver()) |
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.
Can we pass this to the constructor? That way is more explicit, rather than relying on the attribute being present in the parent serialzer.
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.
Maybe at this line
urls = VersionURLsSerializer(source="*") |
However, that would be a new instance of the Resolver since it's at the class definition and we want to share the instance the parent serializer is using due to caching purposes.
Are you thinking something different here?
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.
Looks like you could pass this in the context of the serializer, something like:
class ChildSerializer:
def get_documentation(self, obj):
resolver = self.context.get('resolver')
...
class ParentSerializer:
child = ChildSerializer()
def __init__(self, resolver):
self.fields['child'].context['resolver'] = resolver
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.
Thanks. It sounds similar to what I did but more standardized. I found some documentation about this at https://www.django-rest-framework.org/api-guide/serializers/#including-extra-context
I opened #11280 to track this refactor as it's not a blocker for now and it may not be required in the future anyways.
def get_documentation(self, obj): | ||
version_slug = getattr(self.parent, "version_slug", None) | ||
resolver = getattr(self.parent, "resolver", Resolver()) | ||
return obj.get_docs_url(version_slug=version_slug, resolver=resolver) |
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.
If we already have the version object, we can just call resolver.resolve_version
, so it doesn't query the version object from the DB.
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.
I made this change, but for some reason the amount of queries increased using resolver.resolve_version
:
TestReadTheDocsConfigJson::test_number_of_queries_url_subproject - AssertionError: 32 != 31 : 32 queries executed, 31 expected
TestReadTheDocsConfigJson::test_number_of_queries_url_translations - AssertionError: 60 != 56 : 60 queries executed, 56 expected
This is the diff applied:
diff --git a/common b/common
--- a/common
+++ b/common
@@ -1 +1 @@
-Subproject commit 4af0fffd2cbeeb40f0a71b875beb99d6dc88a355
+Subproject commit 4af0fffd2cbeeb40f0a71b875beb99d6dc88a355-dirty
diff --git a/readthedocs/api/v3/serializers.py b/readthedocs/api/v3/serializers.py
index f0eb9237c..03c4d9a03 100644
--- a/readthedocs/api/v3/serializers.py
+++ b/readthedocs/api/v3/serializers.py
@@ -475,9 +475,9 @@ class ProjectURLsSerializer(BaseLinksSerializer, serializers.Serializer):
return self._absolute_url(path)
def get_documentation(self, obj):
- version_slug = getattr(self.parent, "version_slug", None)
+ version = getattr(self.parent, "version", None)
resolver = getattr(self.parent, "resolver", Resolver())
- return obj.get_docs_url(version_slug=version_slug, resolver=resolver)
+ return resolver.resolve_version(project=obj, version=version)
class RepositorySerializer(serializers.Serializer):
@@ -801,8 +801,8 @@ class ProjectSerializer(FlexFieldsModelSerializer):
}
def __init__(self, *args, **kwargs):
- # Receive a `Version.slug` here to build URLs properly
- self.version_slug = kwargs.pop("version_slug", None)
+ # Receive a `Version` here to build URLs properly
+ self.version = kwargs.pop("version", None)
# Use a shared resolver to reduce the amount of DB queries while
# resolving version URLs.
diff --git a/readthedocs/proxito/views/hosting.py b/readthedocs/proxito/views/hosting.py
index f70638632..52a477ead 100644
--- a/readthedocs/proxito/views/hosting.py
+++ b/readthedocs/proxito/views/hosting.py
@@ -354,12 +354,12 @@ class AddonsResponse:
"current": ProjectSerializerNoLinks(
project,
resolver=resolver,
- version_slug=version.slug if version else None,
+ version=version,
).data,
"translations": ProjectSerializerNoLinks(
project_translations,
resolver=resolver,
- version_slug=version.slug if version else None,
+ version=version,
many=True,
).data,
},
I will leave the code as-is for now. Let me know if you understand why or feel free to open an issue to track this performance improvement.
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.
My two guesses:
- Maybe the version doesn't have all fields (e.g, "only" was used).
- A method needs to be cached.
@stsewd I made all the requested changes and applied the suggestions, thanks! Let me know if this is ready to merge and deploy in your opinion. |
Installs a version of our theme from a pull request that has support for the Addons integration using the `CustomEvent`. Related: readthedocs/sphinx_rtd_theme#1526 Requires: #11205
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.
Nothing blocking, feel free to do the other improvements in another PR or open some issues.
Installs a version of our theme from a pull request that has support for the Addons integration using the `CustomEvent`. Related: readthedocs/sphinx_rtd_theme#1526 Requires: #11205
…11279) * Docs: use the `sphinx-rtd-theme` with support for addons integration Installs a version of our theme from a pull request that has support for the Addons integration using the `CustomEvent`. Related: readthedocs/sphinx_rtd_theme#1526 Requires: #11205 * Disable fail on warning temporary * Delete doc-diff since it's included in addons now
This PR re-structures the Addons API response by removing specific fields and converting them into more general purpose fields. All the new fields render the whole object using the same serializers than APIv3 (https://docs.readthedocs.io/en/stable/api/v3.html)
Fields added
version.aliases
(added toVersionSerializer
) is a list of versions the version points to: known as "original stable/latest version"projects.translations
is a list of projects that are translation of the current oneversions.active
is a list of "active, built and not hidden" versions (basically, the useful ones) 1Fields removed
addons.non_latest_version_warning.versions
addons.flyout.downloads
addons.flyout.translations
addons.flyout.versions
These fields removed in this PR are not required anymore since the same information is into the new fields (they render the full object instead of just a selection of fields). This makes them great for general purpose data and untied them from a specific addon in particular.
Related issues
stable
/latest
aliases addons#132 (comment)versioning_scheme
addons#243project.versioning_scheme
for links addons#265 (comment)Requires
Required by
CustomEvent
on config load addons#60Footnotes
I'm happy to give this field a different name if you have a better suggestion here. ↩