Browse Source

merge with master

Guénaël Muller 7 years ago
parent
commit
acd77f215e
5 changed files with 102 additions and 46 deletions
  1. 1 1
      .gitignore
  2. 7 0
      README.txt
  3. 2 0
      setup.py
  4. 81 42
      tracim/lib/core/content.py
  5. 11 3
      tracim/models/data.py

+ 1 - 1
.gitignore View File

@@ -63,4 +63,4 @@ wsgidav.conf
63 63
 
64 64
 # binary translation files
65 65
 *.mo
66
-
66
+.mypy_cache/

+ 7 - 0
README.txt View File

@@ -36,6 +36,13 @@ Getting Started
36 36
 
37 37
     env/bin/pserve development.ini
38 38
 
39
+- Run mypy checks
40
+
41
+    mypy --ignore-missing-imports --disallow-untyped-defs tracim
42
+
43
+- Run pep8 checks
44
+
45
+    pep8 tracim
39 46
 
40 47
 CI
41 48
 ---

+ 2 - 0
setup.py View File

@@ -30,6 +30,8 @@ tests_require = [
30 30
     'pytest',
31 31
     'pytest-cov',
32 32
     'nose',
33
+    'pep8',
34
+    'mypy',
33 35
 ]
34 36
 
35 37
 # Python version adaptations

+ 81 - 42
tracim/lib/core/content.py View File

@@ -49,7 +49,7 @@ from tracim.models.data import Workspace
49 49
 def compare_content_for_sorting_by_type_and_name(
50 50
         content1: Content,
51 51
         content2: Content
52
-):
52
+) -> int:
53 53
     """
54 54
     :param content1:
55 55
     :param content2:
@@ -58,7 +58,7 @@ def compare_content_for_sorting_by_type_and_name(
58 58
                 0 if content1 = content2
59 59
     """
60 60
 
61
-    if content1.type==content2.type:
61
+    if content1.type == content2.type:
62 62
         if content1.get_label().lower()>content2.get_label().lower():
63 63
             return 1
64 64
         elif content1.get_label().lower()<content2.get_label().lower():
@@ -66,11 +66,20 @@ def compare_content_for_sorting_by_type_and_name(
66 66
         return 0
67 67
     else:
68 68
         # TODO - D.A. - 2014-12-02 - Manage Content Types Dynamically
69
-        content_type_order = [ContentType.Folder, ContentType.Page, ContentType.Thread, ContentType.File]
70
-        result = content_type_order.index(content1.type)-content_type_order.index(content2.type)
71
-        if result<0:
69
+        content_type_order = [
70
+            ContentType.Folder,
71
+            ContentType.Page,
72
+            ContentType.Thread,
73
+            ContentType.File,
74
+        ]
75
+
76
+        content_1_type_index = content_type_order.index(content1.type)
77
+        content_2_type_index = content_type_order.index(content2.type)
78
+        result = content_1_type_index - content_2_type_index
79
+
80
+        if result < 0:
72 81
             return -1
73
-        elif result>0:
82
+        elif result > 0:
74 83
             return 1
75 84
         else:
76 85
             return 0
@@ -79,7 +88,7 @@ def compare_content_for_sorting_by_type_and_name(
79 88
 def compare_tree_items_for_sorting_by_type_and_name(
80 89
         item1: NodeTreeItem,
81 90
         item2: NodeTreeItem
82
-):
91
+) -> int:
83 92
     return compare_content_for_sorting_by_type_and_name(item1.node, item2.node)
84 93
 
85 94
 
@@ -101,13 +110,13 @@ class ContentApi(object):
101 110
             session: Session,
102 111
             current_user: typing.Optional[User],
103 112
             config,
104
-            show_archived=False,
105
-            show_deleted=False,
106
-            show_temporary=False,
107
-            all_content_in_treeview=True,
108
-            force_show_all_types=False,
109
-            disable_user_workspaces_filter=False,
110
-    ):
113
+            show_archived: bool = False,
114
+            show_deleted: bool = False,
115
+            show_temporary: bool = False,
116
+            all_content_in_treeview: bool = True,
117
+            force_show_all_types: bool = False,
118
+            disable_user_workspaces_filter: bool = False,
119
+    ) -> None:
111 120
         self._session = session
