Commit 0766d41f authored by Jérome Perrin's avatar Jérome Perrin

jedi WIP

parent 1f0eba38
# pylint: disable=unused-variable
from __future__ import unicode_literals from __future__ import unicode_literals
import json import json
import sys import sys
...@@ -7,7 +6,6 @@ import logging ...@@ -7,7 +6,6 @@ import logging
import inspect import inspect
logger = logging.getLogger("erp5.extension.Jedi") logger = logging.getLogger("erp5.extension.Jedi")
#logger.info('included plugin loaded')
import os import os
import jedi import jedi
...@@ -17,7 +15,8 @@ import time ...@@ -17,7 +15,8 @@ import time
last_reload_time = time.time() last_reload_time = time.time()
# increase default cache duration # increase default cache duration
jedi.settings.call_signatures_validity = 30 # XXX needed ? # XXX I'm really not sure this is needed, jedi seems fast enough.
jedi.settings.call_signatures_validity = 30
# monkey patch to disable buggy sys.path addition based on buildout. # monkey patch to disable buggy sys.path addition based on buildout.
# rdiff-backup seem to trigger a bug, but it's generally super slow and not correct for us. # rdiff-backup seem to trigger a bug, but it's generally super slow and not correct for us.
...@@ -41,18 +40,96 @@ jedi_api_project.discover_buildout_paths = dont_discover_buildout_paths ...@@ -41,18 +40,96 @@ jedi_api_project.discover_buildout_paths = dont_discover_buildout_paths
try: try:
# for local types annotations in this component # for local types annotations in this component
from erp5.portal_type import IPortalObject # pylint: disable=unused-import from erp5.portal_type import ERP5Site # pylint: disable=unused-import
except ImportError: except ImportError:
pass pass
def executeJediXXX(callback, context, arguments):
from jedi.evaluate.base_context import ContextSet
# XXX function for relaodability
def call():
return callback(context, arguments=arguments)
from jedi.evaluate.context.instance import TreeInstance
from jedi.evaluate.gradual.typing import InstanceWrapper
from jedi.evaluate.lazy_context import LazyKnownContexts
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
def makeFilterFunc(class_from_portal_type, arguments):
def filter_func(val):
if isinstance(val, TreeInstance) and val.tree_node.type == 'classdef':
logger.info(
"classdef cool => %s == %s",
val.tree_node.name.value,
class_from_portal_type)
return val.tree_node.name.value == class_from_portal_type
if isinstance(val, LazyKnownContexts) and filter_func(val.infer()):
return True
if isinstance(val, ContextSet):
return val.filter(filter_func) != NO_CONTEXTS
if isinstance(val, InstanceWrapper):
for wrapped in val.iterate():
if filter_func(wrapped):
return True
annotation_classes = val.gather_annotation_classes()
import pdb; pdb.set_trace()
return val.gather_annotation_classes().filter(filter_func)
# XXX
if str(val).count('<LazyGenericClass:') == 1:
logger.info(
"I don't know ... %s because %s in %s",
"{}@".format(class_from_portal_type) in str(val),
"{}@".format(class_from_portal_type),
val)
return "{}@".format(class_from_portal_type) in str(val)
logger.info("not found in %s", val)
return False
return filter_func
# methods returning portal types
if context.is_function():
# and 1 or context.get_function_execution(
#).function_context.name.string_name == 'newContent':
if not arguments.argument_node:
return call() # no portal_type, we'll use what's defined in the stub
# look for a "portal_type=" argument
for arg_name, arg_value in arguments.unpack():
if arg_name == 'portal_type':
try:
portal_type = iter(arg_value.infer()).next().get_safe_value()
except Exception:
logger.exception("error infering")
continue
if not isinstance(portal_type, str):
continue
logger.info(
'ahah portal_type based method with portal type=%s ...',
portal_type)
# XXX this is really horrible
original = call()
filtered = original.filter(
makeFilterFunc(portal_type.replace(' ', ''), arguments))
#original._set = frozenset(
# {x for x in original._set if class_from_portal_type in str(x)})
logger.info(
'portal_type based method, returning\n %s instead of\n %s',
filtered,
original)
return filtered
# methods returning List of portal types
# methods returning List of Brain of portal types
return call()
def makeERP5Plugin(): def makeERP5Plugin():
logger.info('making erp5 plugin') logger.info('making erp5 plugin')
from jedi.evaluate.context import ModuleContext
from jedi.evaluate.lazy_context import LazyTreeContext
from jedi.evaluate.helpers import get_str_or_none
from jedi.evaluate.gradual.stub_context import TypingModuleWrapper, StubModuleContext
from jedi.evaluate.base_context import ContextSet from jedi.evaluate.base_context import ContextSet
from jedi.parser_utils import get_cached_code_lines from jedi.parser_utils import get_cached_code_lines
from StringIO import StringIO from StringIO import StringIO
...@@ -60,8 +137,8 @@ def makeERP5Plugin(): ...@@ -60,8 +137,8 @@ def makeERP5Plugin():
class JediERP5Plugin(object): class JediERP5Plugin(object):
_cache = {} _cache = {}
def _getPortalObject(self): def _getPortalObject(self): # XXX needed ?
# type: () -> IPortalObject # type: () -> ERP5Site
from Products.ERP5.ERP5Site import getSite from Products.ERP5.ERP5Site import getSite
from Products.ERP5Type.Globals import get_request from Products.ERP5Type.Globals import get_request
from ZPublisher.BaseRequest import RequestContainer from ZPublisher.BaseRequest import RequestContainer
...@@ -75,6 +152,9 @@ def makeERP5Plugin(): ...@@ -75,6 +152,9 @@ def makeERP5Plugin():
logger.info("JediERP5Plugin registering execute") logger.info("JediERP5Plugin registering execute")
def wrapper(context, arguments): def wrapper(context, arguments):
from erp5.component.extension.Jedi import executeJediXXX as _execute
return _execute(callback, context, arguments)
def call(): def call():
return callback(context, arguments=arguments) return callback(context, arguments=arguments)
...@@ -91,7 +171,7 @@ def makeERP5Plugin(): ...@@ -91,7 +171,7 @@ def makeERP5Plugin():
if arg_name == 'portal_type': if arg_name == 'portal_type':
try: try:
portal_type = iter(arg_value.infer()).next().get_safe_value() portal_type = iter(arg_value.infer()).next().get_safe_value()
except Exception as e: except Exception:
logger.exception("error infering") logger.exception("error infering")
continue continue
if not isinstance(portal_type, str): if not isinstance(portal_type, str):
...@@ -102,6 +182,10 @@ def makeERP5Plugin(): ...@@ -102,6 +182,10 @@ def makeERP5Plugin():
# XXX this is really horrible # XXX this is really horrible
class_from_portal_type = portal_type.replace(' ', '') + '@' class_from_portal_type = portal_type.replace(' ', '') + '@'
original = call() original = call()
if 0:
filtered = ContextSet.from_sets({s for s in original})
import pdb
pdb.set_trace()
original._set = frozenset({ original._set = frozenset({
x for x in original._set if class_from_portal_type in str(x) x for x in original._set if class_from_portal_type in str(x)
}) })
...@@ -118,10 +202,11 @@ def makeERP5Plugin(): ...@@ -118,10 +202,11 @@ def makeERP5Plugin():
return wrapper return wrapper
def not_used_import_module(self, callback): def not_used_import_module(self, callback): # TODO: remove, not used
""" """
Handle ERP5 dynamic modules import. Handle ERP5 dynamic modules import.
""" """
from jedi.evaluate.context import ModuleContext, StubModuleContext
logger.info("JediERP5Plugin registering import_module") logger.info("JediERP5Plugin registering import_module")
def wrapper(evaluator, import_names, module_context, *args, **kwargs): def wrapper(evaluator, import_names, module_context, *args, **kwargs):
...@@ -350,7 +435,7 @@ def _guessType(name, context_type=None): ...@@ -350,7 +435,7 @@ def _guessType(name, context_type=None):
if name in ( if name in (
'context', 'context',
'container',): 'container',):
return 'Products.ERP5Type.Core.Folder.Folder' return 'ERP5Site'
if name == 'script': if name == 'script':
return 'Products.PythonScripts.PythonScript' return 'Products.PythonScripts.PythonScript'
if name == 'REQUEST': if name == 'REQUEST':
...@@ -403,20 +488,17 @@ def ERP5Site_getPythonSourceCodeCompletionList(self, data, REQUEST=None): ...@@ -403,20 +488,17 @@ def ERP5Site_getPythonSourceCodeCompletionList(self, data, REQUEST=None):
context_type = None context_type = None
if '_' in script_name: if '_' in script_name:
context_type = script_name.split('_')[0] context_type = script_name.split('_')[0]
if context_type == 'ERP5Site':
context_type = 'IPortalObject' if context_type not in [ti.replace(' ', '')
else: for ti in portal.portal_types.objectIds()] + [
if context_type not in [ 'ERP5Site',]:
ti.replace(' ', '') for ti in portal.portal_types.objectIds()
]:
logger.warning( logger.warning(
"context_type %s has no portal type, using Folder", "context_type %s has no portal type, using ERP5Site",
context_type) context_type)
context_type = None context_type = None
imports = "from erp5.portal_type import {}; import Products.ERP5Type.Core.Folder; import ZPublisher.HTTPRequest; import Products.PythonScripts".format( imports = "from erp5.portal_type import {}; import Products.ERP5Type.Core.Folder; import ZPublisher.HTTPRequest; import Products.PythonScripts".format(
context_type or context_type or 'ERP5Site')
'Base') # import Base to have a valid import in that case ...
type_annotation = " # type: ({}) -> None".format( type_annotation = " # type: ({}) -> None".format(
', '.join( ', '.join(
[_guessType(part, context_type) for part in signature_parts])) [_guessType(part, context_type) for part in signature_parts]))
...@@ -604,58 +686,7 @@ def SkinsTool_getStubForClass(self, class_name): ...@@ -604,58 +686,7 @@ def SkinsTool_getStubForClass(self, class_name):
return "\n".join(line_list) return "\n".join(line_list)
def TypesTool_getStub(self): # XXX useless def TypeInformation_getStub(self):
print('TypesTool_getStub start')
portal = self.getPortalObject()
sources = [TypesTool_getStubHeader(self)]
for ti in portal.portal_types.contentValues():
# XXX skip a few types that we never use directly to keep the file small.
# XXX this seems a bad idea now.
if ti.getId().split()[-1] in ('Constraint', 'Property'):
logger.info('Skipping stub generation for %s', ti.getId())
continue
try:
sources.append(TypeInformation_getStub(ti, include_header=False))
except Exception:
logger.exception('Error generating stub for %s', ti.getId())
sources.append(CatalogTool_getStub(portal))
sources.append(ERP5Site_getPortalStub(portal))
sources.append(
"Type_AnyPortalType = {Type_AnyPortalType}".format(
Type_AnyPortalType='Union[{}]'.format(
', '.join(
safe_python_identifier(ti)
for ti in portal.portal_types.contentIds()
if
not ti.endswith(' Module') # XXX we don't need modules here
))))
print('TypesTool_getStub finished')
# XXX everytime jedi sees a "import erp5.accessor_holder" line, this will load our plugin.
return "import erp5.accessor_holder\n" + "\n".join(
[line for line in sources if line != 'import erp5.accessor_holder'])
def TypesTool_getStubHeader(self):
return textwrap.dedent(
'''\
from typing import Union, List, Optional, Any, overload, Literal, TypeVar, Generic
from DateTime import DateTime
T = TypeVar('T')
class Brain(Generic[T]):
id: str
path: str
# XXX more depending on select_list or select_dict and getCatalogSearchResultKeys
def getObject(self) -> T:
...
''')
# TODO: drop include_header
def TypeInformation_getStub(self, include_header=True):
# type: (ERP5TypeInformation) -> str # type: (ERP5TypeInformation) -> str
"""returns a .pyi stub file for this portal type """returns a .pyi stub file for this portal type
...@@ -686,11 +717,14 @@ def TypeInformation_getStub(self, include_header=True): ...@@ -686,11 +717,14 @@ def TypeInformation_getStub(self, include_header=True):
parent_class = temp_class.mro()[1] parent_class = temp_class.mro()[1]
parent_class_module = parent_class.__module__ parent_class_module = parent_class.__module__
include_private = False imports = set([
imports = ['import erp5.portal_type'] # TODO: this can be a set (all imports) 'from erp5.portal_type import Type_CatalogBrain',
'from erp5.portal_type import Type_AnyPortalTypeList',
'from erp5.portal_type import Type_AnyPortalTypeCatalogBrainList',
'from typing import Union, List, Optional, Any, overload, Literal, TypeVar, Generic',
'from DateTime import DateTime.DateTime as DateTime # XXX help jedi',
])
header = "" header = ""
if include_header:
header = TypesTool_getStubHeader(self.getPortalObject().portal_types)
methods = [] methods = []
debug = "" debug = ""
method_template_template = """ {decorator}\n def {method_name}({method_args}) -> {return_type}:\n {docstring}""" method_template_template = """ {decorator}\n def {method_name}({method_args}) -> {return_type}:\n {docstring}"""
...@@ -700,10 +734,24 @@ def TypeInformation_getStub(self, include_header=True): ...@@ -700,10 +734,24 @@ def TypeInformation_getStub(self, include_header=True):
decorator='', decorator='',
method_name='getPortalType', method_name='getPortalType',
method_args="self", method_args="self",
return_type='str', return_type='Literal["{}"]'.format(self.getId()),
docstring=safe_docstring(self.getId()))) # We want to be able to infer based on the portal type named returned by x.getPortalType()
# jedi does not support Literal in this context, so add a method implementation.
# This is not really valid for a .pyi, but jedi does not care.
docstring="{}\n return '{}'".format(
safe_docstring(self.getId()), self.getId())))
# XXX debug
methods.append(
method_template_template.format(
decorator='',
method_name='reveal_portal_tye_{}'.format(
safe_python_identifier(self.getId())),
method_args='self',
return_type='',
docstring=safe_docstring("ahaha cool :)")))
imports.append('from erp5.portal_type import ERP5Site') imports.add('from erp5.portal_type import ERP5Site')
methods.append( methods.append(
method_template_template.format( method_template_template.format(
decorator='', decorator='',
...@@ -715,10 +763,11 @@ def TypeInformation_getStub(self, include_header=True): ...@@ -715,10 +763,11 @@ def TypeInformation_getStub(self, include_header=True):
# first class contain workflow and some constraints. # first class contain workflow and some constraints.
for property_name in sorted(vars(temp_class)): for property_name in sorted(vars(temp_class)):
if property_name[0] == '_' and not include_private: if property_name[0] == '_':
continue continue
property_value = getattr(temp_class, property_name) property_value = getattr(temp_class, property_name)
if isinstance(property_value, Constant.Getter): if isinstance(property_value, Constant.Getter):
# TODO: add an implementation returning the value so that jedi can infer
methods.append( methods.append(
method_template_template.format( method_template_template.format(
decorator='', decorator='',
...@@ -731,6 +780,7 @@ def TypeInformation_getStub(self, include_header=True): ...@@ -731,6 +780,7 @@ def TypeInformation_getStub(self, include_header=True):
WorkflowState.TranslatedGetter, WorkflowState.TranslatedGetter,
WorkflowState.TranslatedTitleGetter, WorkflowState.TranslatedTitleGetter,
WorkflowState.Getter)): WorkflowState.Getter)):
# TODO: docstring (with link to workflow)
methods.append( methods.append(
method_template_template.format( method_template_template.format(
decorator='', decorator='',
...@@ -739,6 +789,9 @@ def TypeInformation_getStub(self, include_header=True): ...@@ -739,6 +789,9 @@ def TypeInformation_getStub(self, include_header=True):
return_type="str", return_type="str",
docstring=safe_docstring('TODO %s' % property_value))) docstring=safe_docstring('TODO %s' % property_value)))
elif isinstance(property_value, WorkflowMethod): elif isinstance(property_value, WorkflowMethod):
# TODO: docstring (with link to workflow)
# TODO: also docstring for interaction methods (and maybe something clever so that if we
# have an interaction on _setSomething the docstring of setSomething mention it).
methods.append( methods.append(
method_template_template.format( method_template_template.format(
decorator='', decorator='',
...@@ -770,65 +823,35 @@ def TypeInformation_getStub(self, include_header=True): ...@@ -770,65 +823,35 @@ def TypeInformation_getStub(self, include_header=True):
safe_python_identifier(t) for t in allowed_content_types safe_python_identifier(t) for t in allowed_content_types
] ]
if allowed_content_types and hasattr(temp_class, 'contentValues'): if allowed_content_types and hasattr(temp_class, 'contentValues'):
if include_header:
for allowed in allowed_content_types_classes: for allowed in allowed_content_types_classes:
imports.append('from erp5.portal_type import {}'.format(allowed)) imports.add('from erp5.portal_type import {}'.format(allowed))
if len(allowed_content_types) == 1: if len(allowed_content_types) == 1:
subdocument_type = '{}'.format(allowed_content_types_classes[0]) subdocument_type = '{}'.format(allowed_content_types_classes[0])
overloads = []
else: else:
subdocument_type = 'Union[{}]'.format( subdocument_type = 'Union[{}]'.format(
', '.join(allowed_content_types_classes)) ', '.join(allowed_content_types_classes))
# TODO: getParentValue # TODO: getParentValue
# XXX: what's wrong with getSourceValue(portal_type='Organisation')
overloads = [
] # XXX this was bad idea, jedi does not supports overloads and Literal types
for method_name in ('contentValues', 'objectValues', 'searchFolder'): for method_name in ('contentValues', 'objectValues', 'searchFolder'):
return_type = 'List[{}]'.format(subdocument_type) return_type = 'List[{}]'.format(subdocument_type)
if method_name == 'searchFolder': if method_name == 'searchFolder':
return_type = 'List[Brain[{}]]'.format(subdocument_type) return_type = 'List[Type_CatalogBrain[{}]]'.format(subdocument_type)
if len(allowed_content_types) > 1: if len(allowed_content_types) > 1:
# not correct but it makes jedi complete well when portal_type='one' # not correct but it makes jedi complete well when portal_type='one'
return_type = 'Union[{}]'.format( return_type = 'Union[{}]'.format(
', '.join(( ', '.join((
'List[Brain[{}]]'.format(t) 'List[Type_CatalogBrain[{}]]'.format(t)
for t in allowed_content_types_classes))) for t in allowed_content_types_classes)))
methods.append( methods.append(
method_template_template.format( method_template_template.format(
decorator='', decorator='',
method_name=method_name, method_name=method_name,
method_args="self", # TODO method_args="self",
return_type=return_type,
docstring=safe_docstring(
getattr(getattr(temp_class, method_name), '__doc__', None))))
for overload in overloads:
return_type = 'List[{}]'.format(safe_python_identifier(overload))
if method_name == 'searchFolder':
return_type = 'List[Brain[{}]]'.format(
safe_python_identifier(overload))
methods.append(
method_template_template.format(
decorator='@overload',
method_name=method_name,
method_args="self, portal_type:Literal['{}']".format(
overload), # TODO
return_type=return_type, return_type=return_type,
docstring=safe_docstring(
getattr(getattr(temp_class, method_name), '__doc__',
None))))
for overload in overloads:
methods.append(
method_template_template.format(
decorator='@overload',
method_name='newContent',
method_args="self, portal_type:Literal['{}']".format(
overload), # TODO
return_type=safe_python_identifier(overload),
docstring=safe_docstring( docstring=safe_docstring(
getattr(getattr(temp_class, method_name), '__doc__', None)))) getattr(getattr(temp_class, method_name), '__doc__', None))))
methods.append( methods.append(
method_template_template.format( method_template_template.format(
decorator='', decorator='',
...@@ -840,7 +863,11 @@ def TypeInformation_getStub(self, include_header=True): ...@@ -840,7 +863,11 @@ def TypeInformation_getStub(self, include_header=True):
# getattr, getitem and other Zope.OFS alais returns an instance of allowed content types. # getattr, getitem and other Zope.OFS alais returns an instance of allowed content types.
# so that portal.person_module['1'] is a person # so that portal.person_module['1'] is a person
for method_name in ('__getattr__', '__getitem__', '_getOb', 'get'): for method_name in (
'__getattr__',
'__getitem__',
'_getOb',
'get',):
methods.append( methods.append(
method_template_template.format( method_template_template.format(
decorator='', decorator='',
...@@ -851,7 +878,8 @@ def TypeInformation_getStub(self, include_header=True): ...@@ -851,7 +878,8 @@ def TypeInformation_getStub(self, include_header=True):
for identity_method in ( for identity_method in (
'getObject', 'getObject',
'asContext',): 'asContext',
'__of__',):
method = getattr(temp_class, identity_method, None) method = getattr(temp_class, identity_method, None)
if method is not None: if method is not None:
methods.append( methods.append(
...@@ -868,40 +896,29 @@ def TypeInformation_getStub(self, include_header=True): ...@@ -868,40 +896,29 @@ def TypeInformation_getStub(self, include_header=True):
parent_class=safe_python_identifier(parent_class.__name__)) parent_class=safe_python_identifier(parent_class.__name__))
base_classes = [parent_class_alias] base_classes = [parent_class_alias]
for pc in temp_class.mro(): for pc in temp_class.mro():
#debug += "\n# parent class: -> from {} import {}".format( pc.__module__, pc.__name__)
if pc.__module__ == 'erp5.accessor_holder.property_sheet': if pc.__module__ == 'erp5.accessor_holder.property_sheet':
# XXX fake name for property sheets # Fake name for property sheets
prefixed_class_name = 'property_sheet_{}'.format( prefixed_class_name = 'property_sheet_{}'.format(
safe_python_identifier(pc.__name__)) safe_python_identifier(pc.__name__))
imports.append( imports.add(
'from erp5.accessor_holder import {} as {}'.format( 'from erp5.accessor_holder import {} as {}'.format(
safe_python_identifier(pc.__name__), prefixed_class_name)) safe_python_identifier(pc.__name__), prefixed_class_name))
base_classes.append(prefixed_class_name) base_classes.append(prefixed_class_name)
# XXX fake name for skins # Fake name for skins
prefixed_class_name = 'skins_tool_{}'.format( prefixed_class_name = 'skins_tool_{}'.format(
safe_python_identifier(pc.__name__)) safe_python_identifier(pc.__name__))
imports.append( imports.add(
'from erp5.skins_tool import {} as {}'.format( 'from erp5.skins_tool import {} as {}'.format(
safe_python_identifier(pc.__name__), prefixed_class_name)) safe_python_identifier(pc.__name__), prefixed_class_name))
base_classes.append(prefixed_class_name) base_classes.append(prefixed_class_name)
# everything can use ERP5Site_ skins # everything can use ERP5Site_ skins
imports.append('from erp5.skins_tool import ERP5Site as skins_tool_ERP5Site') imports.add('from erp5.skins_tool import ERP5Site as skins_tool_ERP5Site')
base_classes.append('skins_tool_ERP5Site') base_classes.append('skins_tool_ERP5Site')
base_classes.append(prefixed_class_name) base_classes.append(prefixed_class_name)
# XXX special methods for some tools
if self.getId() == 'Simulation Tool':
methods.append(
method_template_template.format(
decorator='',
method_name='getInventoryList',
method_args="self", # TODO
return_type='erp5.portal_type.Person',
docstring=safe_docstring('TODO =)')))
class_template = textwrap.dedent( class_template = textwrap.dedent(
"""\ """\
{header} {header}
...@@ -926,7 +943,7 @@ def TypeInformation_getStub(self, include_header=True): ...@@ -926,7 +943,7 @@ def TypeInformation_getStub(self, include_header=True):
type_url=self.absolute_url()) type_url=self.absolute_url())
return class_template.format( return class_template.format(
imports="\n".join(imports), imports="\n".join(sorted(imports)),
header=header, header=header,
docstring=safe_docstring(docstring), docstring=safe_docstring(docstring),
class_name=safe_python_identifier(temp_class.__name__), class_name=safe_python_identifier(temp_class.__name__),
...@@ -977,12 +994,17 @@ def PropertySheet_getStub(self): ...@@ -977,12 +994,17 @@ def PropertySheet_getStub(self):
imports = [ imports = [
'from typing import Optional, List, Any', 'from typing import Optional, List, Any',
'from DateTime import DateTime', 'from DateTime import DateTime',
'from erp5.portal_type import Type_AnyPortalType' 'from erp5.portal_type import Type_CatalogBrain',
'from erp5.portal_type import Type_AnyPortalType',
'from erp5.portal_type import Type_AnyPortalTypeList'
] ]
method_template_template = """ def {method_name}({method_args}) -> {return_type}:\n {docstring}""" method_template_template = """ def {method_name}({method_args}) -> {return_type}:\n {docstring}"""
# debug
methods.append( methods.append(
method_template_template.format( method_template_template.format(
method_name='cool{}'.format(safe_python_identifier(self.getId())), method_name='reveal_property_sheet_{}'.format(
safe_python_identifier(self.getId())),
method_args='self', method_args='self',
return_type='str', return_type='str',
docstring=safe_docstring("ahaha cool :)"))) docstring=safe_docstring("ahaha cool :)")))
...@@ -997,8 +1019,8 @@ def PropertySheet_getStub(self): ...@@ -997,8 +1019,8 @@ def PropertySheet_getStub(self):
'string': 'str', 'string': 'str',
'boolean': 'bool', 'boolean': 'bool',
'data': 'bytes', 'data': 'bytes',
'date': # XXX jedi does not understand DateTime dynamic name, so use "real name"
'DateTime.DateTime', # XXX jedi does not understand DateTime dynamic name, so use "real name" 'date': 'DateTime.DateTime',
'int': 'int', 'int': 'int',
'long': 'int', # ??? 'long': 'int', # ???
'lines': 'List[str]', 'lines': 'List[str]',
...@@ -1110,14 +1132,14 @@ def PropertySheet_getStub(self): ...@@ -1110,14 +1132,14 @@ def PropertySheet_getStub(self):
method_name='get{}Value'.format( method_name='get{}Value'.format(
convertToUpperCase(category_value.getId())), convertToUpperCase(category_value.getId())),
method_args='self', method_args='self',
return_type='Optional[Type_AnyPortalType]', return_type='Type_AnyPortalType',
docstring=docstring)) docstring=docstring))
methods.append( methods.append(
method_template_template.format( method_template_template.format(
method_name='get{}ValueList'.format( method_name='get{}ValueList'.format(
convertToUpperCase(category_value.getId())), convertToUpperCase(category_value.getId())),
method_args='self', method_args='self',
return_type='List[Type_AnyPortalType]', return_type='Type_AnyPortalTypeList',
docstring=docstring)) docstring=docstring))
methods.append( methods.append(
method_template_template.format( method_template_template.format(
...@@ -1130,14 +1152,14 @@ def PropertySheet_getStub(self): ...@@ -1130,14 +1152,14 @@ def PropertySheet_getStub(self):
method_template_template.format( method_template_template.format(
method_name='set{}Value'.format( method_name='set{}Value'.format(
convertToUpperCase(category_value.getId())), convertToUpperCase(category_value.getId())),
method_args='self, value: Category', method_args='self, value: Base',
return_type='None', return_type='None',
docstring=docstring)) docstring=docstring))
methods.append( methods.append(
method_template_template.format( method_template_template.format(
method_name='set{}ValueList'.format( method_name='set{}ValueList'.format(
convertToUpperCase(category_value.getId())), convertToUpperCase(category_value.getId())),
method_args='self, value_list: List[Category]', method_args='self, value_list: List[Base]',
return_type='None', return_type='None',
docstring=docstring)) docstring=docstring))
...@@ -1158,7 +1180,6 @@ from Products.ERP5.ERP5Site import ERP5Site # pylint: disable=unused-import ...@@ -1158,7 +1180,6 @@ from Products.ERP5.ERP5Site import ERP5Site # pylint: disable=unused-import
def ERP5Site_getPortalStub(self): def ERP5Site_getPortalStub(self):
# type: (ERP5Site) -> str # type: (ERP5Site) -> str
# TODO:
module_stub_template = textwrap.dedent( module_stub_template = textwrap.dedent(
''' '''
@property @property
...@@ -1182,68 +1203,36 @@ def ERP5Site_getPortalStub(self): ...@@ -1182,68 +1203,36 @@ def ERP5Site_getPortalStub(self):
module_class_name=safe_python_identifier( module_class_name=safe_python_identifier(
m.getPortalType())).splitlines()) m.getPortalType())).splitlines())
elif m.getId().startswith('portal_'): else:
tool_class = safe_python_identifier(m.__class__.__name__) tool_class = safe_python_identifier(m.__class__.__name__)
tool_import = 'from {} import {}'.format( tool_import = 'from {} import {}'.format(
m.__class__.__module__, m.__class__.__module__, tool_class)
tool_class,)
if m.getId() == 'portal_catalog': if m.getId() == 'portal_catalog':
tool_class = 'ICatalogTool' # XXX these I prefix are stupid tool_class = 'ICatalogTool' # XXX these I-prefix are stupid
tool_import = 'from erp5.portal_type import ICatalogTool' tool_import = 'from erp5.portal_type import ICatalogTool'
elif m.getId() == 'portal_simulation':
tool_class = 'ISimulationTool' # XXX these I-prefix are stupid
tool_import = 'from erp5.portal_type import ISimulationTool'
source.extend( source.extend(
tool_stub_template.format( tool_stub_template.format(
tool_id=m.getId(), tool_class=tool_class, tool_id=m.getId(), tool_class=tool_class,
tool_import=tool_import).splitlines(), tool_import=tool_import).splitlines())
)
# TODO: tools with at least base categories for CategoryTool # TODO: tools with at least base categories for CategoryTool
return textwrap.dedent( return textwrap.dedent(
''' '''
from Products.ERP5Site.ERP5Site import ERP5Site as ERP5Site_parent_ERP5Site from Products.ERP5Site.ERP5Site import ERP5Site as ERP5Site_parent_ERP5Site
from erp5.skins_tool import ERP5Site as skins_tool_ERP5Site from erp5.skins_tool import ERP5Site as skins_tool_ERP5Site
class ERP5Site(ERP5Site_parent_ERP5Site, skins_tool_ERP5Site): from erp5.skins_tool import Base as skins_tool_Base
class ERP5Site(ERP5Site_parent_ERP5Site, skins_tool_ERP5Site, skins_tool_Base):
{} {}
def getPortalObject(self):
return self
''').format('\n '.join(source)) ''').format('\n '.join(source))
def CatalogTool_getStub(self):
portal = self.getPortalObject()
brain_class_list = []
imports = []
brain_template = textwrap.dedent(
'''\
from typing import TypeVar, Generic, List, Union
T = TypeVar('T')
class Brain(Generic[T]):
id: str
path: str
# XXX more depending on select_{list|dict} and getCatalogSearchResultKeys
def getObject(self) -> T:
...
''')
for ti in portal.portal_types.contentValues():
type_class = safe_python_identifier(ti.getId())
# imports are already imported
# imports.append('from erp5.portal_type import {}'.format(type_class))
brain_class_list.append('List[Brain[{}]]'.format(type_class))
all_types = ', '.join(brain_class_list)
return textwrap.dedent(
'''\
from Products.ERP5Catalog.Tool.ERP5CatalogTool import ERP5CatalogTool
{brain_template}
CatalogToolAllTypes = Union[{all_types}]
class ICatalogTool(ERP5CatalogTool):
def searchResults(self) -> CatalogToolAllTypes:
"""Search Catalog"""
def __call__(self) -> CatalogToolAllTypes:
"""Search Catalog"""
''').format(
brain_template=brain_template, all_types=all_types)
def ERP5Site_dumpModuleCode(self, component_or_script=None): def ERP5Site_dumpModuleCode(self, component_or_script=None):
"""Save code in filesystem for jedi to use it. """Save code in filesystem for jedi to use it.
...@@ -1254,7 +1243,10 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None): ...@@ -1254,7 +1243,10 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
if not os.path.exists(path): if not os.path.exists(path):
os.mkdir(path, 0o700) os.mkdir(path, 0o700)
portal = self.getPortalObject() # type: 'erp5.portal_type.ERP5Site' # this import is for type annotation, but pylint does not understand this.
import erp5.portal_type # pylint: disable=unused-variable
portal = self.getPortalObject() # type: erp5.portal_type.ERP5Site
module_dir = '/tmp/ahaha/erp5/' # TODO module_dir = '/tmp/ahaha/erp5/' # TODO
mkdir_p(module_dir) mkdir_p(module_dir)
...@@ -1268,10 +1260,10 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None): ...@@ -1268,10 +1260,10 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
'skins_tool', 'skins_tool',
'component',): 'component',):
erp5__init__f.write('from . import {module}\n'.format(module=module)) erp5__init__f.write('from . import {module}\n'.format(module=module))
# TODO: accessor_holder real name ?
# TODO: component versions ?
mkdir_p(os.path.join(module_dir, module)) mkdir_p(os.path.join(module_dir, module))
if module == 'portal_type': if module == 'portal_type':
# portal types
all_portal_type_class_names = []
with open( with open(
os.path.join( os.path.join(
module_dir, module_dir,
...@@ -1280,6 +1272,7 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None): ...@@ -1280,6 +1272,7 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
'w',) as module_f: 'w',) as module_f:
for ti in portal.portal_types.contentValues(): for ti in portal.portal_types.contentValues():
class_name = safe_python_identifier(ti.getId()) class_name = safe_python_identifier(ti.getId())
all_portal_type_class_names.append(class_name)
module_f.write( module_f.write(
'from .{class_name} import {class_name}\n'.format( 'from .{class_name} import {class_name}\n'.format(
class_name=class_name)) class_name=class_name))
...@@ -1293,8 +1286,42 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None): ...@@ -1293,8 +1286,42 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
) as type_information_f: ) as type_information_f:
type_information_f.write( type_information_f.write(
ti.TypeInformation_getStub().encode('utf-8')) ti.TypeInformation_getStub().encode('utf-8'))
# tools XXX don't do here but in TypeInformation_getStub ?
#module_f.write('from .CatalogTool import CatalogTool') # portal type groups ( useful ? used in Simulation Tool only )
portal_types_by_group = defaultdict(list)
for ti_for_group in portal.portal_types.contentValues():
for group in ti_for_group.getTypeGroupList():
portal_types_by_group[group].append(
safe_python_identifier(ti_for_group.getId()))
for group, portal_type_class_list in portal_types_by_group.items():
group_class = 'Group_{}'.format(group)
module_f.write(
'from .{} import {}\n'.format(group_class, group_class))
with open(
os.path.join(
module_dir,
module,
'{}.pyi'.format(group_class),),
'w',
) as group_f:
group_f.write(
textwrap.dedent(
'''
{imports}
class {group_class}({bases}):
"""All portal types of group {group}.
"""
''').format(
imports='\n'.join(
'from erp5.portal_type import {}'.format(
portal_type_class)
for portal_type_class in portal_type_class_list),
group_class=group_class,
bases=', '.join(portal_type_class_list),
group=group))
# tools with extra type annotations
module_f.write('from .ICatalogTool import ICatalogTool\n') module_f.write('from .ICatalogTool import ICatalogTool\n')
with open( with open(
os.path.join( os.path.join(
...@@ -1303,15 +1330,42 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None): ...@@ -1303,15 +1330,42 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
'ICatalogTool.pyi',), 'ICatalogTool.pyi',),
'w',) as portal_f: 'w',) as portal_f:
portal_f.write( portal_f.write(
CatalogTool_getStub(self.getPortalObject().portal_catalog)) textwrap.dedent(
module_f.write('from .SimulationTool import SimulationTool\n') '''
# TOODO: simulation tool from Products.ERP5Catalog.Tool.ERP5CatalogTool import ERP5CatalogTool
# XXX CatalogTool itself has a portal type
from erp5.portal_type import Type_AnyPortalTypeCatalogBrainList
class ICatalogTool(ERP5CatalogTool):
def searchResults(self) -> Type_AnyPortalTypeCatalogBrainList:
"""Search Catalog"""
def __call__(self) -> Type_AnyPortalTypeCatalogBrainList:
"""Search Catalog"""
'''))
module_f.write('from .ISimulationTool import ISimulationTool\n')
with open(
os.path.join(
module_dir,
module,
'ISimulationTool.pyi',),
'w',) as portal_f:
portal_f.write(
textwrap.dedent(
'''
from erp5.portal_type import SimulationTool
from erp5.portal_type import Type_AnyPortalTypeInventoryListBrainList
class ISimulationTool(SimulationTool):
def getInventoryList() -> Type_AnyPortalTypeInventoryListBrainList:
...
'''))
# portal object # portal object
module_f.write('from .ERP5Site import ERP5Site\n') module_f.write('from .ERP5Site import ERP5Site\n')
module_f.write(
'from .ERP5Site import ERP5Site as IPortalObject\n'
) # XXX what name for portal ? -> ERP5Site !
with open( with open(
os.path.join( os.path.join(
module_dir, module_dir,
...@@ -1319,6 +1373,108 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None): ...@@ -1319,6 +1373,108 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
'ERP5Site.pyi',), 'ERP5Site.pyi',),
'w',) as portal_f: 'w',) as portal_f:
portal_f.write(ERP5Site_getPortalStub(self.getPortalObject())) portal_f.write(ERP5Site_getPortalStub(self.getPortalObject()))
# some type helpers
module_f.write('from .Type_CatalogBrain import Type_CatalogBrain\n')
with open(
os.path.join(
module_dir,
module,
'Type_CatalogBrain.pyi',),
'w',) as catalog_brain_f:
catalog_brain_f.write(
textwrap.dedent(
'''
from typing import TypeVar, Generic
T = TypeVar('T')
class Type_CatalogBrain(Generic[T]):
id: str
path: str
def getObject(self) -> T:
...
'''))
module_f.write(
'from .Type_InventoryListBrain import Type_InventoryListBrain\n')
with open(
os.path.join(
module_dir,
module,
'Type_InventoryListBrain.pyi',),
'w',
) as catalog_brain_f:
catalog_brain_f.write(
textwrap.dedent(
'''
from typing import TypeVar, Generic
from erp5.component.extension.InventoryBrain import InventoryListBrain
from DateTime import DateTime.DateTime as DateTime
from erp5.portal_type import Group_node
from erp5.portal_type import Group_resource
T = TypeVar('T')
class Type_InventoryListBrain(Generic[T], InventoryListBrain):
node_uid: int
mirror_node_uid: int
section_uid: int
mirror_section_uid: int
function_uid: int
project_uid: int
function_uid: int
funding_uid: int
ledger_uid: int
payment_request_uid: int
node_value: Group_node
mirror_node_value: Group_node
section_value: Group_node
mirror_section_value: Group_node
resource_value: Group_resource
date: DateTime
mirror_date: DateTime
variation_text: str
sub_variation_text: str
simulation_state: str
inventory: float
total_price: float
path: str
stock_uid: uid
def getObject(self) -> T:
...
'''))
module_f.write('from typing import List, Union\n')
module_f.write(
'Type_AnyPortalType = Union[\n {}]\n'.format(
',\n '.join(
'{}'.format(portal_type_class)
for portal_type_class in all_portal_type_class_names),
))
module_f.write(
'Type_AnyPortalTypeList = Union[\n {}]\n'.format(
',\n '.join(
'List[{}]'.format(portal_type_class)
for portal_type_class in all_portal_type_class_names)))
module_f.write(
'Type_AnyPortalTypeCatalogBrainList = Union[\n {}]\n'.format(
',\n '.join(
'List[Type_CatalogBrain[{}]]'.format(portal_type_class)
for portal_type_class in all_portal_type_class_names),
))
module_f.write(
'Type_AnyPortalTypeInventoryListBrainList = Union[\n {}]\n'
.format(
',\n '.join(
'List[Type_InventoryListBrain[{}]]'.format(
portal_type_class)
for portal_type_class in all_portal_type_class_names),
))
elif module == 'accessor_holder': elif module == 'accessor_holder':
# TODO: real path is accessor_holder.something !? # TODO: real path is accessor_holder.something !?
with open( with open(
...@@ -1359,6 +1515,7 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None): ...@@ -1359,6 +1515,7 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
skins_tool, skins_tool,
class_name,).encode('utf-8')) class_name,).encode('utf-8'))
elif module == 'component': elif module == 'component':
# TODO: component versions ?
module_to_component_portal_type_mapping = { module_to_component_portal_type_mapping = {
'test': 'Test Component', 'test': 'Test Component',
'document': 'Document Component', 'document': 'Document Component',
......
...@@ -45,7 +45,13 @@ ...@@ -45,7 +45,13 @@
<item> <item>
<key> <string>text_content_warning_message</string> </key> <key> <string>text_content_warning_message</string> </key>
<value> <value>
<tuple/> <tuple>
<string>W: 58, 2: Reimport \'ContextSet\' (imported line 49) (reimported)</string>
<string>W: 81, 8: Unreachable code (unreachable)</string>
<string>W: 76, 8: Unused variable \'annotation_classes\' (unused-variable)</string>
<string>W:158, 8: Unreachable code (unreachable)</string>
<string>W:186, 16: Unused variable \'filtered\' (unused-variable)</string>
</tuple>
</value> </value>
</item> </item>
<item> <item>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment