diff --git a/.travis.yml b/.travis.yml
index ed4d13386..4474118d4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,6 @@ cache:
pip: true
yarn: true
python:
- - "3.5"
- "3.6"
- "3.7"
env:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index aa6b8d022..b9cb67c8e 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -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)
diff --git a/DEVELOPMENT.rst b/DEVELOPMENT.rst
index c178e5d81..1f7f97a54 100644
--- a/DEVELOPMENT.rst
+++ b/DEVELOPMENT.rst
@@ -15,7 +15,7 @@ Installation and start of the development version
a. Check requirements
'''''''''''''''''''''
-Make sure that you have installed `Python (>= 3.5) `_,
+Make sure that you have installed `Python (>= 3.6) `_,
`Node.js (>=4.x) `_, `Yarn `_ and
`Git `_ 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 `_. 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
diff --git a/Dockerfile b/Dockerfile
index 2609d37b6..4e5d0d89e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -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
diff --git a/README.rst b/README.rst
index 7719accc8..02c27ce27 100644
--- a/README.rst
+++ b/README.rst
@@ -26,7 +26,7 @@ Installation
a. Check requirements
'''''''''''''''''''''
-Make sure that you have installed `Python (>= 3.5) `_
+Make sure that you have installed `Python (>= 3.6) `_
on your system.
Additional you need build-essential packages, header files and a static
diff --git a/openslides/__main__.py b/openslides/__main__.py
index 698a19cca..56b572d69 100644
--- a/openslides/__main__.py
+++ b/openslides/__main__.py
@@ -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:
diff --git a/openslides/agenda/access_permissions.py b/openslides/agenda/access_permissions.py
index 89a47a3b7..a90f73002 100644
--- a/openslides/agenda/access_permissions.py
+++ b/openslides/agenda/access_permissions.py
@@ -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',)
diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py
index 8abe6d614..a6d5a161d 100644
--- a/openslides/agenda/models.py
+++ b/openslides/agenda/models.py
@@ -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
diff --git a/openslides/agenda/signals.py b/openslides/agenda/signals.py
index 1b3e2e787..36208d139 100644
--- a/openslides/agenda/signals.py
+++ b/openslides/agenda/signals.py
@@ -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()
diff --git a/openslides/assignments/access_permissions.py b/openslides/assignments/access_permissions.py
index c3dac3029..7c0f883e7 100644
--- a/openslides/assignments/access_permissions.py
+++ b/openslides/assignments/access_permissions.py
@@ -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
diff --git a/openslides/assignments/apps.py b/openslides/assignments/apps.py
index 04e5398b6..984776b40 100644
--- a/openslides/assignments/apps.py
+++ b/openslides/assignments/apps.py
@@ -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],
diff --git a/openslides/assignments/models.py b/openslides/assignments/models.py
index f3eabc666..35b8b03d3 100644
--- a/openslides/assignments/models.py
+++ b/openslides/assignments/models.py
@@ -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)
diff --git a/openslides/assignments/signals.py b/openslides/assignments/signals.py
index d36589243..48bc9cdb8 100644
--- a/openslides/assignments/signals.py
+++ b/openslides/assignments/signals.py
@@ -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()
diff --git a/openslides/core/apps.py b/openslides/core/apps.py
index da4a8990e..0c342ea4a 100644
--- a/openslides/core/apps.py
+++ b/openslides/core/apps.py
@@ -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.
diff --git a/openslides/core/config.py b/openslides/core/config.py
index 6783ee056..bf28fae1d 100644
--- a/openslides/core/config.py
+++ b/openslides/core/config.py
@@ -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:
"""
diff --git a/openslides/core/views.py b/openslides/core/views.py
index dfaa981f9..23264b37b 100644
--- a/openslides/core/views.py
+++ b/openslides/core/views.py
@@ -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({
diff --git a/openslides/mediafiles/access_permissions.py b/openslides/mediafiles/access_permissions.py
index ab23be913..93c84c8b4 100644
--- a/openslides/mediafiles/access_permissions.py
+++ b/openslides/mediafiles/access_permissions.py
@@ -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
diff --git a/openslides/motions/access_permissions.py b/openslides/motions/access_permissions.py
index 57ccba291..37c629358 100644
--- a/openslides/motions/access_permissions.py
+++ b/openslides/motions/access_permissions.py
@@ -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
diff --git a/openslides/motions/models.py b/openslides/motions/models.py
index c2b0f2e8c..21d0664a4 100644
--- a/openslides/motions/models.py
+++ b/openslides/motions/models.py
@@ -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):
diff --git a/openslides/motions/serializers.py b/openslides/motions/serializers.py
index 6f2791bc2..27c2fca43 100644
--- a/openslides/motions/serializers.py
+++ b/openslides/motions/serializers.py
@@ -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):
diff --git a/openslides/motions/signals.py b/openslides/motions/signals.py
index 4bc838522..41605faf6 100644
--- a/openslides/motions/signals.py
+++ b/openslides/motions/signals.py
@@ -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()
diff --git a/openslides/motions/views.py b/openslides/motions/views.py
index 13b9d6ba6..6b021963d 100644
--- a/openslides/motions/views.py
+++ b/openslides/motions/views.py
@@ -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:
diff --git a/openslides/poll/models.py b/openslides/poll/models.py
index 545e3f111..051f94f8c 100644
--- a/openslides/poll/models.py
+++ b/openslides/poll/models.py
@@ -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
diff --git a/openslides/topics/models.py b/openslides/topics/models.py
index 1f869304a..ecee8331f 100644
--- a/openslides/topics/models.py
+++ b/openslides/topics/models.py
@@ -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):
diff --git a/openslides/users/access_permissions.py b/openslides/users/access_permissions.py
index 545133b5a..3648ca991 100644
--- a/openslides/users/access_permissions.py
+++ b/openslides/users/access_permissions.py
@@ -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:
diff --git a/openslides/users/views.py b/openslides/users/views.py
index d6993d1e4..e0aed75bf 100644
--- a/openslides/users/views.py
+++ b/openslides/users/views.py
@@ -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:
diff --git a/openslides/utils/arguments.py b/openslides/utils/arguments.py
index cfd0d99f3..74321fa46 100644
--- a/openslides/utils/arguments.py
+++ b/openslides/utils/arguments.py
@@ -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:
diff --git a/openslides/utils/autoupdate.py b/openslides/utils/autoupdate.py
index f68770c13..e0757ced1 100644
--- a/openslides/utils/autoupdate.py
+++ b/openslides/utils/autoupdate.py
@@ -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():
diff --git a/openslides/utils/cache.py b/openslides/utils/cache.py
index ac2a162fd..178488936 100644
--- a/openslides/utils/cache.py
+++ b/openslides/utils/cache.py
@@ -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'):
diff --git a/openslides/utils/cache_providers.py b/openslides/utils/cache_providers.py
index 1be8d867d..e2150d4d4 100644
--- a/openslides/utils/cache_providers.py
+++ b/openslides/utils/cache_providers.py
@@ -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.
diff --git a/openslides/utils/collection.py b/openslides/utils/collection.py
index fc43430f6..d0555a3ea 100644
--- a/openslides/utils/collection.py
+++ b/openslides/utils/collection.py
@@ -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]:
diff --git a/openslides/utils/consumers.py b/openslides/utils/consumers.py
index 820b1ad36..5f67bf86a 100644
--- a/openslides/utils/consumers.py
+++ b/openslides/utils/consumers.py
@@ -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())
diff --git a/openslides/utils/models.py b/openslides/utils/models.py
index 08bc033fb..8cdb92396 100644
--- a/openslides/utils/models.py
+++ b/openslides/utils/models.py
@@ -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:
"""
diff --git a/openslides/utils/plugins.py b/openslides/utils/plugins.py
index 987d00b6f..6719415f4 100644
--- a/openslides/utils/plugins.py
+++ b/openslides/utils/plugins.py
@@ -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:
diff --git a/openslides/utils/projector.py b/openslides/utils/projector.py
index c46153d33..14db3d7da 100644
--- a/openslides/utils/projector.py
+++ b/openslides/utils/projector.py
@@ -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]:
diff --git a/openslides/utils/rest_api.py b/openslides/utils/rest_api.py
index e8c40733a..6d42cfbd5 100644
--- a/openslides/utils/rest_api.py
+++ b/openslides/utils/rest_api.py
@@ -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:
diff --git a/openslides/utils/utils.py b/openslides/utils/utils.py
index 8fc4a6dd9..1c64c4ec5 100644
--- a/openslides/utils/utils.py
+++ b/openslides/utils/utils.py
@@ -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])')
diff --git a/openslides/utils/views.py b/openslides/utils/views.py
index dc8411ba5..02e4efd4f 100644
--- a/openslides/utils/views.py
+++ b/openslides/utils/views.py
@@ -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):
diff --git a/requirements.txt b/requirements.txt
index cc82a1270..77450b5f0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,6 +8,7 @@ coverage
git+https://gitlab.com/pycqa/flake8.git
isort
mypy
+pytest>=3.7.2
pytest-django
pytest-asyncio
pytest-cov
diff --git a/setup.py b/setup.py
index b769b8d49..9fd34dfe4 100644
--- a/setup.py
+++ b/setup.py
@@ -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.*']),
diff --git a/tests/integration/utils/test_consumers.py b/tests/integration/utils/test_consumers.py
index c510f79ab..5ae29ef4c 100644
--- a/tests/integration/utils/test_consumers.py
+++ b/tests/integration/utils/test_consumers.py
@@ -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
diff --git a/tests/unit/utils/cache_provider.py b/tests/unit/utils/cache_provider.py
index 3db52363b..49a81f091 100644
--- a/tests/unit/utils/cache_provider.py
+++ b/tests/unit/utils/cache_provider.py
@@ -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)
diff --git a/tests/unit/utils/test_cache.py b/tests/unit/utils/test_cache.py
index aa1a902f1..9c7218b22 100644
--- a/tests/unit/utils/test_cache.py
+++ b/tests/unit/utils/test_cache.py
@@ -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)