112 121
         self._user = current_user
113 122
         self._config = config
@@ -125,7 +134,7 @@ class ContentApi(object):
125 134
             show_archived: bool=False,
126 135
             show_deleted: bool=False,
127 136
             show_temporary: bool=False,
128
-    ):
137
+    ) -> typing.Generator['ContentApi', None, None]:
129 138
         """
130 139
         Use this method as context manager to update show_archived,
131 140
         show_deleted and show_temporary properties during context.
@@ -147,11 +156,10 @@ class ContentApi(object):
147 156
             self._show_deleted = previous_show_deleted
148 157
             self._show_temporary = previous_show_temporary
149 158
 
150
-    def get_revision_join(self):
159
+    def get_revision_join(self) -> sqlalchemy.sql.elements.BooleanClauseList:
151 160
         """
152 161
         Return the Content/ContentRevision query join condition
153 162
         :return: Content/ContentRevision query join condition
154
-        :rtype sqlalchemy.sql.elements.BooleanClauseList
155 163
         """
156 164
         return and_(Content.id == ContentRevisionRO.content_id,
157 165
                     ContentRevisionRO.revision_id == self._session.query(
@@ -161,32 +169,41 @@ class ContentApi(object):
161 169
                     .limit(1)
162 170
                     .correlate(Content))
163 171
 
164
-
165
-    def get_canonical_query(self):
172
+    def get_canonical_query(self) -> Query:
166 173
         """
167 174
         Return the Content/ContentRevision base query who join these table on the last revision.
168 175
         :return: Content/ContentRevision Query
169
-        :rtype sqlalchemy.orm.query.Query
170 176
         """
171
-        return self._session.query(Content).join(ContentRevisionRO, self.get_revision_join())
177
+        return self._session.query(Content)\
178
+            .join(ContentRevisionRO, self.get_revision_join())
172 179
 
173 180
     @classmethod
174
-    def sort_tree_items(cls, content_list: [NodeTreeItem])-> [Content]:
181
+    def sort_tree_items(
182
+        cls,
183
+        content_list: typing.List[NodeTreeItem],
184
+    )-> typing.List[NodeTreeItem]:
175 185
         news = []
176 186
         for item in content_list:
177 187
             news.append(item)
178 188
 
179
-        content_list.sort(key=cmp_to_key(compare_tree_items_for_sorting_by_type_and_name))
189
+        content_list.sort(key=cmp_to_key(
190
+            compare_tree_items_for_sorting_by_type_and_name,
191
+        ))
180 192
 
181 193
         return content_list
182 194
 
183
-
184 195
     @classmethod
185
-    def sort_content(cls, content_list: [Content])-> [Content]:
196
+    def sort_content(
197
+        cls,
198
+        content_list: typing.List[Content],
199
+    ) -> typing.List[Content]:
186 200
         content_list.sort(key=cmp_to_key(compare_content_for_sorting_by_type_and_name))
187 201
         return content_list
188 202
 
189
-    def __real_base_query(self, workspace: Workspace=None):
203
+    def __real_base_query(
204
+        self,
205
+        workspace: Workspace = None,
206
+    ) -> Query:
190 207
         result = self.get_canonical_query()
191 208
 
192 209
         # Exclude non displayable types
@@ -214,7 +231,7 @@ class ContentApi(object):
214 231
 
215 232
         return result
216 233
 
217
-    def _base_query(self, workspace: Workspace=None):
234
+    def _base_query(self, workspace: Workspace=None) -> Query:
218 235
         result = self.__real_base_query(workspace)
219 236
 
220 237
         if not self._show_deleted:
@@ -228,7 +245,10 @@ class ContentApi(object):
228 245
 
229 246
         return result
230 247
 
231
-    def __revisions_real_base_query(self, workspace: Workspace=None):
248
+    def __revisions_real_base_query(
249
+        self,
250
+        workspace: Workspace=None,
251
+    ) -> Query:
232 252
         result = self._session.query(ContentRevisionRO)
233 253
 
234 254
         # Exclude non displayable types
@@ -247,7 +267,10 @@ class ContentApi(object):
247 267
 
248 268
         return result
249 269
 
250
-    def _revisions_base_query(self, workspace: Workspace=None):
270
+    def _revisions_base_query(
271
+        self,
272
+        workspace: Workspace=None,
273
+    ) -> Query:
251 274
         result = self.__revisions_real_base_query(workspace)
252 275
 
253 276
         if not self._show_deleted:
@@ -261,7 +284,10 @@ class ContentApi(object):
261 284
 
262 285
         return result
263 286
 
264
-    def _hard_filtered_base_query(self, workspace: Workspace=None):
287
+    def _hard_filtered_base_query(
288
+        self,
289
+        workspace: Workspace=None,
290
+    ) -> Query:
265 291
         """
