drop python 3.5

This commit is contained in:
Oskar Hahn 2018-08-22 22:00:08 +02:00
parent cbf8a33b8d
commit aac9dcabf5
43 changed files with 123 additions and 125 deletions

View File

@ -5,7 +5,6 @@ cache:
pip: true
yarn: true
python:
- "3.5"
- "3.6"
- "3.7"
env:

View File

@ -9,7 +9,8 @@ Version 3.0 (unreleased)
Core:
- Change URL schema [#3798].
- Update to channels2
- Update to channels2 [#3796].
- Drop Python 3.5 support[#3805].
Version 2.3 (unreleased)

View File

@ -15,7 +15,7 @@ Installation and start of the development version
a. Check requirements
'''''''''''''''''''''
Make sure that you have installed `Python (>= 3.5) <https://www.python.org/>`_,
Make sure that you have installed `Python (>= 3.6) <https://www.python.org/>`_,
`Node.js (>=4.x) <https://nodejs.org/>`_, `Yarn <https://yarnpkg.com/>`_ and
`Git <http://git-scm.com/>`_ on your system. You also need build-essential
packages and header files and a static library for Python.
@ -99,7 +99,7 @@ Use gulp watch in a second command-line interface::
Follow the instructions above (Installation on GNU/Linux or Mac OS X) but care
of the following variations.
To get Python download and run the latest `Python 3.5 32-bit (x86) executable
To get Python download and run the latest `Python 3.7 32-bit (x86) executable
installer <https://www.python.org/downloads/windows/>`_. Note that the 32-bit
installer is required even on a 64-bit Windows system. If you use the 64-bit
installer, step d. of the instruction might fail unless you installed some

View File

@ -1,4 +1,4 @@
FROM python:3.5
FROM python:3.7
RUN apt-get -y update && apt-get -y upgrade
RUN apt-get install -y libpq-dev supervisor curl vim
RUN useradd -m openslides

View File

@ -26,7 +26,7 @@ Installation
a. Check requirements
'''''''''''''''''''''
Make sure that you have installed `Python (>= 3.5) <https://www.python.org/>`_
Make sure that you have installed `Python (>= 3.6) <https://www.python.org/>`_
on your system.
Additional you need build-essential packages, header files and a static

View File

@ -2,7 +2,7 @@
import os
import sys
from typing import Dict # noqa
from typing import Dict
import django
from django.core.management import call_command, execute_from_command_line
@ -239,7 +239,7 @@ def createsettings(args):
"""
settings_dir = args.settings_dir
local_installation = is_local_installation()
context = {} # type: Dict[str, str]
context: Dict[str, str] = {}
if local_installation:
if settings_dir is None:

View File

@ -1,4 +1,4 @@
from typing import Any, Dict, Iterable, List, Optional # noqa
from typing import Any, Dict, Iterable, List, Optional
from ..utils.access_permissions import BaseAccessPermissions
from ..utils.auth import has_perm
@ -72,7 +72,7 @@ class ItemAccessPermissions(BaseAccessPermissions):
# In non internal case managers see everything and non managers see
# everything but comments.
if has_perm(user, 'agenda.can_manage'):
blocked_keys_non_internal_hidden_case = [] # type: Iterable[str]
blocked_keys_non_internal_hidden_case: Iterable[str] = []
can_see_hidden = True
else:
blocked_keys_non_internal_hidden_case = ('comment',)

View File

@ -1,5 +1,5 @@
from collections import defaultdict
from typing import Dict, List, Set # noqa
from typing import Dict, List, Set
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
@ -65,7 +65,7 @@ class ItemManager(models.Manager):
all of their children.
"""
queryset = self.order_by('weight')
item_children = defaultdict(list) # type: Dict[int, List[Item]]
item_children: Dict[int, List[Item]] = defaultdict(list)
root_items = []
for item in queryset:
if only_item_type is not None and item.type != only_item_type:
@ -121,7 +121,7 @@ class ItemManager(models.Manager):
yield (element['id'], parent, weight)
yield from walk_items(element.get('children', []), element['id'])
touched_items = set() # type: Set[int]
touched_items: Set[int] = set()
db_items = dict((item.pk, item) for item in Item.objects.all())
for item_id, parent_id, weight in walk_items(tree):
# Check that the item is only once in the tree to prevent invalid trees

View File

@ -1,4 +1,4 @@
from typing import Set # noqa
from typing import Set
from django.apps import apps
from django.contrib.contenttypes.models import ContentType
@ -72,7 +72,7 @@ def required_users(sender, request_user, **kwargs):
if request_user can see the agenda. This function may return an empty
set.
"""
speakers = set() # type: Set[int]
speakers: Set[int] = set()
if has_perm(request_user, 'agenda.can_see'):
for item_collection_element in Collection(Item.get_collection_string()).element_generator():
full_data = item_collection_element.get_full_data()

View File

@ -1,6 +1,6 @@
from typing import Any, Dict, List, Optional
from ..utils.access_permissions import BaseAccessPermissions # noqa
from ..utils.access_permissions import BaseAccessPermissions
from ..utils.auth import has_perm
from ..utils.collection import CollectionElement

View File

@ -1,4 +1,4 @@
from typing import Dict, List, Union # noqa
from typing import List
from django.apps import AppConfig
from mypy_extensions import TypedDict
@ -48,10 +48,10 @@ class AssignmentsAppConfig(AppConfig):
def get_angular_constants(self):
assignment = self.get_model('Assignment')
InnerItem = TypedDict('InnerItem', {'value': int, 'display_name': str})
Item = TypedDict('Item', {'name': str, 'value': List[InnerItem]}) # noqa
data = {
Item = TypedDict('Item', {'name': str, 'value': List[InnerItem]})
data: Item = {
'name': 'AssignmentPhases',
'value': []} # type: Item
'value': []}
for phase in assignment.PHASES:
data['value'].append({
'value': phase[0],

View File

@ -1,5 +1,5 @@
from collections import OrderedDict
from typing import Any, Dict, List, Optional # noqa
from typing import Any, Dict, List
from django.conf import settings
from django.contrib.contenttypes.fields import GenericRelation
@ -301,14 +301,14 @@ class Assignment(RESTModelMixin, models.Model):
Returns a table represented as a list with all candidates from all
related polls and their vote results.
"""
vote_results_dict = OrderedDict() # type: Dict[Any, List[AssignmentVote]]
vote_results_dict: Dict[Any, List[AssignmentVote]] = OrderedDict()
polls = self.polls.all()
if only_published:
polls = polls.filter(published=True)
# All PollOption-Objects related to this assignment
options = [] # type: List[AssignmentOption]
options: List[AssignmentOption] = []
for poll in polls:
options += poll.get_options()
@ -318,7 +318,7 @@ class Assignment(RESTModelMixin, models.Model):
continue
vote_results_dict[candidate] = []
for poll in polls:
votes = {} # type: Any
votes: Any = {}
try:
# candidate related to this poll
poll_option = poll.get_options().get(candidate=candidate)
@ -333,7 +333,7 @@ class Assignment(RESTModelMixin, models.Model):
"""
Container for runtime information for agenda app (on create or update of this instance).
"""
agenda_item_update_information = {} # type: Dict[str, Any]
agenda_item_update_information: Dict[str, Any] = {}
def get_agenda_title(self):
return str(self)

View File

@ -1,4 +1,4 @@
from typing import Any, Set # noqa
from typing import Any, Set
from django.apps import apps
@ -24,7 +24,7 @@ def required_users(sender, request_user, **kwargs):
options) in any assignment if request_user can see assignments. This
function may return an empty set.
"""
candidates = set() # type: Set[Any] # TODO: Replace Any
candidates: Set[Any] = set()
if has_perm(request_user, 'assignments.can_see'):
for assignment_collection_element in Collection(Assignment.get_collection_string()).element_generator():
full_data = assignment_collection_element.get_full_data()

View File

@ -1,6 +1,6 @@
from collections import OrderedDict
from operator import attrgetter
from typing import Any, List # noqa
from typing import Any, List
from django.apps import AppConfig
from django.conf import settings
@ -93,7 +93,7 @@ class CoreAppConfig(AppConfig):
'value': client_settings_dict}
# Config variables
config_groups = [] # type: List[Any] # TODO: Replace Any by correct type
config_groups: List[Any] = []
for config_variable in sorted(config.config_variables.values(), key=attrgetter('weight')):
if config_variable.is_hidden():
# Skip hidden config variables. Do not even check groups and subgroups.

View File

@ -46,10 +46,10 @@ class ConfigHandler:
def __init__(self) -> None:
# Dict, that keeps all ConfigVariable objects. Has to be set at statup.
# See the ready() method in openslides.core.apps.
self.config_variables = {} # type: Dict[str, ConfigVariable]
self.config_variables: Dict[str, ConfigVariable] = {}
# Index to get the database id from a given config key
self.key_to_id = None # type: Optional[Dict[str, int]]
self.key_to_id: Optional[Dict[str, int]] = None
def __getitem__(self, key: str) -> Any:
"""

View File

@ -1,7 +1,7 @@
import json
import uuid
from textwrap import dedent
from typing import Any, Dict, List, cast # noqa
from typing import Any, Dict, List, cast
from django.apps import apps
from django.conf import settings
@ -91,7 +91,7 @@ class WebclientJavaScriptView(utils_views.View):
AngularJS app for the requested realm (site or projector). Also code
for plugins is appended. The result is not uglified.
"""
cache = {} # type: Dict[str, str]
cache: Dict[str, str] = {}
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
@ -102,8 +102,8 @@ class WebclientJavaScriptView(utils_views.View):
self.init_cache('projector')
def init_cache(self, realm: str) -> None:
angular_modules = [] # type: List[str]
js_files = [] # type: List[str]
angular_modules: List[str] = []
js_files: List[str] = []
for app_config in apps.get_app_configs():
# Add the angular app if the module has one.
if getattr(app_config, 'angular_{}_module'.format(realm), False):
@ -836,16 +836,16 @@ class VersionView(utils_views.APIView):
http_method_names = ['get']
def get_context_data(self, **context):
Result = TypedDict('Result', { # noqa
Result = TypedDict('Result', {
'openslides_version': str,
'openslides_license': str,
'openslides_url': str,
'plugins': List[Dict[str, str]]})
result = dict(
result: Result = dict(
openslides_version=version,
openslides_license=license,
openslides_url=url,
plugins=[]) # type: Result
plugins=[])
# Versions of plugins.
for plugin in settings.INSTALLED_PLUGINS:
result['plugins'].append({

View File

@ -1,6 +1,6 @@
from typing import Any, Dict, List, Optional
from ..utils.access_permissions import BaseAccessPermissions # noqa
from ..utils.access_permissions import BaseAccessPermissions
from ..utils.auth import has_perm
from ..utils.collection import CollectionElement

View File

@ -2,7 +2,7 @@ from copy import deepcopy
from typing import Any, Dict, List, Optional
from ..core.config import config
from ..utils.access_permissions import BaseAccessPermissions # noqa
from ..utils.access_permissions import BaseAccessPermissions
from ..utils.auth import has_perm
from ..utils.collection import CollectionElement

View File

@ -1,4 +1,4 @@
from typing import Any, Dict # noqa
from typing import Any, Dict
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
@ -692,7 +692,7 @@ class Motion(RESTModelMixin, models.Model):
"""
Container for runtime information for agenda app (on create or update of this instance).
"""
agenda_item_update_information = {} # type: Dict[str, Any]
agenda_item_update_information: Dict[str, Any] = {}
def get_agenda_title(self):
"""
@ -1052,7 +1052,7 @@ class MotionBlock(RESTModelMixin, models.Model):
"""
Container for runtime information for agenda app (on create or update of this instance).
"""
agenda_item_update_information = {} # type: Dict[str, Any]
agenda_item_update_information: Dict[str, Any] = {}
@property
def agenda_item(self):

View File

@ -1,4 +1,4 @@
from typing import Dict # noqa
from typing import Dict
from django.db import transaction
from django.utils.translation import ugettext as _
@ -233,7 +233,7 @@ class MotionPollSerializer(ModelSerializer):
def __init__(self, *args, **kwargs):
# The following dictionary is just a cache for several votes.
self._votes_dicts = {} # type: Dict[int, Dict[int, int]]
self._votes_dicts: Dict[int, Dict[int, int]] = {}
return super().__init__(*args, **kwargs)
def get_yes(self, obj):

View File

@ -1,4 +1,4 @@
from typing import Set # noqa
from typing import Set
from django.apps import apps
from django.utils.translation import ugettext_noop
@ -126,7 +126,7 @@ def required_users(sender, request_user, **kwargs):
any motion if request_user can see motions. This function may return an
empty set.
"""
submitters_supporters = set() # type: Set[int]
submitters_supporters: Set[int] = set()
if has_perm(request_user, 'motions.can_see'):
for motion_collection_element in Collection(Motion.get_collection_string()).element_generator():
full_data = motion_collection_element.get_full_data()

View File

@ -1,5 +1,5 @@
import re
from typing import Optional # noqa
from typing import Optional
from django.conf import settings
from django.contrib.auth import get_user_model
@ -113,9 +113,9 @@ class MotionViewSet(ModelViewSet):
# Check if parent motion exists.
if request.data.get('parent_id') is not None:
try:
parent_motion = CollectionElement.from_values(
parent_motion: Optional[CollectionElement] = CollectionElement.from_values(
Motion.get_collection_string(),
request.data['parent_id']) # type: Optional[CollectionElement]
request.data['parent_id'])
except Motion.DoesNotExist:
raise ValidationError({'detail': _('The parent motion does not exist.')})
else:

View File

@ -1,5 +1,5 @@
import locale
from typing import Type # noqa
from typing import Optional, Type
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
@ -17,7 +17,7 @@ class BaseOption(models.Model):
which has to be a subclass of BaseVote. Otherwise you have to override the
get_vote_class method.
"""
vote_class = None # type: Type[BaseVote]
vote_class: Optional[Type['BaseVote']] = None
class Meta:
abstract = True

View File

@ -1,4 +1,4 @@
from typing import Any, Dict # noqa
from typing import Any, Dict
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
@ -59,7 +59,7 @@ class Topic(RESTModelMixin, models.Model):
"""
Container for runtime information for agenda app (on create or update of this instance).
"""
agenda_item_update_information = {} # type: Dict[str, Any]
agenda_item_update_information: Dict[str, Any] = {}
@property
def agenda_item(self):

View File

@ -3,7 +3,7 @@ from typing import Any, Dict, List, Optional
from django.contrib.auth.models import AnonymousUser
from ..core.signals import user_data_required
from ..utils.access_permissions import BaseAccessPermissions # noqa
from ..utils.access_permissions import BaseAccessPermissions
from ..utils.auth import anonymous_is_enabled, has_perm
from ..utils.collection import CollectionElement
@ -172,7 +172,7 @@ class PersonalNoteAccessPermissions(BaseAccessPermissions):
"""
# Parse data.
if user is None:
data = [] # type: List[Dict[str, Any]]
data: List[Dict[str, Any]] = []
else:
for full in full_data:
if full['user_id'] == user.id:

View File

@ -1,5 +1,5 @@
import smtplib
from typing import List # noqa
from typing import List
from asgiref.sync import async_to_sync
from django.conf import settings
@ -325,7 +325,7 @@ class GroupViewSet(ModelViewSet):
# Some permissions are added.
if len(new_permissions) > 0:
collection_elements = [] # type: List[CollectionElement]
collection_elements: List[CollectionElement] = []
signal_results = permission_change.send(None, permissions=new_permissions, action='added')
for receiver, signal_collections in signal_results:
for cachable in signal_collections:

View File

@ -1,9 +1,9 @@
from argparse import Namespace
from typing import Any, Union # noqa
from typing import Any, Optional
class OpenSlidesArguments():
args = None # type: Union[None, Namespace]
args: Optional[Namespace] = None
def __getitem__(self, key: str) -> Any:
if not self.args:

View File

@ -16,7 +16,7 @@ def to_ordered_dict(d: Optional[Dict]) -> Optional[OrderedDict]:
Little helper to hash information dict in inform_*_data.
"""
if isinstance(d, dict):
result = OrderedDict([(key, to_ordered_dict(d[key])) for key in sorted(d.keys())]) # type: Optional[OrderedDict]
result: Optional[OrderedDict] = OrderedDict([(key, to_ordered_dict(d[key])) for key in sorted(d.keys())])
else:
result = d
return result
@ -64,7 +64,7 @@ def inform_deleted_data(elements: Iterable[Tuple[str, int]], information: Dict[s
The argument information is added to each collection element.
"""
collection_elements = {} # type: Dict[str, Any]
collection_elements: Dict[str, Any] = {}
for element in elements:
collection_element = CollectionElement.from_values(
collection_string=element[0],
@ -106,7 +106,7 @@ def inform_data_collection_element_list(collection_elements: List[CollectionElem
"""
Global container for autoupdate bundles
"""
autoupdate_bundle = {} # type: Dict[int, Dict[str, CollectionElement]]
autoupdate_bundle: Dict[int, Dict[str, CollectionElement]] = {}
class AutoupdateBundleMiddleware:
@ -123,7 +123,7 @@ class AutoupdateBundleMiddleware:
response = self.get_response(request)
bundle = autoupdate_bundle.pop(thread_id) # type: Dict[str, CollectionElement]
bundle: Dict[str, CollectionElement] = autoupdate_bundle.pop(thread_id)
async_to_sync(send_autoupdate)(bundle.values())
return response
@ -138,7 +138,7 @@ async def send_autoupdate(collection_elements: Iterable[CollectionElement]) -> N
Does nothing if collection_elements is empty.
"""
if collection_elements:
cache_elements = {} # type: Dict[str, Optional[Dict[str, Any]]]
cache_elements: Dict[str, Optional[Dict[str, Any]]] = {}
for element in collection_elements:
element_id = get_element_id(element.collection_string, element.id)
if element.is_deleted():

View File

@ -73,7 +73,7 @@ class ElementCache:
self.use_restricted_data_cache = use_restricted_data_cache
self.cache_provider = cache_provider_class(redis)
self.cachable_provider = cachable_provider
self._cachables = None # type: Optional[Dict[str, Cachable]]
self._cachables: Optional[Dict[str, Cachable]] = None
# Start time is used as first change_id if there is non in redis
if start_time is None:
@ -81,7 +81,7 @@ class ElementCache:
self.start_time = start_time
# Contains Futures to controll, that only one client updates the restricted_data.
self.restricted_data_cache_updater = {} # type: Dict[int, asyncio.Future]
self.restricted_data_cache_updater: Dict[int, asyncio.Future] = {}
@property
def cachables(self) -> Dict[str, Cachable]:
@ -109,7 +109,7 @@ class ElementCache:
"""
Build or rebuild the full_data cache.
"""
db_data = {} # type: Dict[str, List[Dict[str, Any]]]
db_data = {}
for collection_string, cachable in self.cachables.items():
db_data[collection_string] = await database_sync_to_async(cachable.get_elements)()
await self.save_full_data(db_data)
@ -262,7 +262,7 @@ class ElementCache:
# If this succeeds, there is noone else currently updating the cache.
# TODO: Make a timeout. Else this could block forever
if await self.cache_provider.set_lock_restricted_data(get_user_id(user)):
future = asyncio.Future() # type: asyncio.Future
future: asyncio.Future = asyncio.Future()
self.restricted_data_cache_updater[get_user_id(user)] = future
# Get change_id for this user
value = await self.cache_provider.get_change_id_user(get_user_id(user))
@ -318,7 +318,7 @@ class ElementCache:
await self.update_restricted_data(user)
out = defaultdict(list) # type: Dict[str, List[Dict[str, Any]]]
out: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
restricted_data = await self.cache_provider.get_all_data(get_user_id(user))
for element_id, data in restricted_data.items():
if element_id.decode().startswith('_config'):

View File

@ -1,5 +1,4 @@
from collections import defaultdict
from typing import Set # noqa
from typing import (
TYPE_CHECKING,
Any,
@ -8,6 +7,7 @@ from typing import (
Iterable,
List,
Optional,
Set,
Tuple,
Union,
)
@ -108,7 +108,7 @@ class RedisCacheProvider(BaseCacheProvider):
"""
Cache provider that loads and saves the data to redis.
"""
redis_pool = None # type: Optional[aioredis.RedisConnection]
redis_pool: Optional[aioredis.RedisConnection] = None
def __init__(self, redis: str) -> None:
self.redis_address = redis
@ -232,8 +232,8 @@ class RedisCacheProvider(BaseCacheProvider):
"""
# TODO: rewrite with lua to get all elements with one request
redis = await self.get_connection()
changed_elements = defaultdict(list) # type: Dict[str, List[bytes]]
deleted_elements = [] # type: List[str]
changed_elements: Dict[str, List[bytes]] = defaultdict(list)
deleted_elements: List[str] = []
for element_id in await redis.zrangebyscore(self.get_change_id_cache_key(), min=change_id):
if element_id.startswith(b'_config'):
continue
@ -335,9 +335,9 @@ class MemmoryCacheProvider(BaseCacheProvider):
self.clear_cache()
def clear_cache(self) -> None:
self.full_data = {} # type: Dict[str, str]
self.restricted_data = {} # type: Dict[int, Dict[str, str]]
self.change_id_data = {} # type: Dict[int, Set[str]]
self.full_data: Dict[str, str] = {}
self.restricted_data: Dict[int, Dict[str, str]] = {}
self.change_id_data: Dict[int, Set[str]] = {}
async def reset_full_cache(self, data: Dict[str, str]) -> None:
self.full_data = data
@ -392,8 +392,8 @@ class MemmoryCacheProvider(BaseCacheProvider):
async def get_data_since(
self, change_id: int, user_id: Optional[int] = None) -> Tuple[Dict[str, List[bytes]], List[str]]:
changed_elements = defaultdict(list) # type: Dict[str, List[bytes]]
deleted_elements = [] # type: List[str]
changed_elements: Dict[str, List[bytes]] = defaultdict(list)
deleted_elements: List[str] = []
if user_id is None:
cache_dict = self.full_data
else:
@ -495,7 +495,7 @@ def get_all_cachables() -> List[Cachable]:
"""
Returns all element of OpenSlides.
"""
out = [] # type: List[Cachable]
out: List[Cachable] = []
for app in apps.get_app_configs():
try:
# Get the method get_startup_elements() from an app.

View File

@ -21,7 +21,7 @@ from .cache_providers import Cachable
if TYPE_CHECKING:
from .access_permissions import BaseAccessPermissions # noqa
from .access_permissions import BaseAccessPermissions
AutoupdateFormat = TypedDict(
@ -200,7 +200,7 @@ class CollectionElement:
if self.full_data is None:
if self.instance is None:
# The type of data has to be set for mypy
data = None # type: Optional[Dict[str, Any]]
data: Optional[Dict[str, Any]] = None
if getattr(settings, 'SKIP_CACHE', False):
# Hack for django 2.0 and channels 2.1 to stay in the same thread.
# This is needed for the tests.
@ -277,7 +277,7 @@ class Collection(Cachable):
"""
if self.full_data is None:
# The type of all_full_data has to be set for mypy
all_full_data = {} # type: Dict[str, List[Dict[str, Any]]]
all_full_data: Dict[str, List[Dict[str, Any]]] = {}
if getattr(settings, 'SKIP_CACHE', False):
# Hack for django 2.0 and channels 2.1 to stay in the same thread.
# This is needed for the tests.
@ -316,7 +316,7 @@ class Collection(Cachable):
return self.get_model().get_access_permissions().get_restricted_data(user, elements)
_models_to_collection_string = {} # type: Dict[str, Type[Model]]
_models_to_collection_string: Dict[str, Type[Model]] = {}
def get_model_from_collection_string(collection_string: str) -> Type[Model]:

View File

@ -8,7 +8,6 @@ from ..core.config import config
from ..core.models import Projector
from .auth import async_anonymous_is_enabled, has_perm
from .cache import element_cache, split_element_id
from .collection import AutoupdateFormat # noqa
from .collection import (
Collection,
CollectionElement,
@ -236,7 +235,7 @@ def projector_startup_data(projector_id: int) -> Any:
projector = Projector.objects.get(pk=config['projector_broadcast'])
# Collect all elements that are on the projector.
output = [] # type: List[AutoupdateFormat]
output = []
for requirement in projector.get_all_requirements():
required_collection_element = CollectionElement.from_instance(requirement)
output.append(required_collection_element.as_autoupdate_for_projector())

View File

@ -9,7 +9,7 @@ from .utils import convert_camel_case_to_pseudo_snake_case
if TYPE_CHECKING:
# Dummy import Collection for mypy, can be fixed with python 3.7
from .collection import Collection, CollectionElement # noqa
from .collection import CollectionElement # noqa
class MinMaxIntegerField(models.IntegerField):
@ -32,7 +32,7 @@ class RESTModelMixin:
Mixin for Django models which are used in our REST API.
"""
access_permissions = None # type: BaseAccessPermissions
access_permissions: Optional[BaseAccessPermissions] = None
def get_root_rest_element(self) -> models.Model:
"""

View File

@ -146,7 +146,7 @@ def get_all_plugin_urlpatterns() -> List[Any]:
Helper function to return all urlpatterns of all plugins listed in
settings.INSTALLED_PLUGINS.
"""
urlpatterns = [] # type: List[Any]
urlpatterns: List[Any] = []
for plugin in settings.INSTALLED_PLUGINS:
plugin_urlpatterns = get_plugin_urlpatterns(plugin)
if plugin_urlpatterns:

View File

@ -1,4 +1,4 @@
from typing import Any, Dict, Generator, Iterable, List, Type
from typing import Any, Dict, Generator, Iterable, List, Optional, Type
from .collection import CollectionElement
@ -11,7 +11,7 @@ class ProjectorElement:
subclassing from this base class with different names. The name attribute
has to be set.
"""
name = None # type: str
name: Optional[str] = None
def check_and_update_data(self, projector_object: Any, config_entry: Any) -> Any:
"""
@ -84,7 +84,7 @@ class ProjectorElement:
return output
projector_elements = {} # type: Dict[str, ProjectorElement]
projector_elements: Dict[str, ProjectorElement] = {}
def register_projector_elements(elements: Generator[Type[ProjectorElement], None, None]) -> None:
@ -95,7 +95,7 @@ def register_projector_elements(elements: Generator[Type[ProjectorElement], None
"""
for Element in elements:
element = Element()
projector_elements[element.name] = element
projector_elements[element.name] = element # type: ignore
def get_all_projector_elements() -> Dict[str, ProjectorElement]:

View File

@ -1,11 +1,11 @@
from collections import OrderedDict
from typing import Any, Dict, Iterable, Optional, Type # noqa
from typing import Any, Dict, Iterable, Optional, Type
from django.http import Http404
from rest_framework import status # noqa
from rest_framework.decorators import detail_route, list_route # noqa
from rest_framework.metadata import SimpleMetadata # noqa
from rest_framework.mixins import ( # noqa
from rest_framework import status
from rest_framework.decorators import detail_route, list_route
from rest_framework.metadata import SimpleMetadata
from rest_framework.mixins import (
CreateModelMixin,
DestroyModelMixin,
ListModelMixin as _ListModelMixin,
@ -15,7 +15,7 @@ from rest_framework.mixins import ( # noqa
from rest_framework.relations import MANY_RELATION_KWARGS
from rest_framework.response import Response
from rest_framework.routers import DefaultRouter
from rest_framework.serializers import ( # noqa
from rest_framework.serializers import (
CharField,
DictField,
Field,
@ -32,7 +32,7 @@ from rest_framework.serializers import ( # noqa
SerializerMethodField,
ValidationError,
)
from rest_framework.viewsets import ( # noqa
from rest_framework.viewsets import (
GenericViewSet as _GenericViewSet,
ModelViewSet as _ModelViewSet,
ViewSet as _ViewSet,
@ -43,6 +43,12 @@ from .auth import user_to_collection_user
from .collection import Collection, CollectionElement
__all__ = ['status', 'detail_route', 'list_route', 'SimpleMetadata', 'CreateModelMixin',
'DestroyModelMixin', 'UpdateModelMixin', 'CharField', 'DictField', 'FileField',
'IntegerField', 'JSONField', 'ListField', 'ListSerializer', 'RelatedField',
'SerializerMethodField', 'ValidationError']
router = DefaultRouter()
@ -112,7 +118,7 @@ class PermissionMixin:
Also connects container to handle access permissions for model and
viewset.
"""
access_permissions = None # type: Optional[BaseAccessPermissions]
access_permissions: Optional[BaseAccessPermissions] = None
def get_permissions(self) -> Iterable[str]:
"""
@ -165,7 +171,7 @@ class ModelSerializer(_ModelSerializer):
"""
Returns all fields of the serializer.
"""
fields = OrderedDict() # type: Dict[str, Field]
fields: Dict[str, Field] = OrderedDict()
for field_name, field in super().get_fields().items():
try:

View File

@ -6,7 +6,7 @@ import roman
if TYPE_CHECKING:
# Dummy import Collection for mypy, can be fixed with python 3.7
from .collection import Collection, CollectionElement # noqa
from .collection import CollectionElement # noqa
CAMEL_CASE_TO_PSEUDO_SNAKE_CASE_CONVERSION_REGEX_1 = re.compile('(.)([A-Z][a-z]+)')
CAMEL_CASE_TO_PSEUDO_SNAKE_CASE_CONVERSION_REGEX_2 = re.compile('([a-z0-9])([A-Z])')

View File

@ -1,5 +1,5 @@
import base64
from typing import Any, Dict, List # noqa
from typing import Any, Dict, List, Optional
from django.contrib.staticfiles import finders
from django.core.exceptions import ImproperlyConfigured
@ -28,7 +28,7 @@ class APIView(_APIView):
The Django Rest framework APIView with improvements for OpenSlides.
"""
http_method_names = [] # type: List[str]
http_method_names: List[str] = []
"""
The allowed actions have to be explicitly defined.
@ -60,8 +60,8 @@ class TemplateView(View):
is not allowed to change. So the State has to be saved in this dict. Search for 'Borg design
pattern' for more information.
"""
template_name = None # type: str
state = {} # type: Dict[str, str]
template_name: Optional[str] = None
state: Dict[str, str] = {}
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
@ -78,7 +78,7 @@ class TemplateView(View):
return template.read()
def get(self, *args: Any, **kwargs: Any) -> HttpResponse:
return HttpResponse(self.state[self.template_name])
return HttpResponse(self.state[self.template_name]) # type: ignore
class BinaryTemplateView(TemplateView):

View File

@ -8,6 +8,7 @@ coverage
git+https://gitlab.com/pycqa/flake8.git
isort
mypy
pytest>=3.7.2
pytest-django
pytest-asyncio
pytest-cov

View File

@ -33,7 +33,6 @@ setup(
'Framework :: Django',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7', ],
packages=find_packages(exclude=['tests', 'tests.*']),

View File

@ -43,17 +43,10 @@ def prepare_element_cache(settings):
@pytest.fixture
def communicator(request, event_loop):
async def communicator(request, event_loop):
communicator = WebsocketCommunicator(application, "/ws/site/")
# This style is needed for python 3.5. Use the generaor style when 3.5 ist dropped
def fin():
async def afin():
await communicator.disconnect()
event_loop.run_until_complete(afin())
request.addfinalizer(fin)
return communicator
yield communicator
await communicator.disconnect()
@pytest.mark.asyncio

View File

@ -1,12 +1,12 @@
import asyncio # noqa
import asyncio
from typing import Any, Callable, Dict, List, Optional
from openslides.utils.cache_providers import Cachable, MemmoryCacheProvider
from openslides.utils.collection import CollectionElement # noqa
from openslides.utils.collection import CollectionElement
def restrict_elements(
user: Optional['CollectionElement'],
user: Optional[CollectionElement],
elements: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Adds the prefix 'restricted_' to all values except id.
@ -32,7 +32,7 @@ class Collection1(Cachable):
{'id': 1, 'value': 'value1'},
{'id': 2, 'value': 'value2'}]
def restrict_elements(self, user: Optional['CollectionElement'], elements: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
def restrict_elements(self, user: Optional[CollectionElement], elements: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
return restrict_elements(user, elements)
@ -45,7 +45,7 @@ class Collection2(Cachable):
{'id': 1, 'key': 'value1'},
{'id': 2, 'key': 'value2'}]
def restrict_elements(self, user: Optional['CollectionElement'], elements: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
def restrict_elements(self, user: Optional[CollectionElement], elements: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
return restrict_elements(user, elements)

View File

@ -397,7 +397,7 @@ async def test_update_restricted_data_second_worker_on_same_server(element_cache
"""
element_cache.use_restricted_data_cache = True
element_cache.cache_provider.restricted_data = {0: {}}
future = asyncio.Future() # type: asyncio.Future
future: asyncio.Future = asyncio.Future()
element_cache.restricted_data_cache_updater[0] = future
await element_cache.cache_provider.set_lock_restricted_data(0)
await element_cache.cache_provider.del_lock_restricted_data_after_wait(0, future)