comparison sat/plugins/plugin_misc_merge_requests.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents 989b622faff6
children fee60f17ebac
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT plugin for Pubsub Schemas 4 # SAT plugin for Pubsub Schemas
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 6
21 from sat.core.constants import Const as C 21 from sat.core.constants import Const as C
22 from sat.core import exceptions 22 from sat.core import exceptions
23 from twisted.internet import defer 23 from twisted.internet import defer
24 from twisted.words.protocols.jabber import jid 24 from twisted.words.protocols.jabber import jid
25 from collections import namedtuple 25 from collections import namedtuple
26 from sat.tools import utils
27 from sat.core.log import getLogger 26 from sat.core.log import getLogger
27
28 log = getLogger(__name__) 28 log = getLogger(__name__)
29 29
30 NS_MERGE_REQUESTS = 'org.salut-a-toi.merge_requests:0' 30 NS_MERGE_REQUESTS = 'org.salut-a-toi.merge_requests:0'
31 31
32 PLUGIN_INFO = { 32 PLUGIN_INFO = {
38 C.PI_MAIN: "MergeRequests", 38 C.PI_MAIN: "MergeRequests",
39 C.PI_HANDLER: "no", 39 C.PI_HANDLER: "no",
40 C.PI_DESCRIPTION: _("""Merge requests management plugin""") 40 C.PI_DESCRIPTION: _("""Merge requests management plugin""")
41 } 41 }
42 42
43 FIELD_DATA_TYPE = u'type' 43 FIELD_DATA_TYPE = 'type'
44 FIELD_DATA = u'request_data' 44 FIELD_DATA = 'request_data'
45 45
46 46
47 MergeRequestHandler = namedtuple("MergeRequestHandler", ['name', 47 MergeRequestHandler = namedtuple("MergeRequestHandler", ['name',
48 'handler', 48 'handler',
49 'data_types', 49 'data_types',
50 'short_desc', 50 'short_desc',
51 'priority']) 51 'priority'])
52 52
53 53
54 class MergeRequests(object): 54 class MergeRequests(object):
55 META_AUTHOR = u'author' 55 META_AUTHOR = 'author'
56 META_EMAIL = u'email' 56 META_EMAIL = 'email'
57 META_TIMESTAMP = u'timestamp' 57 META_TIMESTAMP = 'timestamp'
58 META_HASH = u'hash' 58 META_HASH = 'hash'
59 META_PARENT_HASH = u'parent_hash' 59 META_PARENT_HASH = 'parent_hash'
60 META_COMMIT_MSG = u'commit_msg' 60 META_COMMIT_MSG = 'commit_msg'
61 META_DIFF = u'diff' 61 META_DIFF = 'diff'
62 # index of the diff in the whole data 62 # index of the diff in the whole data
63 # needed to retrieve comments location 63 # needed to retrieve comments location
64 META_DIFF_IDX = u'diff_idx' 64 META_DIFF_IDX = 'diff_idx'
65 65
66 def __init__(self, host): 66 def __init__(self, host):
67 log.info(_(u"Merge requests plugin initialization")) 67 log.info(_("Merge requests plugin initialization"))
68 self.host = host 68 self.host = host
69 host.registerNamespace('merge_requests', NS_MERGE_REQUESTS) 69 host.registerNamespace('merge_requests', NS_MERGE_REQUESTS)
70 self._p = self.host.plugins[u"XEP-0060"] 70 self._p = self.host.plugins["XEP-0060"]
71 self._s = self.host.plugins[u"PUBSUB_SCHEMA"] 71 self._s = self.host.plugins["PUBSUB_SCHEMA"]
72 self._t = self.host.plugins[u"TICKETS"] 72 self._t = self.host.plugins["TICKETS"]
73 self._handlers = {} 73 self._handlers = {}
74 self._handlers_list = [] # handlers sorted by priority 74 self._handlers_list = [] # handlers sorted by priority
75 self._type_handlers = {} # data type => handler map 75 self._type_handlers = {} # data type => handler map
76 host.bridge.addMethod("mergeRequestsGet", ".plugin", 76 host.bridge.addMethod("mergeRequestsGet", ".plugin",
77 in_sign='ssiassa{ss}s', out_sign='(asa{ss}aaa{ss})', 77 in_sign='ssiassa{ss}s', out_sign='(asa{ss}aaa{ss})',
78 method=self._get, 78 method=self._get,
79 async=True 79 async_=True
80 ) 80 )
81 host.bridge.addMethod("mergeRequestSet", ".plugin", 81 host.bridge.addMethod("mergeRequestSet", ".plugin",
82 in_sign='ssssa{sas}ssss', out_sign='s', 82 in_sign='ssssa{sas}ssss', out_sign='s',
83 method=self._set, 83 method=self._set,
84 async=True) 84 async_=True)
85 host.bridge.addMethod("mergeRequestsSchemaGet", ".plugin", 85 host.bridge.addMethod("mergeRequestsSchemaGet", ".plugin",
86 in_sign='sss', out_sign='s', 86 in_sign='sss', out_sign='s',
87 method=utils.partial(self._s._getUISchema, 87 method=lambda service, nodeIdentifier, profile_key:
88 default_node=NS_MERGE_REQUESTS), 88 self._s._getUISchema(service,
89 async=True) 89 nodeIdentifier,
90 default_node=NS_MERGE_REQUESTS,
91 profile_key=profile_key),
92 async_=True)
90 host.bridge.addMethod("mergeRequestParseData", ".plugin", 93 host.bridge.addMethod("mergeRequestParseData", ".plugin",
91 in_sign='ss', out_sign='aa{ss}', 94 in_sign='ss', out_sign='aa{ss}',
92 method=self._parseData, 95 method=self._parseData,
93 async=True) 96 async_=True)
94 host.bridge.addMethod("mergeRequestsImport", ".plugin", 97 host.bridge.addMethod("mergeRequestsImport", ".plugin",
95 in_sign='ssssa{ss}s', out_sign='', 98 in_sign='ssssa{ss}s', out_sign='',
96 method=self._import, 99 method=self._import,
97 async=True 100 async_=True
98 ) 101 )
99 102
100 def register(self, name, handler, data_types, short_desc, priority=0): 103 def register(self, name, handler, data_types, short_desc, priority=0):
101 """register an merge request handler 104 """register an merge request handler
102 105
110 - title: title of the commit message (first line) 113 - title: title of the commit message (first line)
111 - body: body of the commit message 114 - body: body of the commit message
112 @aram data_types(list[unicode]): data types that his handler can generate or parse 115 @aram data_types(list[unicode]): data types that his handler can generate or parse
113 """ 116 """
114 if name in self._handlers: 117 if name in self._handlers:
115 raise exceptions.ConflictError(_(u"a handler with name {name} already " 118 raise exceptions.ConflictError(_("a handler with name {name} already "
116 u"exists!").format(name = name)) 119 "exists!").format(name = name))
117 self._handlers[name] = MergeRequestHandler(name, 120 self._handlers[name] = MergeRequestHandler(name,
118 handler, 121 handler,
119 data_types, 122 data_types,
120 short_desc, 123 short_desc,
121 priority) 124 priority)
122 self._handlers_list.append(name) 125 self._handlers_list.append(name)
123 self._handlers_list.sort(key=lambda name: self._handlers[name].priority) 126 self._handlers_list.sort(key=lambda name: self._handlers[name].priority)
124 if isinstance(data_types, basestring): 127 if isinstance(data_types, str):
125 data_types = [data_types] 128 data_types = [data_types]
126 for data_type in data_types: 129 for data_type in data_types:
127 if data_type in self._type_handlers: 130 if data_type in self._type_handlers:
128 log.warning(_(u'merge requests of type {type} are already handled by ' 131 log.warning(_('merge requests of type {type} are already handled by '
129 u'{old_handler}, ignoring {new_handler}').format( 132 '{old_handler}, ignoring {new_handler}').format(
130 type = data_type, 133 type = data_type,
131 old_handler = self._type_handlers[data_type].name, 134 old_handler = self._type_handlers[data_type].name,
132 new_handler = name)) 135 new_handler = name))
133 continue 136 continue
134 self._type_handlers[data_type] = self._handlers[name] 137 self._type_handlers[data_type] = self._handlers[name]
139 extra_dict['parse'] = C.bool(extra_dict['parse']) 142 extra_dict['parse'] = C.bool(extra_dict['parse'])
140 client, service, node, max_items, extra, sub_id = self._s.prepareBridgeGet( 143 client, service, node, max_items, extra, sub_id = self._s.prepareBridgeGet(
141 service, node, max_items, sub_id, extra_dict, profile_key) 144 service, node, max_items, sub_id, extra_dict, profile_key)
142 d = self.get(client, service, node or None, max_items, item_ids, sub_id or None, 145 d = self.get(client, service, node or None, max_items, item_ids, sub_id or None,
143 extra.rsm_request, extra.extra) 146 extra.rsm_request, extra.extra)
144 d.addCallback(lambda (tickets, metadata, parsed_patches): ( 147 d.addCallback(lambda tickets_metadata_parsed_patches: (
145 self._p.transItemsData((tickets, metadata)) + 148 self._p.transItemsData((tickets_metadata_parsed_patches[0], tickets_metadata_parsed_patches[1])) +
146 ([[{key: unicode(value) for key, value in p.iteritems()} 149 ([[{key: str(value) for key, value in p.items()}
147 for p in patches] for patches in parsed_patches],))) 150 for p in patches] for patches in tickets_metadata_parsed_patches[2]],)))
148 return d 151 return d
149 152
150 @defer.inlineCallbacks 153 @defer.inlineCallbacks
151 def get(self, client, service=None, node=None, max_items=None, item_ids=None, 154 def get(self, client, service=None, node=None, max_items=None, item_ids=None,
152 sub_id=None, rsm_request=None, extra=None): 155 sub_id=None, rsm_request=None, extra=None):
165 if extra is None: 168 if extra is None:
166 extra = {} 169 extra = {}
167 # XXX: Q&D way to get list for labels when displaying them, but text when we 170 # XXX: Q&D way to get list for labels when displaying them, but text when we
168 # have to modify them 171 # have to modify them
169 if C.bool(extra.get('labels_as_list', C.BOOL_FALSE)): 172 if C.bool(extra.get('labels_as_list', C.BOOL_FALSE)):
170 filters = {u'labels': self._s.textbox2ListFilter} 173 filters = {'labels': self._s.textbox2ListFilter}
171 else: 174 else:
172 filters = {} 175 filters = {}
173 tickets_xmlui, metadata = yield self._s.getDataFormItems( 176 tickets_xmlui, metadata = yield self._s.getDataFormItems(
174 client, 177 client,
175 service, 178 service,
189 parsed_data = yield self.parseData(request_type, request_data) 192 parsed_data = yield self.parseData(request_type, request_data)
190 parsed_patches.append(parsed_data) 193 parsed_patches.append(parsed_data)
191 defer.returnValue((tickets_xmlui, metadata, parsed_patches)) 194 defer.returnValue((tickets_xmlui, metadata, parsed_patches))
192 195
193 def _set(self, service, node, repository, method, values, schema=None, item_id=None, 196 def _set(self, service, node, repository, method, values, schema=None, item_id=None,
194 extra=u"", profile_key=C.PROF_KEY_NONE): 197 extra="", profile_key=C.PROF_KEY_NONE):
195 client, service, node, schema, item_id, extra = self._s.prepareBridgeSet( 198 client, service, node, schema, item_id, extra = self._s.prepareBridgeSet(
196 service, node, schema, item_id, extra, profile_key) 199 service, node, schema, item_id, extra, profile_key)
197 d = self.set(client, service, node, repository, method, values, schema, 200 d = self.set(client, service, node, repository, method, values, schema,
198 item_id or None, extra, deserialise=True) 201 item_id or None, extra, deserialise=True)
199 d.addCallback(lambda ret: ret or u'') 202 d.addCallback(lambda ret: ret or '')
200 return d 203 return d
201 204
202 @defer.inlineCallbacks 205 @defer.inlineCallbacks
203 def set(self, client, service, node, repository, method=u'auto', values=None, 206 def set(self, client, service, node, repository, method='auto', values=None,
204 schema=None, item_id=None, extra=None, deserialise=False): 207 schema=None, item_id=None, extra=None, deserialise=False):
205 """Publish a tickets 208 """Publish a tickets
206 209
207 @param service(None, jid.JID): Pubsub service to use 210 @param service(None, jid.JID): Pubsub service to use
208 @param node(unicode, None): Pubsub node to use 211 @param node(unicode, None): Pubsub node to use
219 values = {} 222 values = {}
220 update = extra.get('update', False) 223 update = extra.get('update', False)
221 if not repository and not update: 224 if not repository and not update:
222 # in case of update, we may re-user former patches data 225 # in case of update, we may re-user former patches data
223 # so repository is not mandatory 226 # so repository is not mandatory
224 raise exceptions.DataError(_(u"repository must be specified")) 227 raise exceptions.DataError(_("repository must be specified"))
225 228
226 if FIELD_DATA in values: 229 if FIELD_DATA in values:
227 raise exceptions.DataError(_(u"{field} is set by backend, you must not set " 230 raise exceptions.DataError(_("{field} is set by backend, you must not set "
228 u"it in frontend").format(field = FIELD_DATA)) 231 "it in frontend").format(field = FIELD_DATA))
229 232
230 if repository: 233 if repository:
231 if method == u'auto': 234 if method == 'auto':
232 for name in self._handlers_list: 235 for name in self._handlers_list:
233 handler = self._handlers[name].handler 236 handler = self._handlers[name].handler
234 can_handle = yield handler.check(repository) 237 can_handle = yield handler.check(repository)
235 if can_handle: 238 if can_handle:
236 log.info(_(u"{name} handler will be used").format(name=name)) 239 log.info(_("{name} handler will be used").format(name=name))
237 break 240 break
238 else: 241 else:
239 log.warning(_(u"repository {path} can't be handled by any installed " 242 log.warning(_("repository {path} can't be handled by any installed "
240 u"handler").format( 243 "handler").format(
241 path = repository)) 244 path = repository))
242 raise exceptions.NotFound(_(u"no handler for this repository has " 245 raise exceptions.NotFound(_("no handler for this repository has "
243 u"been found")) 246 "been found"))
244 else: 247 else:
245 try: 248 try:
246 handler = self._handlers[name].handler 249 handler = self._handlers[name].handler
247 except KeyError: 250 except KeyError:
248 raise exceptions.NotFound(_(u"No handler of this name found")) 251 raise exceptions.NotFound(_("No handler of this name found"))
249 252
250 data = yield handler.export(repository) 253 data = yield handler.export(repository)
251 if not data.strip(): 254 if not data.strip():
252 raise exceptions.DataError(_(u'export data is empty, do you have any ' 255 raise exceptions.DataError(_('export data is empty, do you have any '
253 u'change to send?')) 256 'change to send?'))
254 257
255 if not values.get(u'title') or not values.get(u'body'): 258 if not values.get('title') or not values.get('body'):
256 patches = yield handler.parse(data, values.get(FIELD_DATA_TYPE)) 259 patches = yield handler.parse(data, values.get(FIELD_DATA_TYPE))
257 commits_msg = patches[-1][self.META_COMMIT_MSG] 260 commits_msg = patches[-1][self.META_COMMIT_MSG]
258 msg_lines = commits_msg.splitlines() 261 msg_lines = commits_msg.splitlines()
259 if not values.get(u'title'): 262 if not values.get('title'):
260 values[u'title'] = msg_lines[0] 263 values['title'] = msg_lines[0]
261 if not values.get(u'body'): 264 if not values.get('body'):
262 ts = self.host.plugins['TEXT_SYNTAXES'] 265 ts = self.host.plugins['TEXT_SYNTAXES']
263 xhtml = yield ts.convert( 266 xhtml = yield ts.convert(
264 u'\n'.join(msg_lines[1:]), 267 '\n'.join(msg_lines[1:]),
265 syntax_from = ts.SYNTAX_TEXT, 268 syntax_from = ts.SYNTAX_TEXT,
266 syntax_to = ts.SYNTAX_XHTML, 269 syntax_to = ts.SYNTAX_XHTML,
267 profile = client.profile) 270 profile = client.profile)
268 values[u'body'] = '<div xmlns="{ns}">{xhtml}</div>'.format( 271 values['body'] = '<div xmlns="{ns}">{xhtml}</div>'.format(
269 ns=C.NS_XHTML, xhtml=xhtml) 272 ns=C.NS_XHTML, xhtml=xhtml)
270 273
271 values[FIELD_DATA] = data 274 values[FIELD_DATA] = data
272 275
273 item_id = yield self._t.set(client, service, node, values, schema, item_id, extra, 276 item_id = yield self._t.set(client, service, node, values, schema, item_id, extra,
275 defer.returnValue(item_id) 278 defer.returnValue(item_id)
276 279
277 def _parseData(self, data_type, data): 280 def _parseData(self, data_type, data):
278 d = self.parseData(data_type, data) 281 d = self.parseData(data_type, data)
279 d.addCallback(lambda parsed_patches: 282 d.addCallback(lambda parsed_patches:
280 {key: unicode(value) for key, value in parsed_patches.iteritems()}) 283 {key: str(value) for key, value in parsed_patches.items()})
281 return d 284 return d
282 285
283 def parseData(self, data_type, data): 286 def parseData(self, data_type, data):
284 """Parse a merge request data according to type 287 """Parse a merge request data according to type
285 288
290 @raise NotFound: no handler can parse this data_type 293 @raise NotFound: no handler can parse this data_type
291 """ 294 """
292 try: 295 try:
293 handler = self._type_handlers[data_type] 296 handler = self._type_handlers[data_type]
294 except KeyError: 297 except KeyError:
295 raise exceptions.NotFound(_(u'No handler can handle data type "{type}"') 298 raise exceptions.NotFound(_('No handler can handle data type "{type}"')
296 .format(type=data_type)) 299 .format(type=data_type))
297 return defer.maybeDeferred(handler.handler.parse, data, data_type) 300 return defer.maybeDeferred(handler.handler.parse, data, data_type)
298 301
299 def _import(self, repository, item_id, service=None, node=None, extra=None, 302 def _import(self, repository, item_id, service=None, node=None, extra=None,
300 profile_key=C.PROF_KEY_NONE): 303 profile_key=C.PROF_KEY_NONE):
324 data = ticket_xmlui.named_widgets[FIELD_DATA].value 327 data = ticket_xmlui.named_widgets[FIELD_DATA].value
325 data_type = ticket_xmlui.named_widgets[FIELD_DATA_TYPE].value 328 data_type = ticket_xmlui.named_widgets[FIELD_DATA_TYPE].value
326 try: 329 try:
327 handler = self._type_handlers[data_type] 330 handler = self._type_handlers[data_type]
328 except KeyError: 331 except KeyError:
329 raise exceptions.NotFound(_(u'No handler found to import {data_type}') 332 raise exceptions.NotFound(_('No handler found to import {data_type}')
330 .format(data_type=data_type)) 333 .format(data_type=data_type))
331 log.info(_(u"Importing patch [{item_id}] using {name} handler").format( 334 log.info(_("Importing patch [{item_id}] using {name} handler").format(
332 item_id = item, 335 item_id = item,
333 name = handler.name)) 336 name = handler.name))
334 yield handler.handler.import_(repository, data, data_type, item, service, node, 337 yield handler.handler.import_(repository, data, data_type, item, service, node,
335 extra) 338 extra)