266 292
         If set to True, then filterign on is_deleted and is_archived will also
267 293
         filter parent properties. This is required for search() function which
@@ -293,10 +319,13 @@ class ContentApi(object):
293 319
 
294 320
         return result
295 321
 
296
-    def get_base_query(self, workspace: Workspace) -> Query:
322
+    def get_base_query(
323
+        self,
324
+        workspace: Workspace,
325
+    ) -> Query:
297 326
         return self._base_query(workspace)
298 327
 
299
-    def get_child_folders(self, parent: Content=None, workspace: Workspace=None, filter_by_allowed_content_types: list=[], removed_item_ids: list=[], allowed_node_types=None) -> [Content]:
328
+    def get_child_folders(self, parent: Content=None, workspace: Workspace=None, filter_by_allowed_content_types: list=[], removed_item_ids: list=[], allowed_node_types=None) -> typing.List[Content]:
300 329
         """
301 330
         This method returns child items (folders or items) for left bar treeview.
302 331
 
@@ -308,6 +337,9 @@ class ContentApi(object):
308 337
                For example, if you want to move a Page from a folder to another, you should show only folders that accept pages
309 338
         :return:
310 339
         """
340
+        filter_by_allowed_content_types = filter_by_allowed_content_types or []  # FDV
341
+        removed_item_ids = removed_item_ids or []  # FDV
342
+
311 343
         if not allowed_node_types:
312 344
             allowed_node_types = [ContentType.Folder]
313 345
         elif allowed_node_types==ContentType.Any:
@@ -334,7 +366,11 @@ class ContentApi(object):
334 366
         result = []
335 367
         for folder in folders:
336 368
             for allowed_content_type in filter_by_allowed_content_types:
337
-                if folder.type==ContentType.Folder and folder.properties['allowed_content'][allowed_content_type]==True:
369
+
370
+                is_folder = folder.type == ContentType.Folder
371
+                content_type__allowed = folder.properties['allowed_content'][allowed_content_type] == True
372
+
373
+                if is_folder and content_type__allowed:
338 374
                     result.append(folder)
339 375
                     break
340 376
 
@@ -638,7 +674,7 @@ class ContentApi(object):
638 674
             ),
639 675
         ))
640 676
 
641
-    def get_all(self, parent_id: int=None, content_type: str=ContentType.Any, workspace: Workspace=None) -> [Content]:
677
+    def get_all(self, parent_id: int=None, content_type: str=ContentType.Any, workspace: Workspace=None) -> typing.List[Content]:
642 678
         assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
643 679
         assert content_type is not None# DYN_REMOVE
644 680
         assert isinstance(content_type, str) # DYN_REMOVE
@@ -655,7 +691,7 @@ class ContentApi(object):
655 691
 
656 692
         return resultset.all()
657 693
 
658
-    def get_children(self, parent_id: int, content_types: list, workspace: Workspace=None) -> [Content]:
694
+    def get_children(self, parent_id: int, content_types: list, workspace: Workspace=None) -> typing.List[Content]:
659 695
         """
660 696
         Return parent_id childs of given content_types
661 697
         :param parent_id: parent id
@@ -674,7 +710,7 @@ class ContentApi(object):
674 710
         return resultset.all()
675 711
 
676 712
     # TODO find an other name to filter on is_deleted / is_archived
677
-    def get_all_with_filter(self, parent_id: int=None, content_type: str=ContentType.Any, workspace: Workspace=None) -> [Content]:
713
+    def get_all_with_filter(self, parent_id: int=None, content_type: str=ContentType.Any, workspace: Workspace=None) -> typing.List[Content]:
678 714
         assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
679 715
         assert content_type is not None# DYN_REMOVE
680 716
         assert isinstance(content_type, str) # DYN_REMOVE
@@ -692,7 +728,7 @@ class ContentApi(object):
692 728
 
693 729
         return resultset.all()
694 730
 
695
-    def get_all_without_exception(self, content_type: str, workspace: Workspace=None) -> [Content]:
731
+    def get_all_without_exception(self, content_type: str, workspace: Workspace=None) -> typing.List[Content]:
696 732
         assert content_type is not None# DYN_REMOVE
697 733
 
698 734
         resultset = self._base_query(workspace)
@@ -702,7 +738,7 @@ class ContentApi(object):
702 738
 
703 739
         return resultset.all()
704 740
 
705
-    def get_last_active(self, parent_id: int, content_type: str, workspace: Workspace=None, limit=10) -> [Content]:
741
+    def get_last_active(self, parent_id: int, content_type: str, workspace: Workspace=None, limit=10) -> typing.List[Content]:
706 742
         assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
707 743
         assert content_type is not None# DYN_REMOVE
708 744
         assert isinstance(content_type, str) # DYN_REMOVE
@@ -738,7 +774,7 @@ class ContentApi(object):
738 774
         return result
739 775
 
740 776
     def get_last_unread(self, parent_id: int, content_type: str,
741
-                        workspace: Workspace=None, limit=10) -> [Content]:
777
+                        workspace: Workspace=None, limit=10) -> typing.List[Content]:
742 778
         assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
743 779
         assert content_type is not None# DYN_REMOVE
744 780
         assert isinstance(content_type, str) # DYN_REMOVE
@@ -1109,7 +1145,7 @@ class ContentApi(object):
1109 1145
 
1110 1146
         return keywords
1111 1147
 
1112
-    def search(self, keywords: [str]) -> sqlalchemy.orm.query.Query:
1148
+    def search(self, keywords: [str]) -> Query:
1113 1149
         """
1114 1150
         :return: a sorted list of Content items
1115 1151
         """
@@ -1126,7 +1162,7 @@ class ContentApi(object):
1126 1162
 
1127 1163
         return title_keyworded_items
1128 1164
 
1129
-    def get_all_types(self) -> [ContentType]:
1165
+    def get_all_types(self) -> typing.List[ContentType]:
1130 1166
         labels = ContentType.all()
1131 1167
         content_types = []
1132 1168
         for label in labels:
@@ -1134,7 +1170,10 @@ class ContentApi(object):
1134 1170
 
1135 1171
         return ContentType.sorted(content_types)
1136 1172
 
1137
-    def exclude_unavailable(self, contents: [Content]) -> [Content]:
1173
+    def exclude_unavailable(
1174
+        self,
1175
+        contents: typing.List[Content],
1176
+    ) -> typing.List[Content]:
1138 1177
         """
1139 1178
         Update and return list with content under archived/deleted removed.
1140 1179
         :param contents: List of contents to parse

+ 11 - 3
tracim/models/data.py View File

@@ -1,4 +1,5 @@
1 1
 # -*- coding: utf-8 -*-
2
+import typing
2 3
 import datetime as datetime_root
3 4
 import json
4 5
 import os
@@ -1327,14 +1328,21 @@ class RevisionReadStatus(DeclarativeBase):
1327 1328
 
1328 1329
 class NodeTreeItem(object):
1329 1330
     """
1330
-        This class implements a model that allow to simply represents the left-panel menu items
1331
-         This model is used by dbapi but is not directly related to sqlalchemy and database
1331
+        This class implements a model that allow to simply represents
1332
+        the left-panel menu items. This model is used by dbapi but
1333
+        is not directly related to sqlalchemy and database
1332 1334
     """
1333
-    def __init__(self, node: Content, children: list('NodeTreeItem'), is_selected = False):
1335
+    def __init__(
1336
+        self,
1337
+        node: Content,
1338
+        children: typing.List['NodeTreeItem'],
1339
+        is_selected: bool = False,
1340
+    ):
1334 1341
         self.node = node
1335 1342
         self.children = children
1336 1343
         self.is_selected = is_selected
1337 1344
 
1345
+
1338 1346
 class VirtualEvent(object):
1339 1347
     @classmethod
1340 1348
     def create_from(cls, object):