commit
e8283ac90b
@ -5,7 +5,6 @@ cache:
|
||||
pip: true
|
||||
yarn: true
|
||||
python:
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
- "3.7"
|
||||
env:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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',)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
@ -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],
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
"""
|
||||
|
@ -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({
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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():
|
||||
|
@ -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'):
|
||||
|
@ -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.
|
||||
|
@ -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]:
|
||||
|
@ -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())
|
||||
|
@ -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:
|
||||
"""
|
||||
|
@ -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:
|
||||
|
@ -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]:
|
||||
|
@ -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:
|
||||
|
@ -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])')
|
||||
|
@ -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):
|
||||
|
@ -8,6 +8,7 @@ coverage
|
||||
git+https://gitlab.com/pycqa/flake8.git
|
||||
isort
|
||||
mypy
|
||||
pytest>=3.7.2
|
||||
pytest-django
|
||||
pytest-asyncio
|
||||
pytest-cov
|
||||
|
1
setup.py
1
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.*']),
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user