Skip to content

Commit

Permalink
squash: everything checks, except test, utils & reference
Browse files Browse the repository at this point in the history
  • Loading branch information
kdmccormick committed Jan 31, 2024
1 parent 24882f7 commit 399af08
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 125 deletions.
2 changes: 1 addition & 1 deletion xblock/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ def __init__(self, *args, **kwargs):

# For backwards compatibility, provide the XBlockMixin in xblock.fields
# without causing a circular import
xblock.fields.XBlockMixin = XBlockMixin
xblock.fields.XBlockMixin = XBlockMixin # type: ignore[attr-defined]

__version__ = '1.10.0'
10 changes: 7 additions & 3 deletions xblock/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
import warnings
import typing as t
from collections import defaultdict
from xml.etree import ElementTree as ET

import pkg_resources
from lxml import etree
from opaque_keys.edx.keys import LearningContextKey, UsageKey
from web_fragments.fragment import Fragment

Expand Down Expand Up @@ -146,7 +146,9 @@ class XBlock(XmlSerializationMixin, HierarchyMixin, ScopedStorageMixin, RuntimeS
entry_point: str = 'xblock.v1'

name = String(help="Short name for the block", scope=Scope.settings)
tags = List(help="Tags for this block", scope=Scope.settings)
tags: List[str] = List(help="Tags for this block", scope=Scope.settings)

has_children: bool

@class_lazy
def _class_tags(cls: type[XBlock]) -> set[str]: # type: ignore[misc]
Expand Down Expand Up @@ -219,6 +221,8 @@ def __init__(
"""
if scope_ids is UNSET:
raise TypeError('scope_ids are required')
else:
assert isinstance(scope_ids, ScopeIds)

# Provide backwards compatibility for external access through _field_data
super().__init__(runtime=runtime, scope_ids=scope_ids, field_data=field_data, *args, **kwargs)
Expand Down Expand Up @@ -271,7 +275,7 @@ def ugettext(self, text) -> str:
runtime_ugettext = runtime_service.ugettext
return runtime_ugettext(text)

def add_xml_to_node(self, node: ET.Element) -> None:
def add_xml_to_node(self, node: etree._Element) -> None:
"""
For exporting, set data on etree.Element `node`.
"""
Expand Down
72 changes: 41 additions & 31 deletions xblock/django/request.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
"""Helpers for WebOb requests and responses."""
from __future__ import annotations

from collections.abc import MutableMapping
from itertools import chain, repeat
from lazy import lazy
import typing as t

import webob
from django.http import HttpResponse
from webob.multidict import MultiDict, NestedMultiDict, NoVars
import webob.multidict
import django.core.files.uploadedfile
import django.http
import django.utils.datastructures


def webob_to_django_response(webob_response):
def webob_to_django_response(webob_response: webob.Response) -> django.http.HttpResponse:
"""Returns a django response to the `webob_response`"""
django_response = HttpResponse(
django_response = django.http.HttpResponse(
webob_response.app_iter,
content_type=webob_response.content_type,
status=webob_response.status_code,
Expand All @@ -28,11 +33,11 @@ class HeaderDict(MutableMapping):
"""
UNPREFIXED_HEADERS = ('CONTENT_TYPE', 'CONTENT_LENGTH')

def __init__(self, meta):
def __init__(self, meta: dict[str, str]):
super().__init__()
self._meta = meta

def _meta_name(self, name):
def _meta_name(self, name: str) -> str:
"""
Translate HTTP header names to the format used by Django request objects.
Expand All @@ -43,41 +48,43 @@ def _meta_name(self, name):
name = 'HTTP_' + name
return name

def _un_meta_name(self, name):
def _un_meta_name(self, name: str) -> str:
"""
Reverse of _meta_name
"""
if name.startswith('HTTP_'):
name = name[5:]
return name.replace('_', '-').title()

def __getitem__(self, name):
def __getitem__(self, name: str):
return self._meta[self._meta_name(name)]

def __setitem__(self, name, value):
def __setitem__(self, name: str, value: str):
self._meta[self._meta_name(name)] = value

def __delitem__(self, name):
def __delitem__(self, name: str):
del self._meta[self._meta_name(name)]

def __iter__(self):
def __iter__(self) -> t.Iterator[str]:
for key in self._meta:
if key in self.UNPREFIXED_HEADERS or key.startswith('HTTP_'):
yield self._un_meta_name(key)

def __len__(self):
def __len__(self) -> int:
return len(list(self))


def querydict_to_multidict(query_dict, wrap=None):
def querydict_to_multidict(
query_dict: django.utils.datastructures.MultiValueDict,
wrap: t.Callable[[t.Any], t.Any] | None = None
) -> webob.multidict.MultiDict:
"""
Returns a new `webob.MultiDict` from a `django.http.QueryDict`.
If `wrap` is provided, it's used to wrap the values.
"""
wrap = wrap or (lambda val: val)
return MultiDict(chain.from_iterable(
return webob.multidict.MultiDict(chain.from_iterable(
zip(repeat(key), (wrap(v) for v in vals))
for key, vals in query_dict.lists()
))
Expand All @@ -87,32 +94,35 @@ class DjangoUploadedFile:
"""
Looks like a cgi.FieldStorage, but wraps a Django UploadedFile.
"""
def __init__(self, uploaded):
def __init__(self, uploaded: django.core.files.uploadedfile.UploadedFile):
# FieldStorage needs a file attribute.
self.file = uploaded
self.file: t.Any = uploaded

@property
def name(self):
def name(self) -> str:
"""The name of the input element used to upload the file."""
return self.file.field_name

@property
def filename(self):
def filename(self) -> str:
"""The name of the uploaded file."""
return self.file.name


class DjangoWebobRequest(webob.Request):
"""
An implementation of the webob request api, backed
by a django request
An implementation of the webob request api, backed by a django request
"""
def __init__(self, request):
# Note:
# This implementation is close enough to webob.Request for it to work OK, but it does
# make mypy complain that the type signatures are different, hence the 'type: ignore' pragmas.

def __init__(self, request: django.http.HttpRequest):
self._request = request
super().__init__(self.environ)

@lazy
def environ(self):
def environ(self) -> dict[str, str]: # type: ignore[override]
"""
Add path_info to the request's META dictionary.
"""
Expand All @@ -123,40 +133,40 @@ def environ(self):
return environ

@property
def GET(self):
def GET(self) -> webob.multidict.MultiDict: # type: ignore[override]
"""
Returns a new `webob.MultiDict` from the request's GET query.
"""
return querydict_to_multidict(self._request.GET)

@property
def POST(self):
def POST(self) -> webob.multidict.MultiDict | webob.multidict.NoVars: # type: ignore[override]
if self.method not in ('POST', 'PUT', 'PATCH'):
return NoVars('Not a form request')
return webob.multidict.NoVars('Not a form request')

# Webob puts uploaded files into the POST dictionary, so here we
# combine the Django POST data and uploaded FILES data into a single
# dict.
return NestedMultiDict(
return webob.multidict.NestedMultiDict(
querydict_to_multidict(self._request.POST),
querydict_to_multidict(self._request.FILES, wrap=DjangoUploadedFile),
)

@property
def body(self):
def body(self) -> bytes: # type: ignore[override]
"""
Return the content of the request body.
"""
return self._request.body

@property
def body_file(self):
@property # type: ignore[misc]
def body_file(self) -> django.http.HttpRequest: # type: ignore[override]
"""
Input stream of the request
"""
return self._request


def django_to_webob_request(django_request):
def django_to_webob_request(django_request: django.http.HttpRequest) -> webob.Request:
"""Returns a WebOb request to the `django_request`"""
return DjangoWebobRequest(django_request)
Loading

0 comments on commit 399af08

Please sign in to comment.