From cf3ad1479505f18bb76265c36491ffc39384bc47 Mon Sep 17 00:00:00 2001 From: Martin Mahner Date: Wed, 3 Jun 2020 19:01:14 +0200 Subject: [PATCH] First pass of Type Annotations --- dpaste/apps.py | 15 +++- dpaste/forms.py | 24 ++--- dpaste/highlight.py | 37 ++++---- dpaste/migrations/0001_initial.py | 87 ++++++++++++++----- dpaste/migrations/0002_auto_20170119_1038.py | 22 ++--- dpaste/migrations/0003_snippet_highlighted.py | 8 +- dpaste/migrations/0004_auto_20180107_1603.py | 10 ++- .../0005_remove_snippet_highlighted.py | 7 +- dpaste/migrations/0006_auto_20180622_1051.py | 15 +++- dpaste/migrations/0007_snippet_rtl.py | 8 +- dpaste/models.py | 15 ++-- dpaste/views.py | 79 ++++++++++++----- 12 files changed, 204 insertions(+), 123 deletions(-) diff --git a/dpaste/apps.py b/dpaste/apps.py index 98ef963..f590bad 100644 --- a/dpaste/apps.py +++ b/dpaste/apps.py @@ -1,4 +1,7 @@ +from typing import Dict, List, Optional, Tuple, Type, Union + from django.apps import AppConfig, apps +from django.core.handlers.wsgi import WSGIRequest from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ @@ -118,7 +121,7 @@ class dpasteAppConfig(AppConfig): PLAIN_CODE_SYMBOL = "_code" @property - def TEXT_FORMATTER(self): + def TEXT_FORMATTER(self,) -> List[Tuple[str, str, Type[object]]]: """ Choices list with all "Text" lexer. Prepend keys with an underscore so they don't accidentally clash with a Pygments Lexer name. @@ -130,6 +133,8 @@ class dpasteAppConfig(AppConfig): Lexer Highlight Class) If the Highlight Class is not given, PygmentsHighlighter is used. + + @FIXME: Make `Type[object]` use the Type[Highlighter] class. """ from dpaste.highlight import ( PlainTextHighlighter, @@ -144,7 +149,9 @@ class dpasteAppConfig(AppConfig): ] @property - def CODE_FORMATTER(self): + def CODE_FORMATTER( + self, + ) -> List[Union[Tuple[str, str, Type[object]], Tuple[str, str]]]: """ Choices list with all "Code" Lexer. Each list contains a lexer tuple of: @@ -621,7 +628,7 @@ class dpasteAppConfig(AppConfig): CACHE_TIMEOUT = 60 * 10 @staticmethod - def get_base_url(request=None): + def get_base_url(request: Optional[WSGIRequest] = None) -> str: """ String. The full qualified hostname and path to the dpaste instance. This is used to generate a link in the API response. If the "Sites" @@ -637,7 +644,7 @@ class dpasteAppConfig(AppConfig): return "https://dpaste-base-url.example.org" @property - def extra_template_context(self): + def extra_template_context(self) -> Dict[str, str]: """ Returns a dictionary with context variables which are passed to all Template Views. diff --git a/dpaste/forms.py b/dpaste/forms.py index 3bb6848..2b555ba 100644 --- a/dpaste/forms.py +++ b/dpaste/forms.py @@ -1,7 +1,9 @@ -import datetime +from datetime import datetime, timedelta +from typing import Dict, Optional, Tuple, Type, Union from django import forms from django.apps import apps +from django.core.handlers.wsgi import WSGIRequest from django.utils.translation import gettext_lazy as _ from .highlight import LEXER_CHOICES, LEXER_DEFAULT, LEXER_KEYS @@ -10,7 +12,9 @@ from .models import Snippet config = apps.get_app_config("dpaste") -def get_expire_values(expires): +def get_expire_values( + expires: str, +) -> Union[Tuple[None, int], Tuple[datetime, int]]: if expires == "never": expire_type = Snippet.EXPIRE_KEEP expires = None @@ -20,9 +24,7 @@ def get_expire_values(expires): else: expire_type = Snippet.EXPIRE_TIME expires = expires and expires or config.EXPIRE_DEFAULT - expires = datetime.datetime.now() + datetime.timedelta( - seconds=int(expires) - ) + expires = datetime.now() + timedelta(seconds=int(expires)) return expires, expire_type @@ -59,7 +61,7 @@ class SnippetForm(forms.ModelForm): model = Snippet fields = ("content", "lexer", "rtl") - def __init__(self, request, *args, **kwargs): + def __init__(self, request: WSGIRequest, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.request = request @@ -72,13 +74,13 @@ class SnippetForm(forms.ModelForm): if "l" in request.GET and request.GET["l"] in LEXER_KEYS: self.fields["lexer"].initial = request.GET["l"] - def clean_content(self): + def clean_content(self) -> str: content = self.cleaned_data.get("content", "") if not content.strip(): raise forms.ValidationError(_("This field is required.")) return content - def clean_expires(self): + def clean_expires(self) -> Optional[datetime]: """ Extract the 'expire_type' from the choice of expire choices. """ @@ -87,7 +89,7 @@ class SnippetForm(forms.ModelForm): self.cleaned_data["expire_type"] = expire_type return expires - def clean(self): + def clean(self) -> Dict[str, Optional[Union[Type[object], None]]]: """ The `title` field is a hidden honeypot field. If its filled, this is likely spam. @@ -96,7 +98,9 @@ class SnippetForm(forms.ModelForm): raise forms.ValidationError("This snippet was identified as Spam.") return self.cleaned_data - def save(self, parent=None, *args, **kwargs): + def save( + self, parent: Optional[Snippet] = None, *args, **kwargs + ) -> Snippet: # Set parent snippet self.instance.parent = parent diff --git a/dpaste/highlight.py b/dpaste/highlight.py index cd3ba7c..5d55604 100644 --- a/dpaste/highlight.py +++ b/dpaste/highlight.py @@ -1,9 +1,11 @@ +from io import StringIO from logging import getLogger +from typing import Any, Iterator, Optional, Type from django.apps import apps from django.template.defaultfilters import escape, linebreaksbr from django.template.loader import render_to_string -from django.utils.safestring import mark_safe +from django.utils.safestring import SafeString, mark_safe from django.utils.translation import gettext_lazy as _ from pygments import highlight from pygments.formatters.html import HtmlFormatter @@ -34,7 +36,13 @@ class Highlighter(object): return l[1] return fallback - def render(self, code_string, lexer_name, direction=None, **kwargs): + def render( + self, + code_string: str, + lexer_name: str, + direction: Optional[str] = None, + **kwargs + ) -> SafeString: highlighted_string = self.highlight(code_string, lexer_name=lexer_name) context = { "highlighted": highlighted_string, @@ -52,7 +60,7 @@ class PlainTextHighlighter(Highlighter): template_name = "dpaste/highlight/text.html" - def highlight(self, code_string, **kwargs): + def highlight(self, code_string: str, **kwargs) -> SafeString: return linebreaksbr(code_string) @@ -99,7 +107,7 @@ class RestructuredTextHighlighter(PlainTextHighlighter): }, } - def highlight(self, code_string, **kwargs): + def highlight(self, code_string: str, **kwargs) -> SafeString: from docutils.core import publish_parts self.publish_args["source"] = code_string @@ -113,10 +121,10 @@ class RestructuredTextHighlighter(PlainTextHighlighter): class NakedHtmlFormatter(HtmlFormatter): """Pygments HTML formatter with no further HTML tags.""" - def wrap(self, source, outfile): + def wrap(self, source: Iterator[Any], outfile: StringIO) -> Iterator[Any]: return self._wrap_code(source) - def _wrap_code(self, source): + def _wrap_code(self, source: Iterator[Any]) -> None: yield from source @@ -125,7 +133,7 @@ class PlainCodeHighlighter(Highlighter): Plain Code. No highlighting but Pygments like span tags around each line. """ - def highlight(self, code_string, **kwargs): + def highlight(self, code_string: str, **kwargs) -> str: return "\n".join( [ '{}'.format(escape(l) or "​") @@ -144,7 +152,7 @@ class PygmentsHighlighter(Highlighter): lexer = None lexer_fallback = PythonLexer() - def highlight(self, code_string, lexer_name): + def highlight(self, code_string: str, lexer_name: str) -> str: if not self.lexer: try: self.lexer = get_lexer_by_name(lexer_name) @@ -155,18 +163,7 @@ class PygmentsHighlighter(Highlighter): return highlight(code_string, self.lexer, self.formatter) -class SolidityHighlighter(PygmentsHighlighter): - """Solidity Specific Highlighter. This uses a 3rd party Pygments lexer.""" - - def __init__(self): - # SolidityLexer does not necessarily need to be installed - # since its imported here and not used later. - from pygments_lexer_solidity import SolidityLexer - - self.lexer = SolidityLexer() - - -def get_highlighter_class(lexer_name): +def get_highlighter_class(lexer_name: str) -> Type[Highlighter]: """ Get Highlighter for lexer name. diff --git a/dpaste/migrations/0001_initial.py b/dpaste/migrations/0001_initial.py index f4b9e20..8f7ec1d 100644 --- a/dpaste/migrations/0001_initial.py +++ b/dpaste/migrations/0001_initial.py @@ -5,31 +5,78 @@ from django.db import models, migrations class Migration(migrations.Migration): - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Snippet', + name="Snippet", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('secret_id', models.CharField(max_length=255, unique=True, null=True, verbose_name='Secret ID', blank=True)), - ('content', models.TextField(verbose_name='Content')), - ('lexer', models.CharField(default=b'python', max_length=30, verbose_name='Lexer')), - ('published', models.DateTimeField(auto_now_add=True, verbose_name='Published')), - ('expire_type', models.PositiveSmallIntegerField(default=1, verbose_name='Expire Type', choices=[(1, 'Expire by timestamp'), (2, 'Keep Forever'), (3, 'One-Time snippet')])), - ('expires', models.DateTimeField(null=True, verbose_name='Expires', blank=True)), - ('view_count', models.PositiveIntegerField(default=0, verbose_name='View count')), - ('lft', models.PositiveIntegerField(editable=False, db_index=True)), - ('rght', models.PositiveIntegerField(editable=False, db_index=True)), - ('tree_id', models.PositiveIntegerField(editable=False, db_index=True)), - ('level', models.PositiveIntegerField(editable=False, db_index=True)), - ('parent', models.ForeignKey(related_name='children', blank=True, to='dpaste.Snippet', null=True, on_delete=models.CASCADE)), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "secret_id", + models.CharField( + max_length=255, + unique=True, + null=True, + verbose_name="Secret ID", + blank=True, + ), + ), + ("content", models.TextField(verbose_name="Content")), + ( + "lexer", + models.CharField( + default=b"python", max_length=30, verbose_name="Lexer" + ), + ), + ( + "published", + models.DateTimeField(auto_now_add=True, verbose_name="Published"), + ), + ( + "expire_type", + models.PositiveSmallIntegerField( + default=1, + verbose_name="Expire Type", + choices=[ + (1, "Expire by timestamp"), + (2, "Keep Forever"), + (3, "One-Time snippet"), + ], + ), + ), + ( + "expires", + models.DateTimeField(null=True, verbose_name="Expires", blank=True), + ), + ( + "view_count", + models.PositiveIntegerField(default=0, verbose_name="View count"), + ), + ("lft", models.PositiveIntegerField(editable=False, db_index=True)), + ("rght", models.PositiveIntegerField(editable=False, db_index=True)), + ("tree_id", models.PositiveIntegerField(editable=False, db_index=True)), + ("level", models.PositiveIntegerField(editable=False, db_index=True)), + ( + "parent", + models.ForeignKey( + related_name="children", + blank=True, + to="dpaste.Snippet", + null=True, + on_delete=models.CASCADE, + ), + ), ], - options={ - 'ordering': ('-published',), - 'db_table': 'dpaste_snippet', - }, + options={"ordering": ("-published",), "db_table": "dpaste_snippet",}, bases=(models.Model,), ), ] diff --git a/dpaste/migrations/0002_auto_20170119_1038.py b/dpaste/migrations/0002_auto_20170119_1038.py index 89aa8d9..ed9e096 100644 --- a/dpaste/migrations/0002_auto_20170119_1038.py +++ b/dpaste/migrations/0002_auto_20170119_1038.py @@ -7,24 +7,12 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('dpaste', '0001_initial'), + ("dpaste", "0001_initial"), ] operations = [ - migrations.RemoveField( - model_name='snippet', - name='level', - ), - migrations.RemoveField( - model_name='snippet', - name='lft', - ), - migrations.RemoveField( - model_name='snippet', - name='rght', - ), - migrations.RemoveField( - model_name='snippet', - name='tree_id', - ), + migrations.RemoveField(model_name="snippet", name="level",), + migrations.RemoveField(model_name="snippet", name="lft",), + migrations.RemoveField(model_name="snippet", name="rght",), + migrations.RemoveField(model_name="snippet", name="tree_id",), ] diff --git a/dpaste/migrations/0003_snippet_highlighted.py b/dpaste/migrations/0003_snippet_highlighted.py index 65ffcbc..56ea08b 100644 --- a/dpaste/migrations/0003_snippet_highlighted.py +++ b/dpaste/migrations/0003_snippet_highlighted.py @@ -7,14 +7,14 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dpaste', '0002_auto_20170119_1038'), + ("dpaste", "0002_auto_20170119_1038"), ] operations = [ migrations.AddField( - model_name='snippet', - name='highlighted', - field=models.TextField(default='', verbose_name='Highlighted Content'), + model_name="snippet", + name="highlighted", + field=models.TextField(default="", verbose_name="Highlighted Content"), preserve_default=False, ), ] diff --git a/dpaste/migrations/0004_auto_20180107_1603.py b/dpaste/migrations/0004_auto_20180107_1603.py index 2a03bde..18bb5f4 100644 --- a/dpaste/migrations/0004_auto_20180107_1603.py +++ b/dpaste/migrations/0004_auto_20180107_1603.py @@ -7,13 +7,15 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dpaste', '0003_snippet_highlighted'), + ("dpaste", "0003_snippet_highlighted"), ] operations = [ migrations.AlterField( - model_name='snippet', - name='lexer', - field=models.CharField(default='python', max_length=30, verbose_name='Lexer'), + model_name="snippet", + name="lexer", + field=models.CharField( + default="python", max_length=30, verbose_name="Lexer" + ), ), ] diff --git a/dpaste/migrations/0005_remove_snippet_highlighted.py b/dpaste/migrations/0005_remove_snippet_highlighted.py index 901819f..584d1ff 100644 --- a/dpaste/migrations/0005_remove_snippet_highlighted.py +++ b/dpaste/migrations/0005_remove_snippet_highlighted.py @@ -6,12 +6,9 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('dpaste', '0004_auto_20180107_1603'), + ("dpaste", "0004_auto_20180107_1603"), ] operations = [ - migrations.RemoveField( - model_name='snippet', - name='highlighted', - ), + migrations.RemoveField(model_name="snippet", name="highlighted",), ] diff --git a/dpaste/migrations/0006_auto_20180622_1051.py b/dpaste/migrations/0006_auto_20180622_1051.py index 9e9207c..2b6a7f8 100644 --- a/dpaste/migrations/0006_auto_20180622_1051.py +++ b/dpaste/migrations/0006_auto_20180622_1051.py @@ -7,13 +7,20 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('dpaste', '0005_remove_snippet_highlighted'), + ("dpaste", "0005_remove_snippet_highlighted"), ] operations = [ migrations.AlterField( - model_name='snippet', - name='parent', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='dpaste.Snippet', verbose_name='Parent Snippet'), + model_name="snippet", + name="parent", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="children", + to="dpaste.Snippet", + verbose_name="Parent Snippet", + ), ), ] diff --git a/dpaste/migrations/0007_snippet_rtl.py b/dpaste/migrations/0007_snippet_rtl.py index f4e5299..9e6219a 100644 --- a/dpaste/migrations/0007_snippet_rtl.py +++ b/dpaste/migrations/0007_snippet_rtl.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('dpaste', '0006_auto_20180622_1051'), + ("dpaste", "0006_auto_20180622_1051"), ] operations = [ migrations.AddField( - model_name='snippet', - name='rtl', - field=models.BooleanField(default=False, verbose_name='Right-to-left'), + model_name="snippet", + name="rtl", + field=models.BooleanField(default=False, verbose_name="Right-to-left"), ), ] diff --git a/dpaste/models.py b/dpaste/models.py index d85568b..eaa186b 100644 --- a/dpaste/models.py +++ b/dpaste/models.py @@ -4,6 +4,7 @@ from random import SystemRandom from django.apps import apps from django.db import models from django.urls import reverse +from django.utils.safestring import SafeString from django.utils.translation import gettext_lazy as _ from dpaste import highlight @@ -13,7 +14,7 @@ logger = getLogger(__file__) R = SystemRandom() -def generate_secret_id(length): +def generate_secret_id(length: int) -> str: if length > config.SLUG_LENGTH: logger.warning( "Slug creation triggered a duplicate, " @@ -75,18 +76,18 @@ class Snippet(models.Model): ordering = ("-published",) db_table = "dpaste_snippet" - def __str__(self): + def __str__(self) -> str: return self.secret_id - def save(self, *args, **kwargs): + def save(self, *args, **kwargs) -> None: if not self.secret_id: self.secret_id = generate_secret_id(length=config.SLUG_LENGTH) super().save(*args, **kwargs) - def get_absolute_url(self): + def get_absolute_url(self) -> str: return reverse("snippet_details", kwargs={"snippet_id": self.secret_id}) - def highlight(self): + def highlight(self) -> SafeString: HighlighterClass = highlight.get_highlighter_class(self.lexer) return HighlighterClass().render( code_string=self.content, @@ -95,12 +96,12 @@ class Snippet(models.Model): ) @property - def lexer_name(self): + def lexer_name(self) -> str: """Display name for this lexer.""" return highlight.Highlighter.get_lexer_display_name(self.lexer) @property - def remaining_views(self): + def remaining_views(self) -> int: if self.expire_type == self.EXPIRE_ONETIME: remaining = config.ONETIME_LIMIT - self.view_count return remaining > 0 and remaining or 0 diff --git a/dpaste/views.py b/dpaste/views.py index c9af647..7a7c69e 100644 --- a/dpaste/views.py +++ b/dpaste/views.py @@ -1,8 +1,11 @@ import datetime import difflib import json +from typing import Any, Dict, Optional, Union from django.apps import apps +from django.core.handlers.wsgi import WSGIRequest +from django.db.models.query import QuerySet from django.http import ( Http404, HttpResponse, @@ -11,9 +14,11 @@ from django.http import ( HttpResponseRedirect, ) from django.shortcuts import get_object_or_404, render +from django.template.response import TemplateResponse from django.urls import reverse from django.utils import timezone from django.utils.cache import add_never_cache_headers, patch_cache_control +from django.utils.datastructures import MultiValueDict from django.utils.http import http_date from django.utils.translation import ugettext from django.views.generic import FormView @@ -23,6 +28,7 @@ from pygments.lexers import get_lexer_for_filename from pygments.util import ClassNotFound from dpaste import highlight +from dpaste.apps import dpasteAppConfig from dpaste.forms import SnippetForm, get_expire_values from dpaste.highlight import PygmentsHighlighter from dpaste.models import Snippet @@ -43,22 +49,26 @@ class SnippetView(FormView): form_class = SnippetForm template_name = "dpaste/new.html" - def get(self, request, *args, **kwargs): + def get(self, request: WSGIRequest, *args, **kwargs) -> TemplateResponse: response = super().get(request, *args, **kwargs) if config.CACHE_HEADER: patch_cache_control(response, max_age=config.CACHE_TIMEOUT) return response - def get_form_kwargs(self): + def get_form_kwargs( + self, + ) -> Dict[str, Optional[Union[MultiValueDict, WSGIRequest]]]: kwargs = super().get_form_kwargs() kwargs.update({"request": self.request}) return kwargs - def form_valid(self, form): + def form_valid(self, form: SnippetForm) -> HttpResponseRedirect: snippet = form.save() return HttpResponseRedirect(snippet.get_absolute_url()) - def get_context_data(self, **kwargs): + def get_context_data( + self, **kwargs + ) -> Dict[str, Union[SnippetForm, "SnippetView", str]]: ctx = super().get_context_data(**kwargs) ctx.update(config.extra_template_context) return ctx @@ -76,7 +86,9 @@ class SnippetDetailView(DetailView, FormView): slug_url_kwarg = "snippet_id" slug_field = "secret_id" - def post(self, request, *args, **kwargs): + def post( + self, request: WSGIRequest, *args, **kwargs + ) -> Union[HttpResponseRedirect, TemplateResponse]: """ Delete a snippet. This is allowed by anybody as long as he knows the snippet id. I got too many manual requests to do this, mostly for legal @@ -95,7 +107,7 @@ class SnippetDetailView(DetailView, FormView): return super().post(request, *args, **kwargs) - def get(self, request, *args, **kwargs): + def get(self, request: WSGIRequest, *args, **kwargs) -> HttpResponse: snippet = self.get_object() # One-Time snippet get deleted if the view count matches our limit @@ -126,7 +138,7 @@ class SnippetDetailView(DetailView, FormView): return response - def get_initial(self): + def get_initial(self) -> Dict[str, Union[str, bool]]: snippet = self.get_object() return { "content": snippet.content, @@ -134,16 +146,23 @@ class SnippetDetailView(DetailView, FormView): "rtl": snippet.rtl, } - def get_form_kwargs(self): + def get_form_kwargs( + self, + ) -> Dict[ + str, + Optional[ + Union[Dict[str, Union[str, bool]], MultiValueDict, WSGIRequest] + ], + ]: kwargs = super().get_form_kwargs() kwargs.update({"request": self.request}) return kwargs - def form_valid(self, form): + def form_valid(self, form: SnippetForm) -> HttpResponseRedirect: snippet = form.save(parent=self.get_object()) return HttpResponseRedirect(snippet.get_absolute_url()) - def get_snippet_diff(self): + def get_snippet_diff(self) -> None: snippet = self.get_object() if not snippet.parent_id: @@ -165,7 +184,7 @@ class SnippetDetailView(DetailView, FormView): # Remove blank lines return highlighted - def get_context_data(self, **kwargs): + def get_context_data(self, **kwargs) -> Dict[str, Any]: self.object = self.get_object() ctx = super().get_context_data(**kwargs) @@ -187,26 +206,30 @@ class SnippetRawView(SnippetDetailView): template_name = "dpaste/raw.html" - def dispatch(self, request, *args, **kwargs): + def dispatch(self, request: WSGIRequest, *args, **kwargs) -> HttpResponse: if not config.RAW_MODE_ENABLED: return HttpResponseForbidden( ugettext("This dpaste installation has Raw view mode disabled.") ) return super().dispatch(request, *args, **kwargs) - def render_plain_text(self, context, **response_kwargs): + def render_plain_text( + self, context: dpasteAppConfig, **response_kwargs + ) -> HttpResponse: snippet = self.get_object() response = HttpResponse(snippet.content) response["Content-Type"] = "text/plain;charset=UTF-8" response["X-Content-Type-Options"] = "nosniff" return response - def render_to_response(self, context, **response_kwargs): + def render_to_response( + self, context: Dict[str, Any], **response_kwargs + ) -> HttpResponse: if config.RAW_MODE_PLAIN_TEXT: return self.render_plain_text(config, **response_kwargs) return super().render_to_response(context, **response_kwargs) - def get_context_data(self, **kwargs): + def get_context_data(self, **kwargs) -> Dict[str, Any]: ctx = super().get_context_data(**kwargs) ctx.update(config.extra_template_context) return ctx @@ -220,16 +243,18 @@ class SnippetHistory(TemplateView): template_name = "dpaste/history.html" - def get_user_snippets(self): + def get_user_snippets(self) -> QuerySet: snippet_id_list = self.request.session.get("snippet_list", []) return Snippet.objects.filter(pk__in=snippet_id_list) - def get(self, request, *args, **kwargs): + def get(self, request: WSGIRequest, *args, **kwargs) -> TemplateResponse: response = super().get(request, *args, **kwargs) add_never_cache_headers(response) return response - def post(self, request, *args, **kwargs): + def post( + self, request: WSGIRequest, *args, **kwargs + ) -> HttpResponseRedirect: """ Delete all user snippets at once. """ @@ -240,7 +265,9 @@ class SnippetHistory(TemplateView): url = "{0}#".format(reverse("snippet_history")) return HttpResponseRedirect(url) - def get_context_data(self, **kwargs): + def get_context_data( + self, **kwargs + ) -> Dict[str, Union["SnippetHistory", QuerySet, str]]: ctx = super().get_context_data(**kwargs) ctx.update({"snippet_list": self.get_user_snippets()}) ctx.update(config.extra_template_context) @@ -257,14 +284,14 @@ class APIView(View): API View """ - def _format_default(self, s): + def _format_default(self, s: Snippet) -> str: """ The default response is the snippet URL wrapped in quotes. """ base_url = config.get_base_url(request=self.request) return f'"{base_url}{s.get_absolute_url()}"' - def _format_url(self, s): + def _format_url(self, s: Snippet) -> str: """ The `url` format returns the snippet URL, no quotes, but a linebreak at the end. @@ -272,7 +299,7 @@ class APIView(View): base_url = config.get_base_url(request=self.request) return f"{base_url}{s.get_absolute_url()}\n" - def _format_json(self, s): + def _format_json(self, s: Snippet) -> str: """ The `json` format export. """ @@ -285,7 +312,7 @@ class APIView(View): } ) - def post(self, request, *args, **kwargs): + def post(self, request: WSGIRequest, *args, **kwargs) -> HttpResponse: content = request.POST.get("content", "") lexer = request.POST.get("lexer", highlight.LEXER_DEFAULT).strip() filename = request.POST.get("filename", "").strip() @@ -357,7 +384,11 @@ class APIView(View): # ----------------------------------------------------------------------------- -def page_not_found(request, exception=None, template_name="dpaste/404.html"): +def page_not_found( + request: WSGIRequest, + exception: Optional[Http404] = None, + template_name: str = "dpaste/404.html", +) -> HttpResponse: context = {} context.update(config.extra_template_context) response = render(request, template_name, context, status=404)