Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
erp5
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Nikola Balog
erp5
Commits
616ebae2
Commit
616ebae2
authored
Oct 29, 2019
by
Jérome Perrin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
jedi: generate stubs WIP
parent
a4136edf
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
1647 additions
and
55 deletions
+1647
-55
bt5/erp5_monaco_editor/ExtensionTemplateItem/portal_components/extension.erp5.Jedi.py
...sionTemplateItem/portal_components/extension.erp5.Jedi.py
+1444
-54
bt5/erp5_monaco_editor/ExtensionTemplateItem/portal_components/extension.erp5.Jedi.xml
...ionTemplateItem/portal_components/extension.erp5.Jedi.xml
+7
-1
bt5/erp5_monaco_editor/SkinTemplateItem/portal_skins/erp5_monaco_editor/ERP5Site_dumpModuleCode.xml
...rtal_skins/erp5_monaco_editor/ERP5Site_dumpModuleCode.xml
+28
-0
bt5/erp5_monaco_editor/SkinTemplateItem/portal_skins/erp5_monaco_editor/ERP5Site_getPortalStub.xml
...ortal_skins/erp5_monaco_editor/ERP5Site_getPortalStub.xml
+28
-0
bt5/erp5_monaco_editor/SkinTemplateItem/portal_skins/erp5_monaco_editor/PropertySheetTool_getStub.xml
...al_skins/erp5_monaco_editor/PropertySheetTool_getStub.xml
+28
-0
bt5/erp5_monaco_editor/SkinTemplateItem/portal_skins/erp5_monaco_editor/PropertySheet_getStub.xml
...portal_skins/erp5_monaco_editor/PropertySheet_getStub.xml
+28
-0
bt5/erp5_monaco_editor/SkinTemplateItem/portal_skins/erp5_monaco_editor/SkinsTool_getStub.xml
...tem/portal_skins/erp5_monaco_editor/SkinsTool_getStub.xml
+28
-0
bt5/erp5_monaco_editor/SkinTemplateItem/portal_skins/erp5_monaco_editor/TypeInformation_getStub.xml
...rtal_skins/erp5_monaco_editor/TypeInformation_getStub.xml
+28
-0
bt5/erp5_monaco_editor/SkinTemplateItem/portal_skins/erp5_monaco_editor/TypesTool_getStub.xml
...tem/portal_skins/erp5_monaco_editor/TypesTool_getStub.xml
+28
-0
No files found.
bt5/erp5_monaco_editor/ExtensionTemplateItem/portal_components/extension.erp5.Jedi.py
View file @
616ebae2
from
__future__
import
unicode_literals
import
json
import
json
import
sys
import
sys
from
threading
import
RLock
from
threading
import
RLock
import
logging
import
logging
import
inspect
logger
=
logging
.
getLogger
(
"erp5.extension.Jedi"
)
import
os
import
jedi
import
jedi
import
zope.dottedname.resolve
import
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.
# rdiff-backup seem to trigger a bug, but it's generally super slow and not correct for us.
try
:
# in jedi 0.15.1 it's here
from
jedi.evaluate
import
sys_path
as
jedi_inference_sys_path
# pylint: disable=import-error
except
ImportError
:
# but it's beeing moved. Next release will be here
# https://github.com/davidhalter/jedi/commit/3b4f2924648eafb9660caac9030b20beb50a83bb
from
jedi.inference
import
sys_path
as
jedi_inference_sys_path
# pylint: disable=import-error
_
=
jedi_inference_sys_path
.
discover_buildout_paths
# make sure it's here
def
dont_discover_buildout_paths
(
*
args
,
**
kw
):
return
set
()
jedi_inference_sys_path
.
discover_buildout_paths
=
dont_discover_buildout_paths
from
jedi.api
import
project
as
jedi_api_project
jedi_api_project
.
discover_buildout_paths
=
dont_discover_buildout_paths
try
:
# for local types annotations in this component
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.base_context
import
ContextSet
from
jedi.parser_utils
import
get_cached_code_lines
from
StringIO
import
StringIO
class
JediERP5Plugin
(
object
):
_cache
=
{}
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
request
=
get_request
()
assert
request
return
getSite
().
__of__
(
RequestContainer
(
REQUEST
=
request
))
def
execute
(
self
,
callback
):
"""Handle dynamic methods accepting portal_type= arguments
"""
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
)
# 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
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
)
})
logger
.
info
(
'portal_type based method, returning
\
n
%s instead of
\
n
%s'
,
original
,
call
())
#import pdb; pdb.set_trace()
return
original
# methods returning List of portal types
# methods returning List of Brain of portal types
return
call
()
return
wrapper
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
):
if
len
(
import_names
)
>
1
and
import_names
[:
2
]
in
(
(
'erp5'
,
'portal_type'
),
(
'erp5'
,
'accessor_holder'
),
(
'erp5'
,
'skins_tool'
),
(
'erp5'
,
'component'
)):
try
:
if
1
:
erp5_code
=
'''
from . import portal_type
from . import skins_tool
from . import accessor_holder
from . import component
'''
portal
=
self
.
_getPortalObject
()
module_to_component_portal_type_mapping
=
{
'test'
:
'Test Component'
,
'document'
:
'Document Component'
,
'extension'
:
'Extension Component'
,
'tool'
:
'Tool Component'
,
'module'
:
'Module Component'
,
'interface'
:
'Interface Component'
,
}
if
import_names
==
(
'erp5'
,
'portal_type'
):
source_code
=
self
.
_cache
.
get
(
'portal_type'
)
if
not
source_code
:
source_code
=
portal
.
portal_types
.
TypesTool_getStub
()
self
.
_cache
[
'portal_type'
]
=
source_code
source_path
=
"erp5/portal_type.pyi"
elif
import_names
==
(
'erp5'
,
'accessor_holder'
):
source_code
=
self
.
_cache
.
get
(
'accessor_holder'
)
if
not
source_code
:
source_code
=
portal
.
portal_types
.
PropertySheetTool_getStub
()
self
.
_cache
[
'accessor_holder'
]
=
source_code
source_path
=
"erp5/accessor_holder.pyi"
elif
import_names
==
(
'erp5'
,
'skins_tool'
):
source_code
=
self
.
_cache
.
get
(
'skins_tool'
)
if
not
source_code
:
source_code
=
portal
.
portal_skins
.
SkinsTool_getStub
()
self
.
_cache
[
'skins_tool'
]
=
source_code
source_path
=
"erp5/skins_tool.pyi"
elif
import_names
[
1
]
in
(
'portal_types'
,
'accessor_holder'
,
'skins_tool'
):
# fallback
return
callback
(
evaluator
,
import_names
,
module_context
,
*
args
,
**
kwargs
)
else
:
# if import_name[-1] == 'component':
dotted_name
=
'.'
.
join
(
import_names
)
logger
.
info
(
'looking up code for %s'
,
dotted_name
)
# we don't have source for some modules
if
dotted_name
==
'erp5.component'
:
source_code
=
textwrap
.
dedent
(
'''
\
from . import document
from . import test
from . import extension
from . import tool
from . import interface
from . import module
'''
)
elif
len
(
import_names
)
==
3
and
import_names
[:
2
]
==
(
'erp5'
,
'component'
)
and
import_names
[
2
]
in
module_to_component_portal_type_mapping
:
line_list
=
[]
for
brain
in
portal
.
portal_catalog
.
searchResults
(
portal_type
=
module_to_component_portal_type_mapping
[
import_names
[
2
]],
validation_state
=
'validated'
,
select_dict
=
{
'reference'
:
None
}):
line_list
.
append
(
'from . import {}'
.
format
(
brain
.
reference
))
source_code
=
'
\
n
'
.
join
(
line_list
)
else
:
source_code
=
inspect
.
getsource
(
zope
.
dottedname
.
resolve
.
resolve
(
dotted_name
))
source_path
=
"{}.pyi"
.
format
(
'/'
.
join
(
import_names
))
logger
.
info
(
'got code for %s'
,
source_path
)
# logger.error('unsupported import %s', import_names)
# raise NotImplementedError()
#logger.info('got code for %s', source_path)
source
=
StringIO
(
source_code
)
# XXX make this StringIO look like a jedi FileIO
source
.
get_last_modified
=
lambda
:
last_reload_time
# os.path.getmtime(file_path) # TODO
source
.
path
=
source_path
source
.
get_parent_folder
=
lambda
:
None
# TODO bug with using StringIO
# AttributeError: StringIO instance has no attribute 'get_parent_folder'
# email = pers.newContent(portal_type='Email')
module
=
evaluator
.
parse
(
file_io
=
source
,
cache
=
True
,
use_latest_grammar
=
True
)
# XXX needed ?
erp5_source
=
StringIO
(
erp5_code
)
erp5_source
.
path
=
"erp5.py"
erp5_source
.
get_last_modified
=
lambda
:
13
erp5_module
=
evaluator
.
parse
(
file_io
=
erp5_source
,
cache
=
True
,
use_latest_grammar
=
True
)
non_stub_context
=
ModuleContext
(
evaluator
,
erp5_module
,
file_io
=
erp5_source
,
string_names
=
None
,
code_lines
=
get_cached_code_lines
(
evaluator
.
latest_grammar
,
erp5_source
.
path
),
is_package
=
True
)
non_stub_context
.
is_builtins_module
=
lambda
:
False
parent_context
=
StubModuleContext
(
ContextSet
([
non_stub_context
]),
evaluator
,
module
,
file_io
=
source
,
string_names
=
None
,
#('erp5', 'portal_type'),
code_lines
=
get_cached_code_lines
(
evaluator
.
latest_grammar
,
source
.
path
),
# is_package=True
)
parent_context
.
is_builtins_module
=
lambda
:
False
#logger.info('got context %s', parent_context)
#import pdb; pdb.set_trace()
return
ContextSet
([
non_stub_context
,
parent_context
])
except
Exception
:
logger
.
exception
(
"merde"
)
return
callback
(
evaluator
,
import_names
,
module_context
,
*
args
,
**
kwargs
)
logger
.
info
(
'returning %s'
,
wrapper
)
return
wrapper
return
JediERP5Plugin
()
# map jedi type to the name of monaco.languages.CompletionItemKind
# map jedi type to the name of monaco.languages.CompletionItemKind
...
@@ -17,50 +351,54 @@ jedi.settings.call_signatures_validity = 30 # XXX needed ?
...
@@ -17,50 +351,54 @@ jedi.settings.call_signatures_validity = 30 # XXX needed ?
# https://github.com/palantir/python-language-server/blob/19b10c47988df504872a4fe07c421b0555b3127e/LICENSE
# https://github.com/palantir/python-language-server/blob/19b10c47988df504872a4fe07c421b0555b3127e/LICENSE
_TYPE_MAP
=
{
_TYPE_MAP
=
{
'none'
:
'Value'
,
'none'
:
'Value'
,
'type'
:
'Class'
,
'type'
:
'Class'
,
'tuple'
:
'Class'
,
'tuple'
:
'Class'
,
'dict'
:
'Class'
,
'dict'
:
'Class'
,
'dictionary'
:
'Class'
,
'dictionary'
:
'Class'
,
'function'
:
'Function'
,
'function'
:
'Function'
,
'lambda'
:
'Function'
,
'lambda'
:
'Function'
,
'generator'
:
'Function'
,
'generator'
:
'Function'
,
'class'
:
'Class'
,
'class'
:
'Class'
,
'instance'
:
'Reference'
,
'instance'
:
'Reference'
,
'method'
:
'Method'
,
'method'
:
'Method'
,
'builtin'
:
'Class'
,
'builtin'
:
'Class'
,
'builtinfunction'
:
'Function'
,
'builtinfunction'
:
'Function'
,
'module'
:
'Module'
,
'module'
:
'Module'
,
'file'
:
'File'
,
'file'
:
'File'
,
'xrange'
:
'Class'
,
'xrange'
:
'Class'
,
'slice'
:
'Class'
,
'slice'
:
'Class'
,
'traceback'
:
'Class'
,
'traceback'
:
'Class'
,
'frame'
:
'Class'
,
'frame'
:
'Class'
,
'buffer'
:
'Class'
,
'buffer'
:
'Class'
,
'dictproxy'
:
'Class'
,
'dictproxy'
:
'Class'
,
'funcdef'
:
'Function'
,
'funcdef'
:
'Function'
,
'property'
:
'Property'
,
'property'
:
'Property'
,
'import'
:
'Module'
,
'import'
:
'Module'
,
'keyword'
:
'Keyword'
,
'keyword'
:
'Keyword'
,
'constant'
:
'Variable'
,
'constant'
:
'Variable'
,
'variable'
:
'Variable'
,
'variable'
:
'Variable'
,
'value'
:
'Value'
,
'value'
:
'Value'
,
'param'
:
'Variable'
,
'param'
:
'Variable'
,
'statement'
:
'Keyword'
,
'statement'
:
'Keyword'
,
}
}
def
_label
(
definition
):
def
_label
(
definition
):
if
definition
.
type
in
(
'function'
,
'method'
)
and
hasattr
(
definition
,
'params'
):
if
definition
.
type
in
(
'function'
,
'method'
)
and
hasattr
(
definition
,
'params'
):
params
=
', '
.
join
([
param
.
name
for
param
in
definition
.
params
])
params
=
', '
.
join
([
param
.
name
for
param
in
definition
.
params
])
return
'{}({})'
.
format
(
definition
.
name
,
params
)
return
'{}({})'
.
format
(
definition
.
name
,
params
)
return
definition
.
name
return
definition
.
name
def
_detail
(
definition
):
def
_detail
(
definition
):
try
:
try
:
return
definition
.
parent
().
full_name
or
''
return
definition
.
parent
().
full_name
or
''
except
AttributeError
:
except
AttributeError
:
return
definition
.
full_name
or
''
return
definition
.
full_name
or
''
def
_sort_text
(
definition
):
def
_sort_text
(
definition
):
""" Ensure builtins appear at the bottom.
""" Ensure builtins appear at the bottom.
Description is of format <type>: <module>.<item>
Description is of format <type>: <module>.<item>
...
@@ -69,9 +407,11 @@ def _sort_text(definition):
...
@@ -69,9 +407,11 @@ def _sort_text(definition):
prefix
=
'z{}'
if
definition
.
name
.
startswith
(
'_'
)
else
'a{}'
prefix
=
'z{}'
if
definition
.
name
.
startswith
(
'_'
)
else
'a{}'
return
prefix
.
format
(
definition
.
name
)
return
prefix
.
format
(
definition
.
name
)
def
_format_docstring
(
docstring
):
def
_format_docstring
(
docstring
):
return
docstring
return
docstring
def
_format_completion
(
d
):
def
_format_completion
(
d
):
completion
=
{
completion
=
{
'label'
:
_label
(
d
),
'label'
:
_label
(
d
),
...
@@ -83,27 +423,30 @@ def _format_completion(d):
...
@@ -83,27 +423,30 @@ def _format_completion(d):
}
}
return
completion
return
completion
def
_guessType
(
name
):
def
_guessType
(
name
,
context_type
=
None
):
"""guess the type of python script parameters based on naming conventions.
"""guess the type of python script parameters based on naming conventions.
TODO: depend on the script name, for Person_getSomething, context is a erp5.portal_type.Person
"""
"""
name
=
name
.
split
(
'='
)[
0
]
# support also assigned names ( like REQUEST=None in params)
name
=
name
.
split
(
'='
)[
if
name
in
(
'context'
,
'container'
,):
0
]
# support also assigned names ( like REQUEST=None in params)
return
'Products.ERP5Type.Core.Folder.Folder'
if
name
==
'context'
and
context_type
:
return
context_type
if
name
in
(
'context'
,
'container'
,):
return
'ERP5Site'
if
name
==
'script'
:
if
name
==
'script'
:
return
'Products.PythonScripts.PythonScript'
return
'Products.PythonScripts.PythonScript'
if
name
==
'REQUEST'
:
if
name
==
'REQUEST'
:
return
'ZPublisher.HTTPRequest.HTTPRequest'
return
'ZPublisher.HTTPRequest.HTTPRequest'
if
name
==
'RESPONSE'
:
if
name
==
'RESPONSE'
:
return
'ZPublisher.HTTPRequest.HTTPResponse'
return
'ZPublisher.HTTPRequest.HTTPResponse'
return
'str'
# assume string by default
return
'str'
# assume string by default
#jedi_lock = RLock() # jedi is not thread safe
import
Products.ERP5Type.Utils
logger
=
logging
.
getLogger
(
"erp5.extension.Jedi"
)
# Jedi is not thread safe
# Jedi is not thread safe
import
Products.ERP5Type.Utils
jedi_lock
=
getattr
(
Products
.
ERP5Type
.
Utils
,
'jedi_lock'
,
None
)
jedi_lock
=
getattr
(
Products
.
ERP5Type
.
Utils
,
'jedi_lock'
,
None
)
if
jedi_lock
is
None
:
if
jedi_lock
is
None
:
logger
.
critical
(
"There was no lock, making a new one"
)
logger
.
critical
(
"There was no lock, making a new one"
)
...
@@ -114,8 +457,16 @@ logger.info("Jedi locking with %s (%s)", jedi_lock, id(jedi_lock))
...
@@ -114,8 +457,16 @@ logger.info("Jedi locking with %s (%s)", jedi_lock, id(jedi_lock))
def
ERP5Site_getPythonSourceCodeCompletionList
(
self
,
data
,
REQUEST
=
None
):
def
ERP5Site_getPythonSourceCodeCompletionList
(
self
,
data
,
REQUEST
=
None
):
"""Complete source code with jedi.
"""Complete source code with jedi.
"""
"""
logger
.
info
(
'jedi get lock %s (%s)'
,
jedi_lock
,
id
(
jedi_lock
))
portal
=
self
.
getPortalObject
()
logger
.
debug
(
'jedi get lock %s (%s)'
,
jedi_lock
,
id
(
jedi_lock
))
with
jedi_lock
:
with
jedi_lock
:
# register our erp5 plugin
from
jedi.plugins
import
plugin_manager
if
not
getattr
(
plugin_manager
,
'_erp5_plugin_registered'
,
None
):
plugin_manager
.
register
(
makeERP5Plugin
())
plugin_manager
.
_erp5_plugin_registered
=
True
if
isinstance
(
data
,
basestring
):
if
isinstance
(
data
,
basestring
):
data
=
json
.
loads
(
data
)
data
=
json
.
loads
(
data
)
...
@@ -124,41 +475,1080 @@ def ERP5Site_getPythonSourceCodeCompletionList(self, data, REQUEST=None):
...
@@ -124,41 +475,1080 @@ def ERP5Site_getPythonSourceCodeCompletionList(self, data, REQUEST=None):
def
indent
(
text
):
def
indent
(
text
):
return
''
.
join
((
" "
+
line
)
for
line
in
text
.
splitlines
(
True
))
return
''
.
join
((
" "
+
line
)
for
line
in
text
.
splitlines
(
True
))
script_name
=
data
.
get
(
'script_name'
,
'unknown.py'
)
# TODO name
is_python_script
=
'bound_names'
in
data
is_python_script
=
'bound_names'
in
data
if
is_python_script
:
if
is_python_script
:
signature_parts
=
data
[
'bound_names'
]
signature_parts
=
data
[
'bound_names'
]
if
data
[
'params'
]:
if
data
[
'params'
]:
signature_parts
+=
[
data
[
'params'
]]
signature_parts
+=
[
data
[
'params'
]]
signature
=
", "
.
join
(
signature_parts
)
signature
=
", "
.
join
(
signature_parts
)
imports
=
"import Products.ERP5Type.Core.Folder; import ZPublisher.HTTPRequest; import Products.PythonScripts"
# guess type of `context`
function_name
=
"function_name"
context_type
=
None
type_annotation
=
" # type: (%s) -> None"
%
(
if
'_'
in
script_name
:
', '
.
join
([
_guessType
(
part
)
for
part
in
signature_parts
]))
context_type
=
script_name
.
split
(
'_'
)[
0
]
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 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
'ERP5Site'
)
type_annotation
=
" # type: ({}) -> None"
.
format
(
', '
.
join
(
[
_guessType
(
part
,
context_type
)
for
part
in
signature_parts
]))
body
=
"%s
\
n
def %s(%s):
\
n
%s
\
n
%s"
%
(
body
=
"%s
\
n
def %s(%s):
\
n
%s
\
n
%s"
%
(
imports
,
imports
,
function
_name
,
script
_name
,
signature
,
signature
,
type_annotation
,
type_annotation
,
indent
(
data
[
'code'
])
or
" pass"
)
indent
(
data
[
'code'
])
or
" pass"
)
data
[
'position'
][
'line'
]
=
data
[
'position'
][
'line'
]
+
3
# imports, fonction header + type annotation line
data
[
'position'
][
'line'
]
=
data
[
'position'
][
data
[
'position'
][
'column'
]
=
data
[
'position'
][
'column'
]
+
2
# " " from indent(text)
'line'
]
+
3
# imports, fonction header + type annotation line
data
[
'position'
][
'column'
]
=
data
[
'position'
][
'column'
]
+
2
# " " from indent(text)
else
:
else
:
body
=
data
[
'code'
]
body
=
data
[
'code'
]
logger
.
info
(
"jedi getting completions...."
)
logger
.
debug
(
"jedi getting completions for %s ..."
,
script_name
)
start
=
time
.
time
()
script
=
jedi
.
Script
(
script
=
jedi
.
Script
(
body
,
body
,
data
[
'position'
][
'line'
],
data
[
'position'
][
'line'
],
data
[
'position'
][
'column'
]
-
1
,
data
[
'position'
][
'column'
]
-
1
,
'example.py'
,
# TODO name
script_name
,
sys_path
=
list
(
sys
.
path
),
sys_path
=
[
'/tmp/ahaha/'
]
+
list
(
sys
.
path
),
)
)
completions
=
[
_format_completion
(
c
)
for
c
in
script
.
completions
()]
completions
=
[
_format_completion
(
c
)
for
c
in
script
.
completions
()]
logger
.
info
(
"jedi got completion"
)
logger
.
info
(
"jedi got %d completions in %.2fs"
,
len
(
completions
),
(
time
.
time
()
-
start
))
if
REQUEST
is
not
None
:
if
REQUEST
is
not
None
:
REQUEST
.
RESPONSE
.
setHeader
(
'content-type'
,
'application/json'
)
REQUEST
.
RESPONSE
.
setHeader
(
'content-type'
,
'application/json'
)
return
json
.
dumps
(
completions
)
return
json
.
dumps
(
completions
)
import
textwrap
def
safe_python_identifier
(
name
):
# type: (str) -> str
"""Format this name as a python idenfier.
For example "Person Module" becomes "PersonModule"
"""
return
name
.
replace
(
" "
,
""
)
def
safe_docstring
(
docstring
):
# type: (str) -> str
"""Formats a docstring to include in generated stub.
"""
if
not
docstring
:
return
'...'
return
"'''{}'''"
.
format
(
docstring
.
replace
(
"'''"
,
r"\'\'\'"
))
from
Products.ERP5Type.Accessor
import
Constant
from
Products.ERP5Type.Accessor
import
WorkflowState
from
Products.ERP5Type.Base
import
WorkflowMethod
from
Products.ERP5Type.ERP5Type
import
ERP5TypeInformation
# pylint: disable=unused-import
from
Products.PythonScripts.PythonScript
import
PythonScript
# pylint: disable=unused-import
from
collections
import
namedtuple
,
defaultdict
def
SkinsTool_getClassSet
(
self
):
portal
=
self
.
getPortalObject
()
class_set
=
set
([])
# TODO: sort by default skin selection and use the ones registered in skin selections
for
skin_folder
in
portal
.
portal_skins
.
objectValues
():
for
script
in
skin_folder
.
objectValues
(
spec
=
(
'Script (Python)'
,
'External Method'
)):
if
not
'_'
in
script
.
getId
():
logger
.
debug
(
'Skipping wrongly named script %s'
,
script
.
getId
())
continue
type_
=
script
.
getId
().
split
(
'_'
)[
0
]
class_set
.
add
(
type_
)
return
class_set
def
SkinsTool_getStubForClass
(
self
,
class_name
):
portal
=
self
.
getPortalObject
()
line_list
=
[]
SkinDefinition
=
namedtuple
(
'SkinDefinition'
,
'id,docstring,type_comment,skin_folder,params'
)
import
parso
grammar
=
parso
.
load_grammar
()
import
re
type_coment_re
=
re
.
compile
(
r"\
s*#
\s*type:.*"
)
# collect skins by type
skin_by_type
=
defaultdict
(
list
)
# TODO: sort by default skin selection and use only the ones registered in skin selections
for
skin_folder
in
portal
.
portal_skins
.
objectValues
():
for
script
in
skin_folder
.
objectValues
(
spec
=
(
'Script (Python)'
,
'External Method'
)):
if
not
'_'
in
script
.
getId
():
logger
.
debug
(
'Skipping wrongly named script %s'
,
script
.
getId
())
continue
type_
=
script
.
getId
().
split
(
'_'
)[
0
]
if
type_
!=
class_name
:
continue
docstring
=
'"""External method"""'
params
=
''
type_comment
=
''
if
script
.
meta_type
==
'Script (Python)'
:
body
=
script
.
body
()
params
=
script
.
params
()
if
params
:
params
=
', {}'
.
format
(
params
)
icon_path
=
script
.
om_icons
()[
0
][
'path'
]
icon_alt
=
script
.
om_icons
()[
0
][
'alt'
]
docstring_first_line
=
(
"![{icon_alt}]({portal_url}/{icon_path}) "
"[`{script_id}`]({portal_url}/portal_skins/{skin_folder_id}/{script_id}/manage_main)
\
n
"
).
format
(
icon_alt
=
icon_alt
,
portal_url
=
portal
.
absolute_url
(),
icon_path
=
icon_path
,
script_id
=
script
.
getId
(),
skin_folder_id
=
skin_folder
.
getId
())
docstring
=
'"""{}"""'
.
format
(
docstring_first_line
)
module
=
grammar
.
parse
(
body
)
if
next
(
iter
(
grammar
.
iter_errors
(
module
)),
None
)
is
not
None
:
first_leaf
=
module
.
get_first_leaf
()
type_comment
=
first_leaf
.
prefix
.
strip
()
if
not
type_coment_re
.
match
(
type_comment
):
type_comment
=
''
else
:
# make sure docstring is indented
type_comment
=
'{}
\
n
'
.
format
(
type_comment
)
if
first_leaf
.
type
==
'string'
:
original_docstring
=
first_leaf
.
value
if
original_docstring
.
startswith
(
"'''"
):
docstring
=
"'''{}
\
n
{}"
.
format
(
docstring_first_line
,
original_docstring
[
3
:])
elif
original_docstring
.
startswith
(
"'"
):
docstring
=
"'''{}
\
n
{}''"
.
format
(
docstring_first_line
,
original_docstring
[
1
:])
elif
original_docstring
.
startswith
(
'"""'
):
docstring
=
'"""{}
\
n
{}'
.
format
(
docstring_first_line
,
original_docstring
[
3
:])
elif
original_docstring
.
startswith
(
'"'
):
docstring
=
'"""{}
\
n
{}""'
.
format
(
docstring_first_line
,
original_docstring
[
1
:])
skin_by_type
[
type_
].
append
(
SkinDefinition
(
script
.
getId
(),
docstring
,
type_comment
,
skin_folder
.
getId
(),
params
))
for
type_
,
skins
in
skin_by_type
.
items
():
line_list
.
append
(
textwrap
.
dedent
(
"""
\
import erp5.portal_type
from erp5 import portal_type
class {class_name}:
{docstring}
"""
).
format
(
class_name
=
safe_python_identifier
(
type_
),
docstring
=
safe_docstring
(
"Skins for {}"
.
format
(
type_
))))
for
skin
in
skins
:
skin
=
skin
# type: SkinDefinition
line_list
.
append
(
# the comment is also here so that dedent keep indentation, because this method block needs
# more indentation than class block
textwrap
.
dedent
(
"""
\
# {skin_id} in {skin_folder}
def {skin_id}(self{params}):
{type_comment}{docstring}
"""
).
format
(
skin_id
=
skin
.
id
,
skin_folder
=
skin
.
skin_folder
,
params
=
skin
.
params
,
type_comment
=
skin
.
type_comment
,
docstring
=
skin
.
docstring
))
return
"
\
n
"
.
join
(
line_list
)
def
TypeInformation_getStub
(
self
):
# type: (ERP5TypeInformation) -> str
"""returns a .pyi stub file for this portal type
https://www.python.org/dev/peps/pep-0484/
"""
portal
=
self
.
getPortalObject
()
# TODO: getParentValue
# TODO: a class for magic things like getPortalObject ?
@
WorkflowMethod
.
disable
def
makeTempClass
():
# everything is allowed in portal trash so we create our
# temp object there.
return
portal
.
portal_trash
.
newContent
(
portal_type
=
self
.
getId
(),
temp_object
=
True
,
id
=
'?'
,
title
=
'?'
).
__class__
temp_class
=
makeTempClass
()
# mro() of temp objects is like :
# (<class 'erp5.temp_portal_type.Temporary Person Module'>,
# <class 'Products.ERP5Type.mixin.temporary.TemporaryDocumentMixin'>,
# <class 'erp5.portal_type.Person Module'>, <-- this is the generated class.
# <class 'Products.ERP5.Document.Person.Person'>, <-- this is the source code of the "real" class,
# extending other "real" classes
# ...
temp_class
=
temp_class
.
mro
()[
2
]
parent_class
=
temp_class
.
mro
()[
1
]
parent_class_module
=
parent_class
.
__module__
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
=
""
methods
=
[]
debug
=
""
method_template_template
=
""" {decorator}
\
n
def {method_name}({method_args}) -> {return_type}:
\
n
{docstring}"""
methods
.
append
(
method_template_template
.
format
(
decorator
=
''
,
method_name
=
'getPortalType'
,
method_args
=
"self"
,
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
.
add
(
'from erp5.portal_type import ERP5Site'
)
methods
.
append
(
method_template_template
.
format
(
decorator
=
''
,
method_name
=
'getPortalObject'
,
method_args
=
"self"
,
return_type
=
'ERP5Site'
,
docstring
=
safe_docstring
(
getattr
(
temp_class
.
getPortalObject
,
'__doc__'
,
None
)
or
'...'
)))
# first class contain workflow and some constraints.
for
property_name
in
sorted
(
vars
(
temp_class
)):
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
=
''
,
method_name
=
safe_python_identifier
(
property_name
),
method_args
=
"self"
,
return_type
=
type
(
property_value
.
value
).
__name__
,
docstring
=
safe_docstring
(
'TODO %s'
%
property_value
)))
elif
isinstance
(
property_value
,
(
WorkflowState
.
TitleGetter
,
WorkflowState
.
TranslatedGetter
,
WorkflowState
.
TranslatedTitleGetter
,
WorkflowState
.
Getter
)):
# TODO: docstring (with link to workflow)
methods
.
append
(
method_template_template
.
format
(
decorator
=
''
,
method_name
=
safe_python_identifier
(
property_name
),
method_args
=
"self"
,
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
=
''
,
method_name
=
safe_python_identifier
(
property_name
),
method_args
=
"self"
,
return_type
=
'None'
,
docstring
=
safe_docstring
(
'TODO %s'
%
property_value
)))
elif
property_name
.
startswith
(
'serialize'
):
# isinstance(property_value, WorkflowState.SerializeGetter): XXX not a class..
methods
.
append
(
method_template_template
.
format
(
decorator
=
''
,
method_name
=
safe_python_identifier
(
property_name
),
method_args
=
"self"
,
return_type
=
'None'
,
docstring
=
safe_docstring
(
getattr
(
property_value
,
'__doc__'
,
None
))))
# TODO: generated methods for categories.
else
:
debug
+=
"
\
n
# not handled property: {} -> {} {}"
.
format
(
property_name
,
property_value
,
getattr
(
property_value
,
'__dict__'
,
''
))
# for folderish contents, generate typed contentValues
allowed_content_types
=
self
.
getTypeAllowedContentTypeList
()
allowed_content_types_classes
=
[
safe_python_identifier
(
t
)
for
t
in
allowed_content_types
]
if
allowed_content_types
and
hasattr
(
temp_class
,
'contentValues'
):
for
allowed
in
allowed_content_types_classes
:
imports
.
add
(
'from erp5.portal_type import {}'
.
format
(
allowed
))
if
len
(
allowed_content_types
)
==
1
:
subdocument_type
=
'{}'
.
format
(
allowed_content_types_classes
[
0
])
else
:
subdocument_type
=
'Union[{}]'
.
format
(
', '
.
join
(
allowed_content_types_classes
))
# TODO: getParentValue
for
method_name
in
(
'contentValues'
,
'objectValues'
,
'searchFolder'
):
return_type
=
'List[{}]'
.
format
(
subdocument_type
)
if
method_name
==
'searchFolder'
:
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[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"
,
return_type
=
return_type
,
docstring
=
safe_docstring
(
getattr
(
getattr
(
temp_class
,
method_name
),
'__doc__'
,
None
))))
methods
.
append
(
method_template_template
.
format
(
decorator
=
''
,
method_name
=
'newContent'
,
method_args
=
"self"
,
# TODO
return_type
=
subdocument_type
,
docstring
=
safe_docstring
(
getattr
(
temp_class
.
newContent
,
'__doc__'
,
None
))))
# 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'
,):
methods
.
append
(
method_template_template
.
format
(
decorator
=
''
,
method_name
=
method_name
,
method_args
=
"self, attribute:str"
,
return_type
=
subdocument_type
,
docstring
=
'...'
))
for
identity_method
in
(
'getObject'
,
'asContext'
,
'__of__'
,):
method
=
getattr
(
temp_class
,
identity_method
,
None
)
if
method
is
not
None
:
methods
.
append
(
method_template_template
.
format
(
decorator
=
''
,
method_name
=
identity_method
,
method_args
=
"self"
,
# TODO
return_type
=
safe_python_identifier
(
temp_class
.
__name__
),
docstring
=
safe_docstring
(
getattr
(
method
,
'__doc__'
,
None
))))
# the parent class is imported in a name that should not clash
parent_class_alias
=
'{class_name}_parent_{parent_class}'
.
format
(
class_name
=
safe_python_identifier
(
temp_class
.
__name__
),
parent_class
=
safe_python_identifier
(
parent_class
.
__name__
))
base_classes
=
[
parent_class_alias
]
for
pc
in
temp_class
.
mro
():
if
pc
.
__module__
==
'erp5.accessor_holder.property_sheet'
:
# Fake name for property sheets
prefixed_class_name
=
'property_sheet_{}'
.
format
(
safe_python_identifier
(
pc
.
__name__
))
imports
.
add
(
'from erp5.accessor_holder import {} as {}'
.
format
(
safe_python_identifier
(
pc
.
__name__
),
prefixed_class_name
))
base_classes
.
append
(
prefixed_class_name
)
# Fake name for skins
prefixed_class_name
=
'skins_tool_{}'
.
format
(
safe_python_identifier
(
pc
.
__name__
))
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
.
add
(
'from erp5.skins_tool import ERP5Site as skins_tool_ERP5Site'
)
base_classes
.
append
(
'skins_tool_ERP5Site'
)
base_classes
.
append
(
prefixed_class_name
)
class_template
=
textwrap
.
dedent
(
"""
\
{header}
{imports}
from {parent_class_module} import {parent_class} as {parent_class_alias}
class {class_name}({base_classes}):
{docstring}
{methods}
{debug}
"""
)
docstring
=
textwrap
.
dedent
(
'''
# {type_title_or_id}
---
{type_description}
{type_url}
'''
).
format
(
type_title_or_id
=
self
.
getTitleOrId
(),
type_description
=
self
.
getDescription
(),
type_url
=
self
.
absolute_url
())
return
class_template
.
format
(
imports
=
"
\
n
"
.
join
(
sorted
(
imports
)),
header
=
header
,
docstring
=
safe_docstring
(
docstring
),
class_name
=
safe_python_identifier
(
temp_class
.
__name__
),
base_classes
=
', '
.
join
(
base_classes
),
parent_class
=
safe_python_identifier
(
parent_class
.
__name__
),
parent_class_alias
=
parent_class_alias
,
parent_class_module
=
safe_python_identifier
(
parent_class_module
),
methods
=
"
\
n
"
.
join
(
methods
),
debug
=
debug
)
from
Products.ERP5Type.Core.PropertySheet
import
PropertySheet
# pylint: disable=unused-import
def
PropertySheetTool_getStub
(
self
):
print
(
'PropertySheetTool_getStub start'
)
sources
=
[]
for
ps
in
self
.
getPortalObject
().
portal_property_sheets
.
contentValues
():
try
:
sources
.
append
(
PropertySheet_getStub
(
ps
))
except
Exception
:
logger
.
exception
(
'error generating stub for %s'
,
ps
.
getId
())
print
(
'PropertySheetTool_getStub end'
)
return
"
\
n
"
.
join
(
sources
)
def
PropertySheet_getStub
(
self
):
# type: (PropertySheet) -> str
"""returns a .pyi stub file for this property sheet
https://www.python.org/dev/peps/pep-0484/
"""
portal_categories
=
self
.
getPortalObject
().
portal_categories
class_template
=
textwrap
.
dedent
(
"""
\
{imports}
class {class_name}:
'''{property_sheet_id}
{property_sheet_description}
'''
{methods}
{debug}
"""
)
debug
=
''
methods
=
[]
imports
=
[
'from typing import Optional, List, Any'
,
'from DateTime import DateTime'
,
'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
=
'reveal_property_sheet_{}'
.
format
(
safe_python_identifier
(
self
.
getId
())),
method_args
=
'self'
,
return_type
=
'str'
,
docstring
=
safe_docstring
(
"ahaha cool :)"
)))
def
_getPythonTypeFromPropertySheetType
(
prop
):
# type: (erp5.portal_type.StandardProperty) -> str
property_sheet_type
=
prop
.
getElementaryType
()
if
property_sheet_type
in
(
'content'
,
'object'
):
# TODO
return
'Any'
mapped_type
=
{
'string'
:
'str'
,
'boolean'
:
'bool'
,
'data'
:
'bytes'
,
# XXX jedi does not understand DateTime dynamic name, so use "real name"
'date'
:
'DateTime.DateTime'
,
'int'
:
'int'
,
'long'
:
'int'
,
# ???
'lines'
:
'List[str]'
,
'tokens'
:
'List[str]'
,
'float'
:
'float'
,
'text'
:
'str'
,
}.
get
(
property_sheet_type
,
'Any'
)
if
prop
.
isMultivalued
():
return
'List[{}]'
.
format
(
mapped_type
)
return
mapped_type
from
Products.ERP5Type.Utils
import
convertToUpperCase
from
Products.ERP5Type.Utils
import
evaluateExpressionFromString
from
Products.ERP5Type.Utils
import
createExpressionContext
expression_context
=
createExpressionContext
(
self
)
for
prop
in
self
.
contentValues
():
if
prop
.
getPortalType
()
in
(
'Standard Property'
,
'Acquired Property'
):
docstring
=
safe_docstring
(
textwrap
.
dedent
(
"""
\
[{property_sheet_title} {property_reference}]({property_url})
{property_description}
"""
).
format
(
property_description
=
prop
.
getDescription
(),
property_sheet_title
=
self
.
getTitle
(),
property_reference
=
prop
.
getReference
(),
property_url
=
prop
.
absolute_url
()))
methods
.
append
(
method_template_template
.
format
(
method_name
=
'get{}'
.
format
(
convertToUpperCase
(
prop
.
getReference
())),
method_args
=
'self'
,
return_type
=
_getPythonTypeFromPropertySheetType
(
prop
),
docstring
=
docstring
))
if
prop
.
getElementaryType
()
==
'boolean'
:
methods
.
append
(
method_template_template
.
format
(
method_name
=
'is{}'
.
format
(
convertToUpperCase
(
prop
.
getReference
())),
method_args
=
'self'
,
return_type
=
_getPythonTypeFromPropertySheetType
(
prop
),
docstring
=
docstring
))
methods
.
append
(
method_template_template
.
format
(
method_name
=
'set{}'
.
format
(
convertToUpperCase
(
prop
.
getReference
())),
method_args
=
'self, value:{}'
.
format
(
_getPythonTypeFromPropertySheetType
(
prop
)),
return_type
=
'None'
,
docstring
=
docstring
))
elif
prop
.
getPortalType
()
in
(
'Category Property'
,
'Dynamic Category Property'
):
if
prop
.
getPortalType
()
==
'Dynamic Category Property'
:
category_id_list
=
evaluateExpressionFromString
(
expression_context
,
prop
.
getCategoryExpression
())
else
:
category_id_list
=
[
prop
.
getReference
()]
for
category
in
category_id_list
:
category_value
=
portal_categories
.
_getOb
(
category
,
None
)
if
category_value
is
None
:
continue
docstring
=
safe_docstring
(
textwrap
.
dedent
(
"""
\
[{property_sheet_title} {property_reference}]({property_url})
[{category_title}]({category_url})
{property_description}
{category_description}
"""
).
format
(
property_description
=
prop
.
getDescription
(),
property_sheet_title
=
self
.
getTitle
(),
property_reference
=
prop
.
getReference
()
or
''
,
property_url
=
prop
.
absolute_url
(),
category_title
=
category_value
.
getTitle
(),
category_url
=
category_value
.
absolute_url
(),
category_description
=
category_value
.
getDescription
(),
))
methods
.
append
(
method_template_template
.
format
(
method_name
=
'get{}'
.
format
(
convertToUpperCase
(
category_value
.
getId
())),
method_args
=
'self'
,
return_type
=
'str'
,
docstring
=
docstring
))
methods
.
append
(
method_template_template
.
format
(
method_name
=
'get{}Title'
.
format
(
convertToUpperCase
(
category_value
.
getId
())),
method_args
=
'self'
,
return_type
=
'str'
,
docstring
=
docstring
))
methods
.
append
(
method_template_template
.
format
(
method_name
=
'get{}TranslatedTitle'
.
format
(
convertToUpperCase
(
category_value
.
getId
())),
method_args
=
'self'
,
return_type
=
'str'
,
docstring
=
docstring
))
methods
.
append
(
method_template_template
.
format
(
method_name
=
'get{}Value'
.
format
(
convertToUpperCase
(
category_value
.
getId
())),
method_args
=
'self'
,
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
=
'Type_AnyPortalTypeList'
,
docstring
=
docstring
))
methods
.
append
(
method_template_template
.
format
(
method_name
=
'set{}'
.
format
(
convertToUpperCase
(
category_value
.
getId
())),
method_args
=
'self, value: str'
,
return_type
=
'None'
,
docstring
=
docstring
))
methods
.
append
(
method_template_template
.
format
(
method_name
=
'set{}Value'
.
format
(
convertToUpperCase
(
category_value
.
getId
())),
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[Base]'
,
return_type
=
'None'
,
docstring
=
docstring
))
return
class_template
.
format
(
imports
=
'
\
n
'
.
join
(
imports
),
class_name
=
safe_python_identifier
(
self
.
getId
()),
property_sheet_id
=
self
.
getId
(),
property_sheet_description
=
self
.
getDescription
().
replace
(
"'''"
,
r"\\'\\'\\'"
),
methods
=
'
\
n
'
.
join
(
methods
),
debug
=
debug
,
)
from
Products.ERP5.ERP5Site
import
ERP5Site
# pylint: disable=unused-import
def
ERP5Site_getPortalStub
(
self
):
# type: (ERP5Site) -> str
module_stub_template
=
textwrap
.
dedent
(
'''
@property
def {module_id}(self):
from erp5.portal_type import {module_class_name}
return {module_class_name}()
'''
)
tool_stub_template
=
textwrap
.
dedent
(
'''
@property
def {tool_id}(self):
{tool_import}
return {tool_class}()
'''
)
source
=
[]
for
m
in
self
.
objectValues
():
if
m
.
getPortalType
().
endswith
(
'Module'
):
source
.
extend
(
module_stub_template
.
format
(
module_id
=
m
.
getId
(),
module_class_name
=
safe_python_identifier
(
m
.
getPortalType
())).
splitlines
())
else
:
tool_class
=
safe_python_identifier
(
m
.
__class__
.
__name__
)
tool_import
=
'from {} import {}'
.
format
(
m
.
__class__
.
__module__
,
tool_class
)
if
m
.
getId
()
==
'portal_catalog'
:
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
())
# 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
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
ERP5Site_dumpModuleCode
(
self
,
component_or_script
=
None
):
"""Save code in filesystem for jedi to use it.
Generate stubs for erp5.* dynamic modules and copy the in-ZODB modules
to files.
"""
def
mkdir_p
(
path
):
if
not
os
.
path
.
exists
(
path
):
os
.
mkdir
(
path
,
0o700
)
# 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
)
# generate erp5/__init__.py
with
open
(
os
.
path
.
join
(
module_dir
,
'__init__.py'
),
'w'
,)
as
erp5__init__f
:
for
module
in
(
'portal_type'
,
'accessor_holder'
,
'skins_tool'
,
'component'
,):
erp5__init__f
.
write
(
'from . import {module}
\
n
'
.
format
(
module
=
module
))
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
,
module
,
'__init__.py'
,),
'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
))
with
open
(
os
.
path
.
join
(
module_dir
,
module
,
'{class_name}.pyi'
.
format
(
class_name
=
class_name
),
),
'w'
,
)
as
type_information_f
:
type_information_f
.
write
(
ti
.
TypeInformation_getStub
().
encode
(
'utf-8'
))
# 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
(
module_dir
,
module
,
'ICatalogTool.pyi'
,),
'w'
,)
as
portal_f
:
portal_f
.
write
(
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
'
)
with
open
(
os
.
path
.
join
(
module_dir
,
module
,
'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
(
os
.
path
.
join
(
module_dir
,
module
,
'__init__.py'
),
'w'
,)
as
accessor_holder_f
:
for
ps
in
portal
.
portal_property_sheets
.
contentValues
():
class_name
=
safe_python_identifier
(
ps
.
getId
())
accessor_holder_f
.
write
(
'from .{class_name} import {class_name}
\
n
'
.
format
(
class_name
=
class_name
))
with
open
(
os
.
path
.
join
(
module_dir
,
module
,
'{class_name}.pyi'
.
format
(
class_name
=
class_name
),
),
'w'
,
)
as
property_sheet_f
:
property_sheet_f
.
write
(
ps
.
PropertySheet_getStub
().
encode
(
'utf-8'
))
elif
module
==
'skins_tool'
:
skins_tool
=
portal
.
portal_skins
with
open
(
os
.
path
.
join
(
module_dir
,
module
,
'__init__.py'
),
'w'
,)
as
skins_tool_f
:
for
class_name
in
SkinsTool_getClassSet
(
skins_tool
):
skins_tool_f
.
write
(
'from {class_name} import {class_name}
\
n
'
.
format
(
class_name
=
class_name
))
with
open
(
os
.
path
.
join
(
module_dir
,
module
,
'{}.pyi'
.
format
(
class_name
),),
'w'
,
)
as
skin_f
:
skin_f
.
write
(
SkinsTool_getStubForClass
(
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'
,
'extension'
:
'Extension Component'
,
'tool'
:
'Tool Component'
,
'module'
:
'Module Component'
,
'interface'
:
'Interface Component'
,
}
with
open
(
os
.
path
.
join
(
module_dir
,
module
,
'__init__.py'
),
'w'
,)
as
component_module__init__f
:
for
sub_module
,
portal_type
in
module_to_component_portal_type_mapping
.
items
():
component_module__init__f
.
write
(
'from . import {}
\
n
'
.
format
(
sub_module
))
mkdir_p
(
os
.
path
.
join
(
module_dir
,
module
,
sub_module
))
with
open
(
os
.
path
.
join
(
module_dir
,
module
,
sub_module
,
'__init__.py'
),
'w'
,
)
as
component_sub_module_init_f
:
for
brain
in
portal
.
portal_catalog
(
portal_type
=
portal_type
,
validation_state
=
(
'validated'
,)):
component
=
brain
.
getObject
()
component_sub_module_init_f
.
write
(
"from {component_reference} import {component_reference}
\
n
"
.
format
(
component_reference
=
component
.
getReference
()))
with
open
(
os
.
path
.
join
(
module_dir
,
module
,
sub_module
,
'{}.py'
.
format
(
component
.
getReference
()),
),
'w'
,
)
as
component_f
:
component_f
.
write
(
component
.
getTextContent
())
#.encode('utf-8'))
return
'done'
bt5/erp5_monaco_editor/ExtensionTemplateItem/portal_components/extension.erp5.Jedi.xml
View file @
616ebae2
...
@@ -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>
...
...
bt5/erp5_monaco_editor/SkinTemplateItem/portal_skins/erp5_monaco_editor/ERP5Site_dumpModuleCode.xml
0 → 100644
View file @
616ebae2
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"ExternalMethod"
module=
"Products.ExternalMethod.ExternalMethod"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
_function
</string>
</key>
<value>
<string>
ERP5Site_dumpModuleCode
</string>
</value>
</item>
<item>
<key>
<string>
_module
</string>
</key>
<value>
<string>
Jedi
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
ERP5Site_dumpModuleCode
</string>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string></string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_monaco_editor/SkinTemplateItem/portal_skins/erp5_monaco_editor/ERP5Site_getPortalStub.xml
0 → 100644
View file @
616ebae2
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"ExternalMethod"
module=
"Products.ExternalMethod.ExternalMethod"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
_function
</string>
</key>
<value>
<string>
ERP5Site_getPortalStub
</string>
</value>
</item>
<item>
<key>
<string>
_module
</string>
</key>
<value>
<string>
Jedi
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
ERP5Site_getPortalStub
</string>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string></string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_monaco_editor/SkinTemplateItem/portal_skins/erp5_monaco_editor/PropertySheetTool_getStub.xml
0 → 100644
View file @
616ebae2
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"ExternalMethod"
module=
"Products.ExternalMethod.ExternalMethod"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
_function
</string>
</key>
<value>
<string>
PropertySheetTool_getStub
</string>
</value>
</item>
<item>
<key>
<string>
_module
</string>
</key>
<value>
<string>
Jedi
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
PropertySheetTool_getStub
</string>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string></string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_monaco_editor/SkinTemplateItem/portal_skins/erp5_monaco_editor/PropertySheet_getStub.xml
0 → 100644
View file @
616ebae2
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"ExternalMethod"
module=
"Products.ExternalMethod.ExternalMethod"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
_function
</string>
</key>
<value>
<string>
PropertySheet_getStub
</string>
</value>
</item>
<item>
<key>
<string>
_module
</string>
</key>
<value>
<string>
Jedi
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
PropertySheet_getStub
</string>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string></string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_monaco_editor/SkinTemplateItem/portal_skins/erp5_monaco_editor/SkinsTool_getStub.xml
0 → 100644
View file @
616ebae2
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"ExternalMethod"
module=
"Products.ExternalMethod.ExternalMethod"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
_function
</string>
</key>
<value>
<string>
SkinsTool_getStub
</string>
</value>
</item>
<item>
<key>
<string>
_module
</string>
</key>
<value>
<string>
Jedi
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
SkinsTool_getStub
</string>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string></string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_monaco_editor/SkinTemplateItem/portal_skins/erp5_monaco_editor/TypeInformation_getStub.xml
0 → 100644
View file @
616ebae2
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"ExternalMethod"
module=
"Products.ExternalMethod.ExternalMethod"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
_function
</string>
</key>
<value>
<string>
TypeInformation_getStub
</string>
</value>
</item>
<item>
<key>
<string>
_module
</string>
</key>
<value>
<string>
Jedi
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
TypeInformation_getStub
</string>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string></string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_monaco_editor/SkinTemplateItem/portal_skins/erp5_monaco_editor/TypesTool_getStub.xml
0 → 100644
View file @
616ebae2
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"ExternalMethod"
module=
"Products.ExternalMethod.ExternalMethod"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
_function
</string>
</key>
<value>
<string>
TypesTool_getStub
</string>
</value>
</item>
<item>
<key>
<string>
_module
</string>
</key>
<value>
<string>
Jedi
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
TypesTool_getStub
</string>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string></string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment