Added python3.5-style type annotations to pretix.base

This commit is contained in:
Raphael Michel
2015-10-25 21:26:04 +01:00
parent ecebe481d7
commit 79ad8b40ed
28 changed files with 142 additions and 113 deletions

View File

@@ -3,11 +3,12 @@ import time
from django.core.cache import caches
from django.db.models import Model
from typing import Dict, List
class NamespacedCache:
def __init__(self, prefixkey, cache: str='default'):
def __init__(self, prefixkey: str, cache: str='default'):
self.cache = caches[cache]
self.prefixkey = prefixkey
@@ -30,7 +31,7 @@ class NamespacedCache:
def _strip_prefix(self, key: str) -> str:
return key.split(":", 2 + self.prefixkey.count(":"))[-1]
def clear(self):
def clear(self) -> None:
try:
prefix = self.cache.incr(self.prefixkey, 1)
except ValueError:
@@ -43,14 +44,14 @@ class NamespacedCache:
def get(self, key: str) -> str:
return self.cache.get(self._prefix_key(key))
def get_many(self, keys: "list[str]") -> "dict[str, str]":
def get_many(self, keys: List[str]) -> Dict[str, str]:
values = self.cache.get_many([self._prefix_key(key) for key in keys])
newvalues = {}
for k, v in values.items():
newvalues[self._strip_prefix(k)] = v
return newvalues
def set_many(self, values: "dict[str, str]", timeout=3600):
def set_many(self, values: Dict[str, str], timeout=3600):
newvalues = {}
for k, v in values.items():
newvalues[self._prefix_key(k)] = v
@@ -59,7 +60,7 @@ class NamespacedCache:
def delete(self, key: str): # NOQA
return self.cache.delete(self._prefix_key(key))
def delete_many(self, keys: "list[str]"): # NOQA
def delete_many(self, keys: List[str]): # NOQA
return self.cache.delete_many([self._prefix_key(key) for key in keys])
def incr(self, key: str, by: int=1): # NOQA
@@ -86,6 +87,6 @@ class ObjectRelatedCache(NamespacedCache):
times as you want.
"""
def __init__(self, obj, cache: str='default'):
def __init__(self, obj: Model, cache: str='default'):
assert isinstance(obj, Model)
super().__init__('%s:%s' % (obj._meta.object_name, obj.pk), cache)

View File

@@ -2,6 +2,7 @@ import json
from django.core.serializers.json import DjangoJSONEncoder
from django.dispatch import receiver
from typing import Tuple
from pretix.base.signals import register_data_exporters
@@ -60,7 +61,7 @@ class BaseExporter:
"""
return {}
def render(self, form_data: dict) -> tuple:
def render(self, form_data: dict) -> Tuple[str, str, str]:
"""
Render the exported file and return a tuple consisting of a filename, a file type
and file content.

View File

@@ -43,7 +43,7 @@ class LoginForm(forms.Form):
return self.cleaned_data
def confirm_login_allowed(self, user):
def confirm_login_allowed(self, user: User):
"""
Controls whether the given User may log in. This is a policy setting,
independent of end-user authentication. This default behavior is to

View File

@@ -6,6 +6,7 @@ from django.conf import settings
from django.db.models import SubfieldBase, TextField
from django.utils import translation
from django.utils.safestring import mark_safe
from typing import Dict, List
class LazyI18nString:
@@ -13,7 +14,7 @@ class LazyI18nString:
This represents an internationalized string that is/was/will be stored in the database.
"""
def __init__(self, data):
def __init__(self, data: Dict[str, str]):
"""
Input data should be a dictionary which maps language codes to content.
"""
@@ -26,7 +27,7 @@ class LazyI18nString:
else:
self.data = j
def __str__(self):
def __str__(self) -> str:
"""
Evaluate the given string with respect to the currently active locale.
This will rather return you a string in a wrong language than give you an
@@ -53,10 +54,10 @@ class LazyI18nString:
else:
return str(self.data)
def __repr__(self):
def __repr__(self) -> str:
return '<LazyI18nString: %s>' % repr(self.data)
def __lt__(self, other): # NOQA
def __lt__(self, other) -> bool: # NOQA
return str(self) < str(other)
@@ -68,7 +69,7 @@ class I18nWidget(forms.MultiWidget):
"""
widget = forms.TextInput
def __init__(self, langcodes, field, attrs=None):
def __init__(self, langcodes: List[str], field: forms.Field, attrs=None):
widgets = []
self.langcodes = langcodes
self.enabled_langcodes = langcodes

View File

@@ -3,6 +3,7 @@ from collections import OrderedDict
import pytz
from django.conf import settings
from django.core.urlresolvers import get_script_prefix
from django.http import HttpRequest, HttpResponse
from django.utils import timezone, translation
from django.utils.cache import patch_vary_headers
from django.utils.translation import LANGUAGE_SESSION_KEY
@@ -21,7 +22,7 @@ class LocaleMiddleware:
for a request.
"""
def process_request(self, request):
def process_request(self, request: HttpRequest):
language = get_language_from_request(request)
if hasattr(request, 'event') and not request.path.startswith(get_script_prefix() + 'control'):
if language not in request.event.settings.locales:
@@ -51,7 +52,7 @@ class LocaleMiddleware:
else:
timezone.deactivate()
def process_response(self, request, response):
def process_response(self, request: HttpRequest, response: HttpResponse):
language = translation.get_language()
patch_vary_headers(response, ('Accept-Language',))
if 'Content-Language' not in response:
@@ -59,14 +60,14 @@ class LocaleMiddleware:
return response
def get_language_from_user_settings(request) -> str:
def get_language_from_user_settings(request: HttpRequest) -> str:
if request.user.is_authenticated():
lang_code = request.user.locale
if lang_code in _supported and lang_code is not None and check_for_language(lang_code):
return lang_code
def get_language_from_session_or_cookie(request) -> str:
def get_language_from_session_or_cookie(request: HttpRequest) -> str:
if hasattr(request, 'session'):
lang_code = request.session.get(LANGUAGE_SESSION_KEY)
if lang_code in _supported and lang_code is not None and check_for_language(lang_code):
@@ -79,7 +80,7 @@ def get_language_from_session_or_cookie(request) -> str:
pass
def get_language_from_event(request) -> str:
def get_language_from_event(request: HttpRequest) -> str:
if hasattr(request, 'event'):
lang_code = request.event.settings.locale
try:
@@ -88,7 +89,7 @@ def get_language_from_event(request) -> str:
pass
def get_language_from_browser(request) -> str:
def get_language_from_browser(request: HttpRequest) -> str:
accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
for accept_lang, unused in parse_accept_lang_header(accept):
if accept_lang == '*':
@@ -110,7 +111,7 @@ def get_default_language():
return settings.LANGUAGE_CODE
def get_language_from_request(request) -> str:
def get_language_from_request(request: HttpRequest) -> str:
"""
Analyzes the request to find what language the user wants the system to
show. Only languages listed in settings.LANGUAGES are taken into account.

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.db import migrations, models
class Migration(migrations.Migration):

View File

@@ -12,13 +12,13 @@ class UserManager(BaseUserManager):
model documentation to see what's so special about our user model.
"""
def create_user(self, email, password=None, **kwargs):
def create_user(self, email: str, password: str=None, **kwargs):
user = self.model(email=email, **kwargs)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password=None): # NOQA
def create_superuser(self, email: str, password: str=None): # NOQA
# Not used in the software but required by Django
if password is None:
raise Exception("You must provide a password")

View File

@@ -1,5 +1,6 @@
import copy
import uuid
from datetime import datetime
import six
from django.db import models
@@ -12,7 +13,7 @@ class Versionable(BaseVersionable):
class Meta:
abstract = True
def clone_shallow(self, forced_version_date=None):
def clone_shallow(self, forced_version_date: datetime=None):
"""
This behaves like clone(), but misses all the Many2Many-relation-handling. This is
a performance optimization for cases in which we have to handle the Many2Many relations
@@ -63,7 +64,7 @@ class Versionable(BaseVersionable):
})
def cachedfile_name(instance, filename):
def cachedfile_name(instance, filename: str) -> str:
return 'cachedfiles/%s.%s' % (instance.id, filename.split('.')[-1])

View File

@@ -1,4 +1,6 @@
import sys
from datetime import datetime
from decimal import Decimal
from itertools import product
from django.db import models
@@ -6,6 +8,7 @@ from django.db.models import Q, Case, Count, Sum, When
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from typing import List, Tuple, Union
from versions.models import VersionedForeignKey, VersionedManyToManyField
from pretix.base.i18n import I18nCharField, I18nTextField
@@ -61,11 +64,11 @@ class ItemCategory(Versionable):
def sortkey(self):
return self.position, self.version_birth_date
def __lt__(self, other):
def __lt__(self, other) -> bool:
return self.sortkey < other.sortkey
def itempicture_upload_to(instance, filename):
def itempicture_upload_to(instance, filename: str) -> str:
return '%s/%s/item-%s.%s' % (
instance.event.organizer.slug, instance.event.slug, instance.identity,
filename.split('.')[-1]
@@ -170,7 +173,7 @@ class Item(Versionable):
if self.event:
self.event.get_cache().clear()
def get_all_variations(self, use_cache: bool=False) -> "list[VariationDict]":
def get_all_variations(self, use_cache: bool=False) -> List[VariationDict]:
"""
This method returns a list containing all variations of this
item. The list contains one VariationDict per variation, where
@@ -438,10 +441,10 @@ class PropertyValue(Versionable):
self.prop.event.get_cache().clear()
@property
def sortkey(self):
def sortkey(self) -> Tuple[int, datetime]:
return self.position, self.version_birth_date
def __lt__(self, other):
def __lt__(self, other) -> bool:
return self.sortkey < other.sortkey
@@ -508,7 +511,7 @@ class ItemVariation(Versionable):
if self.item:
self.item.event.get_cache().clear()
def check_quotas(self):
def check_quotas(self) -> Tuple[int, int]:
"""
This method is used to determine whether this ItemVariation is currently
available for sale in terms of quotas.
@@ -518,7 +521,7 @@ class ItemVariation(Versionable):
return min([q.availability() for q in self.quotas.all()],
key=lambda s: (s[0], s[1] if s[1] is not None else sys.maxsize))
def to_variation_dict(self):
def to_variation_dict(self) -> VariationDict:
"""
:return: a :py:class:`VariationDict` representing this variation.
"""
@@ -528,7 +531,7 @@ class ItemVariation(Versionable):
vd['variation'] = self
return vd
def check_restrictions(self):
def check_restrictions(self) -> Union[bool, Decimal]:
"""
This method is used to determine whether this ItemVariation is restricted
in sale by any restriction plugins.
@@ -806,7 +809,7 @@ class Quota(Versionable):
if self.event:
self.event.get_cache().clear()
def availability(self):
def availability(self) -> Tuple[int, int]:
"""
This method is used to determine whether Items or ItemVariations belonging
to this quota should currently be available for sale.
@@ -864,7 +867,7 @@ class Quota(Versionable):
return o
@cached_property
def _position_lookup(self):
def _position_lookup(self) -> Q:
return (
( # Orders for items which do not have any variations
Q(variation__isnull=True)

View File

@@ -5,6 +5,7 @@ from datetime import datetime
from django.db import models
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from typing import List, Union
from versions.models import VersionedForeignKey
from .base import CachedFile, Versionable
@@ -133,7 +134,7 @@ class Order(Versionable):
verbose_name_plural = _("Orders")
ordering = ("-datetime",)
def str(self):
def __str__(self):
return self.full_code
@property
@@ -160,7 +161,7 @@ class Order(Versionable):
return
@property
def can_modify_answers(self):
def can_modify_answers(self) -> bool:
"""
Is ``True`` if the user can change the question answers / attendee names that are
related to the order. This checks order status and modification deadlines. It also
@@ -187,7 +188,7 @@ class Order(Versionable):
order.save()
return order
def _can_be_paid(self):
def _can_be_paid(self) -> Union[bool, str]:
error_messages = {
'late': _("The payment is too late to be accepted."),
}
@@ -202,7 +203,7 @@ class Order(Versionable):
return self._is_still_available()
def _is_still_available(self):
def _is_still_available(self) -> Union[bool, str]:
error_messages = {
'unavailable': _('Some of the ordered products were no longer available.'),
}
@@ -339,7 +340,7 @@ class OrderPosition(ObjectWithAnswers, Versionable):
verbose_name_plural = _("Order positions")
@classmethod
def transform_cart_positions(cls, cp: list, order) -> list:
def transform_cart_positions(cls, cp: List, order) -> list:
ops = []
for cartpos in cp:
op = OrderPosition(

View File

@@ -45,7 +45,7 @@ class Organizer(Versionable):
verbose_name_plural = _("Organizers")
ordering = ("name",)
def __str__(self):
def __str__(self) -> str:
return self.name
def save(self, *args, **kwargs):
@@ -97,7 +97,7 @@ class OrganizerPermission(Versionable):
verbose_name = _("Organizer permission")
verbose_name_plural = _("Organizer permissions")
def __str__(self):
def __str__(self) -> str:
return _("%(name)s on %(object)s") % {
'name': str(self.user),
'object': str(self.organizer),

View File

@@ -9,8 +9,9 @@ from django.forms import Form
from django.http import HttpRequest
from django.template.loader import get_template
from django.utils.translation import ugettext_lazy as _
from typing import Any, Dict
from pretix.base.models import CartPosition, Order, Quota
from pretix.base.models import CartPosition, Event, Order, Quota
from pretix.base.settings import SettingsSandbox
from pretix.base.signals import register_payment_providers
@@ -20,7 +21,7 @@ class BasePaymentProvider:
This is the base class for all payment providers.
"""
def __init__(self, event):
def __init__(self, event: Event):
self.event = event
self.settings = SettingsSandbox('payment', self.identifier, event)
@@ -193,7 +194,7 @@ class BasePaymentProvider:
"""
raise NotImplementedError() # NOQA
def checkout_prepare(self, request: HttpRequest, cart: dict) -> "bool|str":
def checkout_prepare(self, request: HttpRequest, cart: Dict[str, Any]) -> "bool|str":
"""
Will be called after the user selected this provider as his payment method.
If you provided a form to the user to enter payment data, this method should
@@ -395,7 +396,7 @@ class FreeOrderProvider(BasePaymentProvider):
def identifier(self) -> str:
return "free"
def checkout_confirm_render(self, request) -> str:
def checkout_confirm_render(self, request: HttpRequest) -> str:
return _("No payment is required as this order only includes products which are free of charge.")
def order_pending_render(self, request: HttpRequest, order: Order) -> str:

View File

@@ -1,6 +1,7 @@
from enum import Enum
from django.apps import apps
from typing import List
class PluginType(Enum):
@@ -10,7 +11,7 @@ class PluginType(Enum):
EXPORT = 4
def get_all_plugins() -> "List[class]":
def get_all_plugins() -> List[type]:
"""
Returns the PretixPluginMeta classes of all plugins found in the installed Django apps.
"""

View File

@@ -1,9 +1,10 @@
from datetime import timedelta
from datetime import datetime, timedelta
from django.conf import settings
from django.db.models import Q
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from typing import List, Optional, Tuple
from pretix.base.models import (
CartPosition, Event, EventLock, Item, ItemVariation, Quota,
@@ -29,7 +30,7 @@ error_messages = {
}
def _extend_existing(event, cart_id, expiry):
def _extend_existing(event: Event, cart_id: str, expiry: datetime) -> None:
# Extend this user's cart session to 30 minutes from now to ensure all items in the
# cart expire at the same time
# We can extend the reservation of items which are not yet expired without risk
@@ -38,7 +39,7 @@ def _extend_existing(event, cart_id, expiry):
).update(expires=expiry)
def _re_add_expired_positions(items, event, cart_id):
def _re_add_expired_positions(items: List[CartPosition], event: Event, cart_id: str) -> List[CartPosition]:
positions = set()
# For items that are already expired, we have to delete and re-add them, as they might
# be no longer available or prices might have changed. Sorry!
@@ -51,20 +52,21 @@ def _re_add_expired_positions(items, event, cart_id):
return positions
def _delete_expired(expired):
def _delete_expired(expired: List[CartPosition]) -> None:
for cp in expired:
if cp.version_end_date is None:
cp.delete()
def _check_date(event):
def _check_date(event: Event) -> None:
if event.presale_start and now() < event.presale_start:
raise CartError(error_messages['not_started'])
if event.presale_end and now() > event.presale_end:
raise CartError(error_messages['ended'])
def _add_items(event, items, cart_id, expiry):
def _add_items(event: Event, items: List[Tuple[str, Optional[str], int]],
cart_id: str, expiry: datetime) -> Optional[str]:
err = None
# Fetch items from the database
@@ -129,7 +131,7 @@ def _add_items(event, items, cart_id, expiry):
return err
def _add_items_to_cart(event: Event, items: list, cart_id: str=None):
def _add_items_to_cart(event: Event, items: List[Tuple[str, Optional[str], int]], cart_id: str=None) -> None:
with event.lock():
_check_date(event)
existing = CartPosition.objects.current.filter(Q(cart_id=cart_id) & Q(event=event)).count()
@@ -150,7 +152,7 @@ def _add_items_to_cart(event: Event, items: list, cart_id: str=None):
raise CartError(err)
def add_items_to_cart(event: str, items: list, cart_id: str=None):
def add_items_to_cart(event: str, items: List[Tuple[str, Optional[str], int]], cart_id: str=None) -> None:
"""
Adds a list of items to a user's cart.
:param event: The event ID in question
@@ -160,12 +162,12 @@ def add_items_to_cart(event: str, items: list, cart_id: str=None):
"""
event = Event.objects.current.get(identity=event)
try:
return _add_items_to_cart(event, items, cart_id)
_add_items_to_cart(event, items, cart_id)
except EventLock.LockTimeoutException:
raise CartError(error_messages['busy'])
def remove_items_from_cart(event: str, items: list, cart_id: str=None):
def remove_items_from_cart(event: str, items: List[Tuple[str, Optional[str], int]], cart_id: str=None) -> None:
"""
Removes a list of items from a user's cart.
:param event: The event ID in question
@@ -188,10 +190,10 @@ if settings.HAS_CELERY:
from pretix.celery import app
@app.task(bind=True, max_retries=5, default_retry_delay=2)
def add_items_to_cart_task(self, event: str, items: list, cart_id: str):
def add_items_to_cart_task(self, event: str, items: List[Tuple[str, Optional[str], int]], cart_id: str):
event = Event.objects.current.get(identity=event)
try:
return _add_items_to_cart(event, items, cart_id)
_add_items_to_cart(event, items, cart_id)
except EventLock.LockTimeoutException:
self.retry(exc=CartError(error_messages['busy']))

View File

@@ -1,11 +1,12 @@
from django.conf import settings
from django.core.files.base import ContentFile
from typing import Any, Dict
from pretix.base.models import CachedFile, Event, cachedfile_name
from pretix.base.signals import register_data_exporters
def export(event, fileid, provider, form_data):
def export(event: str, fileid: str, provider: str, form_data: Dict[str, Any]) -> None:
event = Event.objects.current.get(identity=event)
file = CachedFile.objects.get(id=fileid)
responses = register_data_exporters.send(event)

View File

@@ -5,6 +5,7 @@ from django.core.mail import EmailMessage
from django.template.loader import get_template
from django.utils import translation
from django.utils.translation import ugettext as _
from typing import Any, Dict
from pretix.base.i18n import LazyI18nString
from pretix.base.models import Event
@@ -12,7 +13,8 @@ from pretix.base.models import Event
logger = logging.getLogger('pretix.base.mail')
def mail(email: str, subject: str, template: str, context: dict=None, event: Event=None, locale: str=None):
def mail(email: str, subject: str, template: str,
context: Dict[str, Any]=None, event: Event=None, locale: str=None):
"""
Sends out an email to a user.
@@ -58,7 +60,7 @@ def mail(email: str, subject: str, template: str, context: dict=None, event: Eve
translation.activate(_lng)
def mail_send(to, subject, body, sender):
def mail_send(to: str, subject: str, body: str, sender: str) -> bool:
email = EmailMessage(subject, body, sender, to=to)
try:

View File

@@ -4,6 +4,7 @@ from django.conf import settings
from django.db import transaction
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from typing import List
from pretix.base.models import (
CartPosition, Event, EventLock, Order, OrderPosition, Quota,
@@ -30,7 +31,7 @@ error_messages = {
def mark_order_paid(order: Order, provider: str=None, info: str=None, date: datetime=None, manual: bool=None,
force: bool=False):
force: bool=False) -> Order:
"""
Marks an order as paid. This clones the order object, sets the payment provider,
info and date and returns the cloned order object.
@@ -82,14 +83,14 @@ class OrderError(Exception):
pass
def _check_date(event):
def _check_date(event: Event):
if event.presale_start and now() < event.presale_start:
raise OrderError(error_messages['not_started'])
if event.presale_end and now() > event.presale_end:
raise OrderError(error_messages['ended'])
def _check_positions(event: Event, dt: datetime, positions: list):
def _check_positions(event: Event, dt: datetime, positions: List[CartPosition]):
err = None
_check_date(event)
@@ -135,7 +136,7 @@ def _check_positions(event: Event, dt: datetime, positions: list):
@transaction.atomic()
def _create_order(event: Event, email: str, positions: list, dt: datetime,
def _create_order(event: Event, email: str, positions: List[CartPosition], dt: datetime,
payment_provider: BasePaymentProvider, locale: str=None):
total = sum([c.price for c in positions])
payment_fee = payment_provider.calculate_fee(total)
@@ -159,7 +160,7 @@ def _create_order(event: Event, email: str, positions: list, dt: datetime,
return order
def _perform_order(event: str, payment_provider: str, position_ids: list,
def _perform_order(event: str, payment_provider: str, position_ids: List[str],
email: str, locale: str):
event = Event.objects.current.get(identity=event)
responses = register_payment_providers.send(event)
@@ -197,7 +198,7 @@ def _perform_order(event: str, payment_provider: str, position_ids: list,
return order.identity
def perform_order(event: str, payment_provider: str, positions: list,
def perform_order(event: str, payment_provider: str, positions: List[str],
email: str=None, locale: str=None):
try:
return _perform_order(event, payment_provider, positions, email, locale)
@@ -211,7 +212,7 @@ if settings.HAS_CELERY:
from pretix.celery import app
@app.task(bind=True, max_retries=5, default_retry_delay=2)
def perform_order_task(self, event: str, payment_provider: str, positions: list,
def perform_order_task(self, event: str, payment_provider: str, positions: List[str],
email: str=None, locale: str=None):
try:
return _perform_order(event, payment_provider, positions, email, locale)

View File

@@ -1,7 +1,10 @@
from decimal import Decimal
from django.db.models import Count, Sum
from django.utils.translation import ugettext_lazy as _
from typing import Any, Dict, Iterable, List, Tuple
from pretix.base.models import ItemCategory, Order, OrderPosition
from pretix.base.models import Event, Item, ItemCategory, Order, OrderPosition
from pretix.base.signals import register_payment_providers
@@ -10,14 +13,14 @@ class DummyObject:
class Dontsum:
def __init__(self, value):
def __init__(self, value: Any):
self.value = value
def __str__(self):
def __str__(self) -> str:
return str(self.value)
def tuplesum(tuples):
def tuplesum(tuples: Iterable[Tuple]) -> Tuple:
def mysum(it):
sit = [i for i in it if not isinstance(i, Dontsum)]
return sum(sit)
@@ -25,7 +28,7 @@ def tuplesum(tuples):
return tuple(map(mysum, zip(*list(tuples))))
def order_overview(event):
def order_overview(event: Event) -> Tuple[List[Tuple[ItemCategory, List[Item]]], Dict[str, Tuple[Decimal, Decimal]]]:
items = event.items.all().select_related(
'category', # for re-grouping
).prefetch_related(

View File

@@ -8,7 +8,7 @@ from pretix.base.models import CachedFile, CachedTicket, Order, cachedfile_name
from pretix.base.signals import register_ticket_outputs
def generate(order, provider):
def generate(order: str, provider: str):
order = Order.objects.current.select_related('event').get(identity=order)
ct = CachedTicket.objects.get_or_create(order=order, provider=provider)[0]
if not ct.cachedfile:

View File

@@ -7,6 +7,7 @@ from django.conf import settings
from django.core.files import File
from django.core.files.storage import default_storage
from django.db.models import Model
from typing import Any, Callable, Dict, Optional, TypeVar, Union
from versions.models import Versionable
DEFAULTS = {
@@ -109,23 +110,23 @@ class SettingsProxy:
you. It will return None for non-existing properties.
"""
def __init__(self, obj, parent=None, type=None):
def __init__(self, obj: Model, parent: Optional[Model]=None, type=None):
self._obj = obj
self._parent = parent
self._cached_obj = None
self._type = type
def _cache(self):
def _cache(self) -> Dict[str, Any]:
if self._cached_obj is None:
self._cached_obj = {}
for setting in self._obj.setting_objects.current.all():
self._cached_obj[setting.key] = setting
return self._cached_obj
def _flush(self):
def _flush(self) -> None:
self._cached_obj = None
def _unserialize(self, value, as_type):
def _unserialize(self, value: str, as_type: type) -> Any:
if as_type is not None and isinstance(value, as_type):
return value
elif value is None:
@@ -155,7 +156,7 @@ class SettingsProxy:
return as_type.objects.get(pk=value)
return value
def _serialize(self, value):
def _serialize(self, value: Any) -> str:
if isinstance(value, str):
return value
elif isinstance(value, int) or isinstance(value, float) \
@@ -174,7 +175,7 @@ class SettingsProxy:
raise TypeError('Unable to serialize %s into a setting.' % str(type(value)))
def get(self, key, default=None, as_type=None):
def get(self, key: str, default: Any=None, as_type: type=None):
"""
Get a setting specified by key 'key'. Normally, settings are strings, but
if you put non-strings into the settings object, you can request unserialization
@@ -199,21 +200,21 @@ class SettingsProxy:
return self._unserialize(value, as_type)
def __getitem__(self, key):
def __getitem__(self, key: str) -> Any:
return self.get(key)
def __getattr__(self, key):
def __getattr__(self, key: str) -> Any:
return self.get(key)
def __setattr__(self, key, value):
def __setattr__(self, key: str, value: Any) -> None:
if key.startswith('_'):
return super().__setattr__(key, value)
self.set(key, value)
def __setitem__(self, key, value):
def __setitem__(self, key: str, value: Any) -> None:
self.set(key, value)
def set(self, key, value):
def set(self, key: str, value: Any) -> None:
if key in self._cache():
s = self._cache()[key]
s = s.clone()
@@ -223,12 +224,12 @@ class SettingsProxy:
s.save()
self._cache()[key] = s
def __delattr__(self, key):
def __delattr__(self, key: str) -> None:
if key.startswith('_'):
return super().__delattr__(key)
return self.__delitem__(key)
def __delitem__(self, key):
def __delitem__(self, key: str) -> None:
if key in self._cache():
self._cache()[key].delete()
del self._cache()[key]
@@ -240,36 +241,36 @@ class SettingsSandbox:
prefixes for you.
"""
def __init__(self, type, key, event):
def __init__(self, type: str, key: str, event: Versionable):
self._event = event
self._type = type
self._key = key
def _convert_key(self, key):
def _convert_key(self, key: str) -> str:
return '%s_%s_%s' % (self._type, self._key, key)
def __setitem__(self, key, value):
def __setitem__(self, key: str, value: Any) -> None:
self.set(key, value)
def __setattr__(self, key, value):
def __setattr__(self, key: str, value: Any) -> None:
if key.startswith('_'):
return super().__setattr__(key, value)
self.set(key, value)
def __getattr__(self, item):
def __getattr__(self, item: str) -> Any:
return self.get(item)
def __getitem__(self, item):
def __getitem__(self, item: str) -> Any:
return self.get(item)
def __delitem__(self, key):
def __delitem__(self, key: str) -> None:
del self._event.settings[self._convert_key(key)]
def __delattr__(self, key):
def __delattr__(self, key: str) -> None:
del self._event.settings[self._convert_key(key)]
def get(self, key, default=None, as_type=str):
def get(self, key: str, default: Any=None, as_type: type=str):
return self._event.settings.get(self._convert_key(key), default=default, as_type=as_type)
def set(self, key, value):
def set(self, key: str, value: Any):
self._event.settings.set(self._convert_key(key), value)

View File

@@ -1,6 +1,7 @@
import django.dispatch
from django.apps import apps
from django.dispatch.dispatcher import NO_RECEIVERS
from typing import Any, Callable, List, Tuple
from .models import Event
@@ -12,7 +13,7 @@ class EventPluginSignal(django.dispatch.Signal):
Event.
"""
def send(self, sender, **named):
def send(self, sender: Event, **named) -> List[Tuple[Callable, Any]]:
"""
Send signal from sender to all connected receivers that belong to
plugins enabled for the given Event.

View File

@@ -3,8 +3,9 @@ from collections import OrderedDict
from django import forms
from django.http import HttpRequest
from django.utils.translation import ugettext_lazy as _
from typing import Tuple
from pretix.base.models import Order
from pretix.base.models import Event, Order
from pretix.base.settings import SettingsSandbox
@@ -13,7 +14,7 @@ class BaseTicketOutput:
This is the base class for all ticket outputs.
"""
def __init__(self, event):
def __init__(self, event: Event):
self.event = event
self.settings = SettingsSandbox('ticketoutput', self.identifier, event)
@@ -28,7 +29,7 @@ class BaseTicketOutput:
"""
return self.settings.get('_enabled', as_type=bool)
def generate(self, order: Order) -> tuple:
def generate(self, order: Order) -> Tuple[str, str, str]:
"""
This method should generate the download file and return a tuple consisting of a
filename, a file type and file content.

View File

@@ -1,3 +1,6 @@
from typing import Iterable, List, Tuple
class VariationDict(dict):
"""
A VariationDict object behaves exactle the same as the Python built-in
@@ -7,7 +10,7 @@ class VariationDict(dict):
"""
IGNORE_KEYS = ('variation', 'key', 'available', 'price')
def relevant_items(self) -> "list[(str, PropertyValue)]":
def relevant_items(self) -> Iterable[Tuple]:
"""
Iterate over all items with numeric keys.
@@ -16,7 +19,7 @@ class VariationDict(dict):
"""
return (i for i in self.items() if i[0] not in self.IGNORE_KEYS)
def relevant_values(self) -> "list[PropertyValue]":
def relevant_values(self) -> Iterable["PropertyValue"]:
"""
Iterate over all values with numeric keys.
@@ -60,14 +63,14 @@ class VariationDict(dict):
else:
return super().__eq__(other)
def empty(self):
def empty(self) -> bool:
"""
Returns true, if this VariationDict does not contain any "real" data like
references to PropertyValues, but only "metadata".
"""
return not next(self.relevant_items(), False)
def ordered_values(self) -> "list[ItemVariation]":
def ordered_values(self) -> List["ItemVariation"]:
"""
Returns a list of values ordered by their keys
"""
@@ -79,7 +82,7 @@ class VariationDict(dict):
)
]
def __str__(self):
def __str__(self) -> str:
return " ".join([str(v.value) for v in self.ordered_values()])
def copy(self) -> "VariationDict":

View File

@@ -1,4 +1,4 @@
from django.http import HttpResponse
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.utils.functional import cached_property
from django.views.generic import TemplateView
@@ -10,10 +10,10 @@ class DownloadView(TemplateView):
template_name = "pretixbase/cachedfiles/pending.html"
@cached_property
def object(self):
def object(self) -> CachedFile:
return get_object_or_404(CachedFile, id=self.kwargs['id'])
def get(self, request, *args, **kwargs):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
if 'ajax' in request.GET:
return HttpResponse('1' if self.object.file else '0')
elif self.object.file:

View File

@@ -0,0 +1,2 @@
typing