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
import json
import sys
......@@ -7,7 +6,6 @@ import logging
import inspect
logger = logging.getLogger("erp5.extension.Jedi")
#logger.info('included plugin loaded')
import os
import jedi
......@@ -17,7 +15,8 @@ import time
last_reload_time = time.time()
# 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.
# 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
try:
# 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:
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():
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.parser_utils import get_cached_code_lines
from StringIO import StringIO
......@@ -60,8 +137,8 @@ def makeERP5Plugin():
class JediERP5Plugin(object):
_cache = {}
def _getPortalObject(self):
# type: () -> IPortalObject
def _getPortalObject(self): # XXX needed ?
# type: () -> ERP5Site
from Products.ERP5.ERP5Site import getSite
from Products.ERP5Type.Globals import get_request
from ZPublisher.BaseRequest import RequestContainer
......@@ -75,6 +152,9 @@ def makeERP5Plugin():
logger.info("JediERP5Plugin registering execute")
def wrapper(context, arguments):
from erp5.component.extension.Jedi import executeJediXXX as _execute
return _execute(callback, context, arguments)
def call():
return callback(context, arguments=arguments)
......@@ -91,7 +171,7 @@ def makeERP5Plugin():
if arg_name == 'portal_type':
try:
portal_type = iter(arg_value.infer()).next().get_safe_value()
except Exception as e:
except Exception:
logger.exception("error infering")
continue
if not isinstance(portal_type, str):
......@@ -102,6 +182,10 @@ def makeERP5Plugin():
# XXX this is really horrible
class_from_portal_type = portal_type.replace(' ', '') + '@'
original = call()
if 0:
filtered = ContextSet.from_sets({s for s in original})
import pdb
pdb.set_trace()
original._set = frozenset({
x for x in original._set if class_from_portal_type in str(x)
})
......@@ -118,10 +202,11 @@ def makeERP5Plugin():
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.
"""
from jedi.evaluate.context import ModuleContext, StubModuleContext
logger.info("JediERP5Plugin registering import_module")
def wrapper(evaluator, import_names, module_context, *args, **kwargs):
......@@ -350,7 +435,7 @@ def _guessType(name, context_type=None):
if name in (
'context',
'container',):
return 'Products.ERP5Type.Core.Folder.Folder'
return 'ERP5Site'
if name == 'script':
return 'Products.PythonScripts.PythonScript'
if name == 'REQUEST':
......@@ -403,20 +488,17 @@ def ERP5Site_getPythonSourceCodeCompletionList(self, data, REQUEST=None):
context_type = None
if '_' in script_name:
context_type = script_name.split('_')[0]
if context_type == 'ERP5Site':
context_type = 'IPortalObject'
else:
if context_type not in [
ti.replace(' ', '') for ti in portal.portal_types.objectIds()
]:
if context_type not in [ti.replace(' ', '')
for ti in portal.portal_types.objectIds()] + [
'ERP5Site',]:
logger.warning(
"context_type %s has no portal type, using Folder",
"context_type %s has no portal type, using ERP5Site",
context_type)
context_type = None
imports = "from erp5.portal_type import {}; import Products.ERP5Type.Core.Folder; import ZPublisher.HTTPRequest; import Products.PythonScripts".format(
context_type or
'Base') # import Base to have a valid import in that case ...
context_type or 'ERP5Site')
type_annotation = " # type: ({}) -> None".format(
', '.join(
[_guessType(part, context_type) for part in signature_parts]))
......@@ -604,58 +686,7 @@ def SkinsTool_getStubForClass(self, class_name):
return "\n".join(line_list)
def TypesTool_getStub(self): # XXX useless
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):
def TypeInformation_getStub(self):
# type: (ERP5TypeInformation) -> str
"""returns a .pyi stub file for this portal type
......@@ -686,11 +717,14 @@ def TypeInformation_getStub(self, include_header=True):
parent_class = temp_class.mro()[1]
parent_class_module = parent_class.__module__
include_private = False
imports = ['import erp5.portal_type'] # TODO: this can be a set (all imports)
imports = set([
'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 = ""
if include_header:
header = TypesTool_getStubHeader(self.getPortalObject().portal_types)
methods = []
debug = ""
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):
decorator='',
method_name='getPortalType',
method_args="self",
return_type='str',
docstring=safe_docstring(self.getId())))
return_type='Literal["{}"]'.format(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(
method_template_template.format(
decorator='',
......@@ -715,10 +763,11 @@ def TypeInformation_getStub(self, include_header=True):
# first class contain workflow and some constraints.
for property_name in sorted(vars(temp_class)):
if property_name[0] == '_' and not include_private:
if property_name[0] == '_':
continue
property_value = getattr(temp_class, property_name)
if isinstance(property_value, Constant.Getter):
# TODO: add an implementation returning the value so that jedi can infer
methods.append(
method_template_template.format(
decorator='',
......@@ -731,6 +780,7 @@ def TypeInformation_getStub(self, include_header=True):
WorkflowState.TranslatedGetter,
WorkflowState.TranslatedTitleGetter,
WorkflowState.Getter)):
# TODO: docstring (with link to workflow)
methods.append(
method_template_template.format(
decorator='',
......@@ -739,6 +789,9 @@ def TypeInformation_getStub(self, include_header=True):
return_type="str",
docstring=safe_docstring('TODO %s' % property_value)))
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(
method_template_template.format(
decorator='',
......@@ -770,65 +823,35 @@ def TypeInformation_getStub(self, include_header=True):
safe_python_identifier(t) for t in allowed_content_types
]
if allowed_content_types and hasattr(temp_class, 'contentValues'):
if include_header:
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:
subdocument_type = '{}'.format(allowed_content_types_classes[0])
overloads = []
else:
subdocument_type = 'Union[{}]'.format(
', '.join(allowed_content_types_classes))
# 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'):
return_type = 'List[{}]'.format(subdocument_type)
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:
# not correct but it makes jedi complete well when portal_type='one'
return_type = 'Union[{}]'.format(
', '.join((
'List[Brain[{}]]'.format(t)
'List[Type_CatalogBrain[{}]]'.format(t)
for t in allowed_content_types_classes)))
methods.append(
method_template_template.format(
decorator='',
method_name=method_name,
method_args="self", # TODO
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
method_args="self",
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(
getattr(getattr(temp_class, method_name), '__doc__', None))))
methods.append(
method_template_template.format(
decorator='',
......@@ -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.
# 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(
method_template_template.format(
decorator='',
......@@ -851,7 +878,8 @@ def TypeInformation_getStub(self, include_header=True):
for identity_method in (
'getObject',
'asContext',):
'asContext',
'__of__',):
method = getattr(temp_class, identity_method, None)
if method is not None:
methods.append(
......@@ -868,40 +896,29 @@ def TypeInformation_getStub(self, include_header=True):
parent_class=safe_python_identifier(parent_class.__name__))
base_classes = [parent_class_alias]
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':
# XXX fake name for property sheets
# Fake name for property sheets
prefixed_class_name = 'property_sheet_{}'.format(
safe_python_identifier(pc.__name__))
imports.append(
imports.add(
'from erp5.accessor_holder import {} as {}'.format(
safe_python_identifier(pc.__name__), prefixed_class_name))
base_classes.append(prefixed_class_name)
# XXX fake name for skins
# Fake name for skins
prefixed_class_name = 'skins_tool_{}'.format(
safe_python_identifier(pc.__name__))
imports.append(
imports.add(
'from erp5.skins_tool import {} as {}'.format(
safe_python_identifier(pc.__name__), prefixed_class_name))
base_classes.append(prefixed_class_name)
# 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(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(
"""\
{header}
......@@ -926,7 +943,7 @@ def TypeInformation_getStub(self, include_header=True):
type_url=self.absolute_url())
return class_template.format(
imports="\n".join(imports),
imports="\n".join(sorted(imports)),
header=header,
docstring=safe_docstring(docstring),
class_name=safe_python_identifier(temp_class.__name__),
......@@ -977,12 +994,17 @@ def PropertySheet_getStub(self):
imports = [
'from typing import Optional, List, Any',
'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}"""
# debug
methods.append(
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',
return_type='str',
docstring=safe_docstring("ahaha cool :)")))
......@@ -997,8 +1019,8 @@ def PropertySheet_getStub(self):
'string': 'str',
'boolean': 'bool',
'data': 'bytes',
'date':
'DateTime.DateTime', # XXX jedi does not understand DateTime dynamic name, so use "real name"
# XXX jedi does not understand DateTime dynamic name, so use "real name"
'date': 'DateTime.DateTime',
'int': 'int',
'long': 'int', # ???
'lines': 'List[str]',
......@@ -1110,14 +1132,14 @@ def PropertySheet_getStub(self):
method_name='get{}Value'.format(
convertToUpperCase(category_value.getId())),
method_args='self',
return_type='Optional[Type_AnyPortalType]',
return_type='Type_AnyPortalType',
docstring=docstring))
methods.append(
method_template_template.format(
method_name='get{}ValueList'.format(
convertToUpperCase(category_value.getId())),
method_args='self',
return_type='List[Type_AnyPortalType]',
return_type='Type_AnyPortalTypeList',
docstring=docstring))
methods.append(
method_template_template.format(
......@@ -1130,14 +1152,14 @@ def PropertySheet_getStub(self):
method_template_template.format(
method_name='set{}Value'.format(
convertToUpperCase(category_value.getId())),
method_args='self, value: Category',
method_args='self, value: Base',
return_type='None',
docstring=docstring))
methods.append(
method_template_template.format(
method_name='set{}ValueList'.format(
convertToUpperCase(category_value.getId())),
method_args='self, value_list: List[Category]',
method_args='self, value_list: List[Base]',
return_type='None',
docstring=docstring))
......@@ -1158,7 +1180,6 @@ from Products.ERP5.ERP5Site import ERP5Site # pylint: disable=unused-import
def ERP5Site_getPortalStub(self):
# type: (ERP5Site) -> str
# TODO:
module_stub_template = textwrap.dedent(
'''
@property
......@@ -1182,68 +1203,36 @@ def ERP5Site_getPortalStub(self):
module_class_name=safe_python_identifier(
m.getPortalType())).splitlines())
elif m.getId().startswith('portal_'):
else:
tool_class = safe_python_identifier(m.__class__.__name__)
tool_import = 'from {} import {}'.format(
m.__class__.__module__,
tool_class,)
m.__class__.__module__, tool_class)
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'
elif m.getId() == 'portal_simulation':
tool_class = 'ISimulationTool' # XXX these I-prefix are stupid
tool_import = 'from erp5.portal_type import ISimulationTool'
source.extend(
tool_stub_template.format(
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
return textwrap.dedent(
'''
from Products.ERP5Site.ERP5Site import ERP5Site as ERP5Site_parent_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))
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):
"""Save code in filesystem for jedi to use it.
......@@ -1254,7 +1243,10 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
if not os.path.exists(path):
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
mkdir_p(module_dir)
......@@ -1268,10 +1260,10 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
'skins_tool',
'component',):
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))
if module == 'portal_type':
# portal types
all_portal_type_class_names = []
with open(
os.path.join(
module_dir,
......@@ -1280,6 +1272,7 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
'w',) as module_f:
for ti in portal.portal_types.contentValues():
class_name = safe_python_identifier(ti.getId())
all_portal_type_class_names.append(class_name)
module_f.write(
'from .{class_name} import {class_name}\n'.format(
class_name=class_name))
......@@ -1293,8 +1286,42 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
) as type_information_f:
type_information_f.write(
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')
with open(
os.path.join(
......@@ -1303,15 +1330,42 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
'ICatalogTool.pyi',),
'w',) as portal_f:
portal_f.write(
CatalogTool_getStub(self.getPortalObject().portal_catalog))
module_f.write('from .SimulationTool import SimulationTool\n')
# TOODO: simulation tool
textwrap.dedent(
'''
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
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(
os.path.join(
module_dir,
......@@ -1319,6 +1373,108 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
'ERP5Site.pyi',),
'w',) as portal_f:
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':
# TODO: real path is accessor_holder.something !?
with open(
......@@ -1359,6 +1515,7 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
skins_tool,
class_name,).encode('utf-8'))
elif module == 'component':
# TODO: component versions ?
module_to_component_portal_type_mapping = {
'test': 'Test Component',
'document': 'Document Component',
......
......@@ -45,7 +45,13 @@
<item>
<key> <string>text_content_warning_message</string> </key>
<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>
</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