-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Add modeling for intersphinx data #5289
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
Changes from all commits
83c6f40
db5fbd8
c2cd164
41b2857
806d3c2
0f82c26
639b219
67d909a
537c66a
647ae3f
5b7d599
1dfabe8
08296ae
1e087fb
12bdf04
8e015c9
444e739
1672421
462545e
f53e589
98aae46
c934046
96fa45a
d8d75d5
09b3c80
0da9283
1bc31d0
97851c4
021e12d
ab7a30c
fa72754
4ea9121
ec71993
18877c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,8 @@ | |
from django.utils import timezone | ||
from django.utils.translation import ugettext_lazy as _ | ||
from slumber.exceptions import HttpClientError | ||
from sphinx.ext import intersphinx | ||
|
||
|
||
from readthedocs.builds.constants import ( | ||
BUILD_STATE_BUILDING, | ||
|
@@ -58,6 +60,7 @@ | |
) | ||
from readthedocs.doc_builder.loader import get_builder_class | ||
from readthedocs.doc_builder.python_environments import Conda, Virtualenv | ||
from readthedocs.sphinx_domains.models import SphinxDomain | ||
from readthedocs.projects.models import APIProject | ||
from readthedocs.restapi.client import api as api_v2 | ||
from readthedocs.vcs_support import utils as vcs_support_utils | ||
|
@@ -1232,6 +1235,7 @@ def fileify(version_pk, commit): | |
), | ||
) | ||
_manage_imported_files(version, path, commit) | ||
_update_intersphinx_data(version, path, commit) | ||
else: | ||
log.info( | ||
LOG_TEMPLATE.format( | ||
|
@@ -1242,6 +1246,59 @@ def fileify(version_pk, commit): | |
) | ||
|
||
|
||
def _update_intersphinx_data(version, path, commit): | ||
""" | ||
Update intersphinx data for this version | ||
|
||
:param version: Version instance | ||
:param path: Path to search | ||
:param commit: Commit that updated path | ||
""" | ||
object_file = os.path.join(path, 'objects.inv') | ||
|
||
# These classes are copied from Sphinx | ||
# https://git.io/fhFbI | ||
class MockConfig: | ||
intersphinx_timeout = None | ||
tls_verify = False | ||
|
||
class MockApp: | ||
srcdir = '' | ||
config = MockConfig() | ||
|
||
def warn(self, msg): | ||
log.warning('Sphinx MockApp: %s', msg) | ||
|
||
invdata = intersphinx.fetch_inventory(MockApp(), '', object_file) | ||
for key, value in sorted(invdata.items() or {}): | ||
domain, _type = key.split(':') | ||
for name, einfo in sorted(value.items()): | ||
# project, version, url, display_name | ||
# ('Sphinx', '1.7.9', 'faq.html#epub-faq', 'Epub info') | ||
url = einfo[2] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd add a small comment with previous to this line, with the expected structure of this
or... a link to the docs. I found debugging this complicated later. |
||
if '#' in url: | ||
doc_name, anchor = url.split('#') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could use urlparse https://docs.python.org/3/library/urllib.parse.html, but the logic here is very simple, so not really sure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, feels unnecessary. It's also not a full URL, so it might be confused by it. |
||
else: | ||
doc_name, anchor = url, '' | ||
display_name = einfo[3] | ||
obj, _ = SphinxDomain.objects.get_or_create( | ||
project=version.project, | ||
version=version, | ||
domain=domain, | ||
name=name, | ||
display_name=display_name, | ||
type=_type, | ||
doc_name=doc_name, | ||
anchor=anchor, | ||
) | ||
if obj.commit != commit: | ||
obj.commit = commit | ||
obj.save() | ||
SphinxDomain.objects.filter(project=version.project, | ||
version=version | ||
).exclude(commit=commit).delete() | ||
|
||
|
||
def _manage_imported_files(version, path, commit): | ||
""" | ||
Update imported files for version. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
"""Domain Admin classes.""" | ||
from django.contrib import admin | ||
|
||
from .models import SphinxDomain | ||
|
||
|
||
class SphinxDomainAdmin(admin.ModelAdmin): | ||
list_filter = ('type', 'project') | ||
raw_id_fields = ('project', 'version') | ||
search_fields = ('doc_name', 'name') | ||
|
||
|
||
admin.site.register(SphinxDomain, SphinxDomainAdmin) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
"""Domain API classes.""" | ||
|
||
from rest_framework import serializers | ||
|
||
from readthedocs.restapi.views.model_views import UserSelectViewSet | ||
|
||
from .models import SphinxDomain | ||
|
||
|
||
class SphinxDomainSerializer(serializers.ModelSerializer): | ||
project = serializers.SlugRelatedField(slug_field='slug', read_only=True) | ||
version = serializers.SlugRelatedField(slug_field='slug', read_only=True) | ||
|
||
class Meta: | ||
model = SphinxDomain | ||
fields = ( | ||
'project', | ||
'version', | ||
'name', | ||
'display_name', | ||
'role_name', | ||
'docs_url', | ||
) | ||
|
||
|
||
class SphinxDomainAdminSerializer(SphinxDomainSerializer): | ||
|
||
class Meta(SphinxDomainSerializer.Meta): | ||
fields = '__all__' | ||
|
||
|
||
class SphinxDomainAPIView(UserSelectViewSet): # pylint: disable=too-many-ancestors | ||
model = SphinxDomain | ||
serializer_class = SphinxDomainSerializer | ||
admin_serializer_class = SphinxDomainAdminSerializer | ||
filter_fields = ( | ||
'project__slug', | ||
'version__slug', | ||
'domain', | ||
'type', | ||
'doc_name', | ||
'name', | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
""" | ||
Sphinx Domain modeling. | ||
|
||
http://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html | ||
""" | ||
|
||
from django.db import models | ||
from django.utils.translation import ugettext_lazy as _ | ||
|
||
from django_extensions.db.models import TimeStampedModel | ||
|
||
from readthedocs.builds.models import Version | ||
from readthedocs.core.resolver import resolve | ||
from readthedocs.projects.models import Project | ||
from readthedocs.projects.querysets import RelatedProjectQuerySet | ||
|
||
|
||
class SphinxDomain(TimeStampedModel): | ||
|
||
""" | ||
Information from a project about it's Sphinx domains. | ||
|
||
This captures data about API objects that exist in that codebase. | ||
""" | ||
|
||
project = models.ForeignKey( | ||
Project, | ||
related_name='sphinx_domains', | ||
) | ||
version = models.ForeignKey( | ||
Version, | ||
verbose_name=_('Version'), | ||
related_name='sphinx_domains', | ||
) | ||
commit = models.CharField(_('Commit'), max_length=255, null=True) | ||
|
||
domain = models.CharField( | ||
_('Domain'), | ||
max_length=255, | ||
) | ||
name = models.CharField( | ||
_('Name'), | ||
max_length=255, | ||
) | ||
display_name = models.CharField( | ||
_('Display Name'), | ||
max_length=255, | ||
) | ||
type = models.CharField( | ||
_('Type'), | ||
max_length=255, | ||
) | ||
doc_name = models.CharField( | ||
_('Doc Name'), | ||
max_length=255, | ||
) | ||
anchor = models.CharField( | ||
_('Anchor'), | ||
max_length=255, | ||
) | ||
objects = RelatedProjectQuerySet.as_manager() | ||
|
||
def __str__(self): | ||
return f''' | ||
SphinxDomain [{self.project.slug}:{self.version.slug}] | ||
[{self.domain}:{self.type}] {self.name} -> | ||
{self.doc_name}#{self.anchor} | ||
''' | ||
|
||
@property | ||
def role_name(self): | ||
return f'{self.domain}:{self.type}' | ||
|
||
@property | ||
def docs_url(self): | ||
path = self.doc_name | ||
if self.anchor: | ||
path += f'#{self.anchor}' | ||
full_url = resolve( | ||
project=self.project, | ||
version_slug=self.version.slug, | ||
filename=path, | ||
) | ||
return full_url |
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.
Why not to run this function in a celery tasks instead of calling syncly?
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.
It is already run inside of a celery task.