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
Frederic Thoma
erp5
Commits
39fd2694
Commit
39fd2694
authored
Mar 11, 2020
by
Jérome Perrin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
monaco_editor: jedi WIP
parent
1e04bbac
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
888 additions
and
516 deletions
+888
-516
bt5/erp5_monaco_editor/ExtensionTemplateItem/portal_components/extension.erp5.Jedi.py
...sionTemplateItem/portal_components/extension.erp5.Jedi.py
+844
-484
bt5/erp5_monaco_editor/ExtensionTemplateItem/portal_components/extension.erp5.Jedi.xml
...ionTemplateItem/portal_components/extension.erp5.Jedi.xml
+20
-16
bt5/erp5_monaco_editor/ExtensionTemplateItem/portal_components/extension.erp5.YAPF.py
...sionTemplateItem/portal_components/extension.erp5.YAPF.py
+4
-0
bt5/erp5_monaco_editor/ExtensionTemplateItem/portal_components/extension.erp5.YAPF.xml
...ionTemplateItem/portal_components/extension.erp5.YAPF.xml
+20
-16
No files found.
bt5/erp5_monaco_editor/ExtensionTemplateItem/portal_components/extension.erp5.Jedi.py
View file @
39fd2694
# coding: utf-8
# TODO: drop this ? it confuse type checking this file
from
__future__
import
unicode_literals
import
json
import
sys
import
inspect
# pylint: disable=unused-import
from
typing
import
List
,
Type
,
Optional
,
Dict
,
Tuple
,
Sequence
,
TYPE_CHECKING
import
typing
import
logging
from
threading
import
RLock
from
Products.ERP5Type.Cache
import
transactional_cached
logger
=
logging
.
getLogger
(
"erp5.extension.Jedi"
)
logger
.
setLevel
(
logging
.
DEBUG
)
import
os
import
jedi
import
time
import
erp5.portal_type
last_reload_time
=
time
.
time
()
# increase default cache duration
...
...
@@ -42,9 +52,6 @@ from jedi.evaluate.gradual.typing import InstanceWrapper
from
jedi.evaluate.lazy_context
import
LazyKnownContexts
from
jedi.evaluate.base_context
import
ContextSet
,
NO_CONTEXTS
if
typing
.
TYPE_CHECKING
:
import
erp5.portal_type.ERP5Site
# pylint: disable=unused-import,no-name-in-module,import-error
def
executeJediXXX
(
callback
,
context
,
arguments
):
# XXX function for relaodability
...
...
@@ -55,8 +62,7 @@ def executeJediXXX(callback, context, 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
,
"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
()):
...
...
@@ -68,9 +74,9 @@ def executeJediXXX(callback, context, arguments):
if
filter_func
(
wrapped
):
return
True
return
False
annotation_classes
=
val
.
gather_annotation_classes
()
#import pdb; pdb.set_trace()
return
val
.
gather_annotation_classes
().
filter
(
filter_func
)
##
annotation_classes = val.gather_annotation_classes()
#
#
import pdb; pdb.set_trace()
##
return val.gather_annotation_classes().filter(filter_func)
logger
.
info
(
"not found in %s"
,
val
)
return
False
...
...
@@ -108,8 +114,7 @@ def executeJediXXX(callback, context, arguments):
# {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
)
filtered
,
original
)
return
filtered
# methods returning List of portal types
...
...
@@ -190,20 +195,23 @@ _TYPE_MAP = {
def
_label
(
definition
):
# type: (jedi.api.classes.Completion,) -> str
if
definition
.
type
==
'param'
:
return
'{}='
.
format
(
definition
.
name
)
#
if definition.type == 'param':
#
return '{}='.format(definition.name)
if
definition
.
type
in
(
'function'
,
'method'
)
and
hasattr
(
definition
,
'params'
):
params
=
', '
.
join
([
param
.
name
for
param
in
definition
.
params
])
return
'{}({})'
.
format
(
definition
.
name
,
params
)
return
definition
.
name
def
_insertText
(
definition
):
# type: (jedi.api.classes.Completion,) -> str
if
definition
.
type
==
'param'
:
return
'{}='
.
format
(
definition
.
name
)
# XXX
#if definition.type == 'param':
# return '{}='.format(definition.name)
return
definition
.
name
def
_detail
(
definition
):
try
:
return
definition
.
parent
().
full_name
or
''
...
...
@@ -220,17 +228,21 @@ def _sort_text(definition):
return
prefix
.
format
(
definition
.
name
)
def
_format_docstring
(
docstring
):
return
docstring
def
_format_docstring
(
d
):
try
:
return
d
.
docstring
()
except
Exception
as
e
:
logger
.
exception
(
'error getting completions from %s'
,
d
)
return
"```{}```"
.
format
(
repr
(
e
))
def
_format_completion
(
d
):
# type: (jedi.api.classes.Completion,) ->
typing.Dict[str, str
]
# type: (jedi.api.classes.Completion,) ->
Dict[str, Optional[str]
]
completion
=
{
'label'
:
_label
(
d
),
'_kind'
:
_TYPE_MAP
.
get
(
d
.
type
),
'detail'
:
_detail
(
d
),
'documentation'
:
_format_docstring
(
d
.
docstring
()
),
'documentation'
:
_format_docstring
(
d
),
'sortText'
:
_sort_text
(
d
),
'insertText'
:
_insertText
(
d
),
}
...
...
@@ -247,7 +259,8 @@ def _guessType(name, context_type=None):
return
context_type
if
name
in
(
'context'
,
'container'
,):
'container'
,
):
return
'erp5.portal_type.ERP5Site'
if
name
==
'script'
:
return
'Products.PythonScripts.PythonScript'
...
...
@@ -272,10 +285,17 @@ def ERP5Site_getPythonSourceCodeCompletionList(self, data, REQUEST=None):
"""
portal
=
self
.
getPortalObject
()
logger
.
debug
(
'jedi get lock %s (%s)'
,
jedi_lock
,
id
(
jedi_lock
))
if
not
jedi_lock
.
acquire
(
False
):
for
_
in
range
(
10
):
locked
=
not
jedi_lock
.
acquire
(
False
)
if
locked
:
time
.
sleep
(.
5
)
else
:
jedi_lock
.
release
()
break
else
:
raise
RuntimeError
(
'jedi is locked'
)
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
):
...
...
@@ -307,24 +327,20 @@ def ERP5Site_getPythonSourceCodeCompletionList(self, data, REQUEST=None):
if
context_type
not
in
[
ti
.
replace
(
' '
,
''
)
for
ti
in
portal
.
portal_types
.
objectIds
()]
+
[
'ERP5Site'
,]:
'ERP5Site'
,
]:
logger
.
warning
(
"context_type %s has no portal type, using ERP5Site"
,
context_type
)
"context_type %s has no portal type, using ERP5Site"
,
context_type
)
context_type
=
None
else
:
context_type
=
'erp5.portal_type.{}'
.
format
(
context_type
)
imports
=
"import erp5.portal_type; import Products.ERP5Type.Core.Folder; import ZPublisher.HTTPRequest; import Products.PythonScripts"
type_annotation
=
" # type: ({}) -> None"
.
format
(
', '
.
join
(
[
_guessType
(
part
,
context_type
)
for
part
in
signature_parts
]))
', '
.
join
([
_guessType
(
part
,
context_type
)
for
part
in
signature_parts
]))
body
=
"%s
\
n
def %s(%s):
\
n
%s
\
n
%s"
%
(
imports
,
script_name
,
signature
,
type_annotation
,
indent
(
data
[
'code'
])
or
" pass"
)
imports
,
script_name
,
signature
,
type_annotation
,
indent
(
data
[
'code'
])
or
" pass"
)
data
[
'position'
][
'line'
]
=
data
[
'position'
][
'line'
]
+
3
# imports, fonction header + type annotation line
data
[
'position'
][
...
...
@@ -332,6 +348,7 @@ def ERP5Site_getPythonSourceCodeCompletionList(self, data, REQUEST=None):
else
:
body
=
data
[
'code'
]
with
jedi_lock
:
logger
.
debug
(
"jedi getting completions for %s ..."
,
script_name
)
start
=
time
.
time
()
script
=
jedi
.
Script
(
...
...
@@ -342,15 +359,77 @@ def ERP5Site_getPythonSourceCodeCompletionList(self, data, REQUEST=None):
sys_path
=
[
'/tmp/ahaha/'
]
+
list
(
sys
.
path
),
)
completions
=
[
_format_completion
(
c
)
for
c
in
script
.
completions
()]
def
_get_param_name
(
p
):
if
(
p
.
name
.
startswith
(
'param '
)):
return
p
.
name
[
6
:]
# drop leading 'param '
return
p
.
name
def
_get_param_value
(
p
):
pair
=
p
.
description
.
split
(
'='
)
if
(
len
(
pair
)
>
1
):
return
pair
[
1
]
return
None
completions
=
[]
signature_completions
=
set
()
try
:
signatures
=
[]
call_signatures
=
script
.
call_signatures
()
logger
.
info
(
"jedi got %d completions in %.2fs"
,
len
(
completions
),
(
time
.
time
()
-
start
))
"jedi first got %d call signatures in %.2fs"
,
len
(
call_signatures
),
(
time
.
time
()
-
start
))
for
signature
in
call_signatures
:
for
pos
,
param
in
enumerate
(
signature
.
params
):
if
not
param
.
name
:
continue
name
=
_get_param_name
(
param
)
if
param
.
name
==
'self'
and
pos
==
0
:
continue
if
name
.
startswith
(
'*'
):
continue
value
=
_get_param_value
(
param
)
signatures
.
append
((
signature
,
name
,
value
))
for
signature
,
name
,
value
in
signatures
:
completion
=
{
'label'
:
'{}='
.
format
(
name
),
'_kind'
:
'Variable'
,
'detail'
:
value
,
#'documentation': value,
'sortText'
:
'aaaaa_{}'
.
format
(
name
),
'insertText'
:
'{}='
.
format
(
name
),
}
completions
.
append
(
completion
)
signature_completions
.
add
(
name
)
except
Exception
:
logger
.
exception
(
"Error getting call signatures"
)
completions
.
extend
(
_format_completion
(
c
)
for
c
in
script
.
completions
()
if
c
.
name
not
in
signature_completions
)
logger
.
info
(
"jedi got %d completions in %.2fs"
,
len
(
completions
),
(
time
.
time
()
-
start
))
if
data
.
get
(
'xxx_hover'
):
completions
=
''
# XXX this is not "completions" ...
for
definition
in
script
.
goto_definitions
():
completions
=
definition
.
docstring
()
documentation_lines
=
definition
.
docstring
().
splitlines
()
# reformat this in nicer markdown
completions
=
textwrap
.
dedent
(
'''
\
`{}`
---
{}
'''
).
format
(
documentation_lines
[
0
],
'
\
n
'
.
join
(
documentation_lines
[
1
:]),
)
logger
.
info
(
'hover: %s'
,
completions
)
if
REQUEST
is
not
None
:
REQUEST
.
RESPONSE
.
setHeader
(
'content-type'
,
'application/json'
)
return
json
.
dumps
(
completions
)
...
...
@@ -374,7 +453,7 @@ def safe_docstring(docstring):
"""
if
not
docstring
:
return
'...'
return
"'''{}'''"
.
format
(
docstring
.
replace
(
"'''"
,
r"\'\'\'"
))
return
"'''{}
\
n
'''"
.
format
(
docstring
.
replace
(
"'''"
,
r"\'\'\'"
))
from
Products.ERP5Type.Accessor
import
Constant
...
...
@@ -413,11 +492,18 @@ def SkinsTool_getStubForClass(self, class_name):
# collect skins by type
skin_by_type
=
defaultdict
(
list
)
# TODO: sort by default skin selection and use only the ones registered in skin selections
# TODO: don't make this silly loop for all classes ? or maybe keep it - it could be useful
# when we are able to regenerate only what was changed.
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
())
logger
.
debug
(
'Skipping script without prefix %s'
,
script
.
getId
())
continue
# TODO: understand more invalid characters (use a regex)
if
" "
in
script
.
getId
()
or
"."
in
script
.
getId
():
logger
.
debug
(
'Skipping script with invalid characters %s'
,
script
.
getId
())
continue
type_
=
script
.
getId
().
split
(
'_'
)[
0
]
if
type_
!=
class_name
:
...
...
@@ -446,6 +532,7 @@ def SkinsTool_getStubForClass(self, class_name):
if
next
(
iter
(
grammar
.
iter_errors
(
module
)),
None
)
is
not
None
:
first_leaf
=
module
.
get_first_leaf
()
type_comment
=
first_leaf
.
prefix
.
strip
()
# TODO: adjust type comment ?
if
not
type_coment_re
.
match
(
type_comment
):
type_comment
=
''
else
:
...
...
@@ -469,18 +556,17 @@ def SkinsTool_getStubForClass(self, class_name):
skin_by_type
[
type_
].
append
(
SkinDefinition
(
script
.
getId
(),
docstring
,
type_comment
,
skin_folder
.
getId
(),
script
.
getId
(),
docstring
,
type_comment
,
skin_folder
.
getId
(),
params
))
# TODO: this loop is nonsense.
for
type_
,
skins
in
skin_by_type
.
items
():
line_list
.
append
(
textwrap
.
dedent
(
"""
\
# coding: utf-8
import erp5.portal_type
from erp5 import portal_type
import typing
class {class_name}:
{docstring}
...
...
@@ -488,8 +574,16 @@ def SkinsTool_getStubForClass(self, class_name):
"""
).
format
(
class_name
=
safe_python_identifier
(
type_
),
docstring
=
safe_docstring
(
"Skins for {}"
.
format
(
type_
))))
# TODO: we just ignore duplicated scripts, but it would be better to use @typing.overload
defined_methods
=
set
([])
for
skin
in
skins
:
skin
=
skin
# type: SkinDefinition
if
skin
.
id
in
defined_methods
:
logger
.
debug
(
"Skipping duplicated skin %s while defining erp5.skins_tool.%s"
,
skin
.
id
,
type_
)
continue
defined_methods
.
add
(
skin
.
id
)
line_list
.
append
(
# the comment is also here so that dedent keep indentation, because this method block needs
# more indentation than class block
...
...
@@ -507,6 +601,115 @@ def SkinsTool_getStubForClass(self, class_name):
return
"
\
n
"
.
join
(
line_list
)
@
WorkflowMethod
.
disable
def
makeTempClass
(
portal
,
portal_type
):
# type: (erp5.portal_type.ERP5Site, str) -> Type[Products.ERP5Type.Base.Base]
return
portal
.
newContent
(
portal_type
=
portal_type
,
temp_object
=
True
,
id
=
'?'
,
title
=
'?'
,
).
__class__
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'
:
'Sequence[str]'
,
'tokens'
:
'Sequence[str]'
,
'float'
:
'float'
,
'text'
:
'str'
,
}.
get
(
property_sheet_type
,
'Any'
)
if
prop
.
isMultivalued
()
\
and
property_sheet_type
not
in
(
'lines'
,
'token'
):
# XXX see Resource/p_variation_base_category_property, we can have multivalued lines properties
return
'Sequence[{}]'
.
format
(
mapped_type
)
return
mapped_type
def
_isMultiValuedProperty
(
prop
):
# type: (erp5.portal_type.StandardProperty,) -> bool
"""If this is a multi valued property, we have to generate list accessor.
"""
if
prop
.
isMultivalued
():
return
True
return
prop
.
getElementaryType
()
in
(
'lines'
,
'tokens'
)
@
transactional_cached
()
def
TypeInformation_getEditParameterDict
(
self
):
# type: (ERP5TypeInformation) -> Dict[str, Tuple[str, str]]
"""returns a mapping of properties that can be set on this type by edit or newContent
The returned data format is tuples containing documentation and type annotations,
keyed by parameter, like:
{ "title": ("The title of the document", "str") }
Python has a limitation on the number of arguments in a function, to prevent
SyntaxError: more than 255 arguments
we only generate the most common ones.
"""
portal
=
self
.
getPortalObject
()
property_dict
=
{}
# type: Dict[str, Tuple[str, str]]
temp_class
=
makeTempClass
(
portal
,
self
.
getId
())
for
property_sheet_id
in
[
parent_class
.
__name__
for
parent_class
in
temp_class
.
mro
()
if
parent_class
.
__module__
==
'erp5.accessor_holder.property_sheet'
]:
property_sheet
=
portal
.
portal_property_sheets
[
property_sheet_id
]
for
prop
in
property_sheet
.
contentValues
():
if
not
prop
.
getReference
():
continue
if
prop
.
getPortalType
()
in
(
'Standard Property'
,
'Acquired Property'
):
property_dict
[(
'{}_list'
if
_isMultiValuedProperty
(
prop
)
else
'{}'
).
format
(
prop
.
getReference
())]
=
(
prop
.
getDescription
(),
_getPythonTypeFromPropertySheetType
(
prop
))
elif
prop
.
getPortalType
()
in
(
'Category Property'
,
'Dynamic Category Property'
,
):
# XXX only generate a few
# property_dict['{}'.format(
# prop.getReference())] = (prop.getDescription(), 'str')
# property_dict['{}_list'.format(
# prop.getReference())] = (prop.getDescription(), 'Sequence[str]')
property_dict
[
'{}_value'
.
format
(
prop
.
getReference
())]
=
(
prop
.
getDescription
(),
'"erp5.portal_type.Type_AnyPortalType"'
)
# property_dict['{}_value_list'.format(prop.getReference())] = (
# prop.getDescription(),
# 'Sequence["erp5.portal_type.Type_AnyPortalType"]')
elif
prop
.
getPortalType
()
==
'Dynamic Category Property'
:
# TODO
pass
return
property_dict
def
XXX_skins_class_exists
(
name
):
# type: (str) -> bool
"""Returns true if a skin class exists for this name.
"""
return
os
.
path
.
exists
(
"/tmp/ahaha/erp5/skins_tool/{name}.pyi"
.
format
(
name
=
name
))
def
TypeInformation_getStub
(
self
):
# type: (ERP5TypeInformation) -> str
"""returns a .pyi stub file for this portal type
...
...
@@ -514,18 +717,11 @@ def TypeInformation_getStub(self):
https://www.python.org/dev/peps/pep-0484/
"""
portal
=
self
.
getPortalObject
()
portal_url
=
portal
.
absolute_url
()
# 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
()
temp_class
=
makeTempClass
(
portal
,
self
.
getId
())
# mro() of temp objects is like :
# (<class 'erp5.temp_portal_type.Temporary Person Module'>,
...
...
@@ -538,12 +734,17 @@ def TypeInformation_getStub(self):
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'
,
imports
=
set
(
[
'from Products.ERP5Type.Base import Base as Products_ERP5Type_Base_Base'
,
'import erp5.portal_type'
,
# TODO use "" style type definition without importing
# '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'
,
'from DateTime.DateTime import DateTime as DateTime # XXX help jedi'
,
# 'TranslatedMessage = str # TODO: this is type for translations ( Products.ERP5Type.Message.translateString should return this )'
])
header
=
""
methods
=
[]
...
...
@@ -555,30 +756,18 @@ def TypeInformation_getStub(self):
decorator
=
''
,
method_name
=
'getPortalType'
,
method_args
=
"self"
,
return_type
=
'Literal["{}"]'
.
format
(
self
.
getId
()),
return_type
=
'Literal[
b
"{}"]'
.
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
(
docstring
=
"{}
\
n
return b'{}'"
.
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
'
,
return_type
=
'
"ERP5Site"
'
,
docstring
=
safe_docstring
(
getattr
(
temp_class
.
getPortalObject
,
'__doc__'
,
None
)
or
'...'
)))
...
...
@@ -588,7 +777,9 @@ def TypeInformation_getStub(self):
continue
property_value
=
getattr
(
temp_class
,
property_name
)
if
isinstance
(
property_value
,
Constant
.
Getter
):
# XXX skipped for now, too many methods that are not so useful
# TODO: add an implementation returning the value so that jedi can infer
if
0
:
methods
.
append
(
method_template_template
.
format
(
decorator
=
''
,
...
...
@@ -596,30 +787,82 @@ def TypeInformation_getStub(self):
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
,
elif
isinstance
(
property_value
,
(
# we don't generate for TitleGetter and TranslatedGetter because they are useless
WorkflowState
.
TranslatedTitleGetter
,
WorkflowState
.
Getter
)):
# TODO: docstring (with link to workflow)
WorkflowState
.
Getter
,
)):
workflow_id
=
property_value
.
_key
workflow_url
=
'{portal_url}/portal_workflow/{workflow_id}'
.
format
(
portal_url
=
portal_url
,
workflow_id
=
workflow_id
,
)
docstring
=
"State on [{workflow_id}]({workflow_url}/manage_main)
\
n
"
.
format
(
workflow_id
=
workflow_id
,
workflow_url
=
workflow_url
,
)
if
isinstance
(
property_value
,
WorkflowState
.
Getter
):
docstring
+=
"
\
n
---
\
n
"
docstring
+=
" | State ID | State Name |
\
n
"
docstring
+=
" | --- | --- |
\
n
"
for
state
in
portal
.
portal_workflow
[
workflow_id
].
states
.
objectValues
():
docstring
+=
" | {state_id} | [{state_title}]({workflow_url}/states/{state_id}/manage_properties) |
\
n
"
.
format
(
state_id
=
state
.
getId
(),
state_title
=
state
.
title_or_id
(),
workflow_url
=
workflow_url
,
)
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
)))
docstring
=
safe_docstring
(
docstring
)))
elif
isinstance
(
property_value
,
WorkflowMethod
):
# TODO: docstring (with link to workflow)
docstring
=
""
method_args
=
"self, comment:TranslatedMessage=None, **kw:Any"
return_type
=
'None'
if
hasattr
(
parent_class
,
property_name
):
parent_method
=
getattr
(
parent_class
,
property_name
)
docstring
=
inspect
.
getdoc
(
parent_method
)
+
"
\
n
\
n
--
\
n
"
method_args
=
"self, *args:Any, **kw:Any"
return_type
=
'Any'
if
(
property_name
.
startswith
(
"manage_"
)
or
property_name
.
startswith
(
"set"
)
or
property_name
.
startswith
(
"get"
)):
logger
.
debug
(
"Skipping workflow method %s wrapping existing %s (types: %s)"
,
property_name
,
parent_method
,
typing
.
get_type_hints
(
parent_method
))
continue
# 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).
# or maybe not because:
# TODO: only do this for REAL workflow method, not interaction workflow wrap?
# issue is that we loose the type information of wrapped method
for
workflow_id
,
transition_list
in
property_value
.
_invoke_always
.
get
(
temp_class
.
__name__
,
{}).
items
():
workflow_url
=
'{portal_url}/portal_workflow/{workflow_id}'
.
format
(
portal_url
=
portal_url
,
workflow_id
=
workflow_id
,
)
for
transition_id
in
transition_list
:
docstring
+=
"Transition [{transition_id}]({workflow_url}/transitions/{transition_id}/manage_properties) on [{workflow_id}]({workflow_url}/manage_main)
\
n
\
n
"
.
format
(
transition_id
=
transition_id
,
workflow_id
=
workflow_id
,
workflow_url
=
workflow_url
,
)
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
)))
method_args
=
method_args
,
return_type
=
return_type
,
docstring
=
safe_docstring
(
docstring
)))
elif
property_name
.
startswith
(
'serialize'
):
# isinstance(property_value, WorkflowState.SerializeGetter): XXX not a class..
...
...
@@ -634,81 +877,120 @@ def TypeInformation_getStub(self):
# TODO: generated methods for categories.
else
:
debug
+=
"
\
n
# not handled property: {} -> {} {}"
.
format
(
property_name
,
property_value
,
property_name
,
property_value
,
getattr
(
property_value
,
'__dict__'
,
''
))
# for folderish contents, generate typed contentValues
# for folderish contents, generate typed contentValues
and other folderish methods
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
])
multiple_allowed_content_types
=
len
(
allowed_content_types
)
>
1
# TODO generate contentValues() without portal_type argument
for
allowed_content_type
in
allowed_content_types
:
if
multiple_allowed_content_types
:
new_content_portal_type_type_annotation
=
'Literal[b"{allowed_content_type}"]'
.
format
(
allowed_content_type
=
allowed_content_type
)
else
:
subdocument_type
=
'Union[{}]'
.
format
(
', '
.
join
(
allowed_content_types_classes
))
new_content_portal_type_type_annotation
=
'str="{allowed_content_type}"'
.
format
(
allowed_content_type
=
allowed_content_type
)
subdocument_type
=
'"{}"'
.
format
(
safe_python_identifier
(
allowed_content_type
))
# TODO: getParentValue
new_content_method_arg
=
"self, portal_type:{new_content_portal_type_type_annotation}"
.
format
(
new_content_portal_type_type_annotation
=
new_content_portal_type_type_annotation
)
parameters_by_parameter_name
=
defaultdict
(
list
)
for
prop
,
prop_def
in
TypeInformation_getEditParameterDict
(
portal
.
portal_types
[
allowed_content_type
]).
items
():
parameters_by_parameter_name
[
prop
].
append
(
(
allowed_content_type
,
prop_def
))
if
parameters_by_parameter_name
:
new_content_method_arg
+=
',
\
n
'
for
prop
,
prop_defs
in
sorted
(
parameters_by_parameter_name
.
items
()):
# XXX we could build a better documentation with this prop_def, but no tools seems to understand this.
# XXX can we assume that all properties have same types ? shouldn't we build unions ?
param_type
=
prop_defs
[
0
][
1
][
1
]
new_content_method_arg
+=
' {}:{} = None,
\
n
'
.
format
(
safe_python_identifier
(
prop
),
param_type
,
)
methods
.
append
(
method_template_template
.
format
(
decorator
=
'@overload'
if
multiple_allowed_content_types
else
''
,
method_name
=
'newContent'
,
method_args
=
new_content_method_arg
,
return_type
=
subdocument_type
,
docstring
=
safe_docstring
(
getattr
(
temp_class
.
newContent
,
'__doc__'
,
None
))))
# TODO: getParentValue
method_args
=
'self, portal_type:str="{allowed_content_type}"'
.
format
(
allowed_content_type
=
allowed_content_type
,)
if
multiple_allowed_content_types
:
method_args
=
'self, portal_type:Literal[b"{allowed_content_type}"]'
.
format
(
allowed_content_type
=
allowed_content_type
,)
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
:
return_type
=
'
Sequence
[{}]'
.
format
(
subdocument_type
)
if
0
and
method_name
==
'searchFolder'
:
# TODO searchFolder is different, it returns brain and accepts **kw
return_type
=
'
Sequence
[Type_CatalogBrain[{}]]'
.
format
(
subdocument_type
)
if
multiple_allowed_content_types
:
# 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
)))
', '
.
join
(
(
'Sequence[Type_CatalogBrain["erp5.portal_type.{}"]]'
# TODO
.
format
(
t
)
for
t
in
'allowed_content_types_classes'
)))
methods
.
append
(
method_template_template
.
format
(
decorator
=
''
,
decorator
=
'
@overload'
if
multiple_allowed_content_types
else
'
'
,
method_name
=
method_name
,
method_args
=
"self"
,
method_args
=
method_args
,
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
))))
subdocument_type
=
'None'
if
allowed_content_types
:
subdocument_type
=
'"{}"'
.
format
(
safe_python_identifier
(
allowed_content_types
[
0
]))
if
multiple_allowed_content_types
:
subdocument_type
=
'Union[{}]'
.
format
(
', '
.
join
(
'"{}"'
.
format
(
safe_python_identifier
(
allowed_content_type
))
for
allowed_content_type
in
allowed_content_types
))
# getattr, getitem and other Zope.OFS alai
s returns an instance of allowed content types.
# getattr, getitem and other Zope.OFS alia
s returns an instance of allowed content types.
# so that portal.person_module['1'] is a person
for
method_name
in
(
'__getattr__'
,
'__getitem__'
,
'_getOb'
,
'get'
,):
'get'
,
):
# TODO: some accept default=None !
methods
.
append
(
method_template_template
.
format
(
decorator
=
''
,
method_name
=
method_name
,
method_args
=
"self, attribute:str
"
,
method_args
=
"self, attribute:str, default:Any=None
"
,
return_type
=
subdocument_type
,
docstring
=
'...'
))
# TODO not true for __of__(context) and asContent(**kw)
for
identity_method
in
(
'getObject'
,
'asContext'
,
'__of__'
,):
'__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__
),
method_args
=
"self"
,
return_type
=
'"{}"'
.
format
(
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
...
...
@@ -727,24 +1009,30 @@ def TypeInformation_getStub(self):
base_classes
.
append
(
prefixed_class_name
)
# Fake name for skins
prefixed_class_name
=
'skins_tool_{}'
.
format
(
safe_python_identifier
(
pc
.
__name__
))
if
XXX_skins_class_exists
(
pc
.
__name__
):
class_name
=
safe_python_identifier
(
pc
.
__name__
)
prefixed_class_name
=
'skins_tool_{class_name}'
.
format
(
class_name
=
class_name
)
imports
.
add
(
'from erp5.skins_tool import {} as {}'
.
format
(
safe_python_identifier
(
pc
.
__name__
),
prefixed_class_name
))
'from erp5.skins_tool.{class_name} import {class_name} as {prefixed_class_name}'
.
format
(
class_name
=
class_name
,
prefixed_class_name
=
prefixed_class_name
))
if
prefixed_class_name
not
in
base_classes
:
base_classes
.
append
(
prefixed_class_name
)
# everything can use ERP5Site_ skins
imports
.
add
(
'from erp5.skins_tool import ERP5Site as skins_tool_ERP5Site'
)
if
'skins_tool_ERP5Site'
not
in
base_classes
:
imports
.
add
(
'from erp5.skins_tool.ERP5Site 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}):
class {class_name}(
{base_classes}):
{docstring}
{methods}
{debug}
...
...
@@ -752,11 +1040,12 @@ def TypeInformation_getStub(self):
docstring
=
textwrap
.
dedent
(
'''
# {type_title_or_id}
## [{type_title_or_id}](type_url)
---
{type_description}
{type_url}
'''
).
format
(
type_title_or_id
=
self
.
getTitleOrId
(),
type_description
=
self
.
getDescription
(),
...
...
@@ -767,7 +1056,7 @@ def TypeInformation_getStub(self):
header
=
header
,
docstring
=
safe_docstring
(
docstring
),
class_name
=
safe_python_identifier
(
temp_class
.
__name__
),
base_classes
=
', '
.
join
(
base_classes
),
base_classes
=
',
\
n
'
.
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
),
...
...
@@ -800,7 +1089,6 @@ def PropertySheet_getStub(self):
class_template
=
textwrap
.
dedent
(
"""
\
{imports}
class {class_name}:
'''{property_sheet_id}
...
...
@@ -811,56 +1099,8 @@ def PropertySheet_getStub(self):
"""
)
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
()
\
and
property_sheet_type
not
in
(
'lines'
,
'token'
):
# XXX see Resource/p_variation_base_category_property, we can have multivalued lines properties
return
'List[{}]'
.
format
(
mapped_type
)
return
mapped_type
def
_isMultiValuedProperty
(
prop
):
# type: (erp5.portal_type.StandardProperty,) -> str
"""If this is a multi valued property, we have to generate list accessor.
"""
if
prop
.
isMultivalued
():
return
True
return
prop
.
getElementaryType
()
in
(
'lines'
,
'tokens'
)
method_template_template
=
""" def {method_name}({method_args}) -> {return_type}:
\
n
{docstring}"""
from
Products.ERP5Type.Utils
import
convertToUpperCase
from
Products.ERP5Type.Utils
import
evaluateExpressionFromString
...
...
@@ -868,6 +1108,13 @@ def PropertySheet_getStub(self):
expression_context
=
createExpressionContext
(
self
)
for
prop
in
self
.
contentValues
():
# XXX skip duplicate property
# TODO: how about just removing this from business templates ?
if
self
.
getId
()
==
'Resource'
and
prop
.
getReference
()
in
(
'destination_title'
,
'source_title'
):
logger
.
debug
(
"Skipping Resource duplicate property %s"
,
prop
.
getRelativeUrl
())
continue
if
prop
.
getPortalType
()
in
(
'Standard Property'
,
'Acquired Property'
):
docstring
=
safe_docstring
(
...
...
@@ -920,6 +1167,9 @@ def PropertySheet_getStub(self):
category_value
=
portal_categories
.
_getOb
(
category
,
None
)
if
category_value
is
None
:
continue
# XXX size category clashes with size accessor from Data propertysheet
if
category
in
(
'size'
,):
continue
docstring
=
safe_docstring
(
textwrap
.
dedent
(
...
...
@@ -966,14 +1216,14 @@ def PropertySheet_getStub(self):
method_name
=
'get{}Value'
.
format
(
convertToUpperCase
(
category_value
.
getId
())),
method_args
=
'self'
,
return_type
=
'
Type_AnyPortalType
'
,
return_type
=
'
"erp5.portal_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
'
,
return_type
=
'
"erp5.portal_type.Type_AnyPortalTypeList"
'
,
docstring
=
docstring
))
methods
.
append
(
method_template_template
.
format
(
...
...
@@ -986,19 +1236,18 @@ def PropertySheet_getStub(self):
method_template_template
.
format
(
method_name
=
'set{}Value'
.
format
(
convertToUpperCase
(
category_value
.
getId
())),
method_args
=
'self, value:
Base
'
,
method_args
=
'self, value:
"erp5.portal_type.Type_AnyPortalType"
'
,
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]
'
,
method_args
=
'self, value_list:
"erp5.portal_type.Type_AnyPortalTypeList"
'
,
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
(
...
...
@@ -1014,20 +1263,23 @@ def ERP5Site_getPortalStub(self):
module_stub_template
=
textwrap
.
dedent
(
'''
@property
def {module_id}(self):
from erp5.portal_type import {module_class_name}
return {module_class_name}()
def {module_id}(self)
-> '{module_class_name}'
:
...
#
return {module_class_name}()
'''
)
tool_stub_template
=
textwrap
.
dedent
(
'''
@property
def {tool_id}(self):
{tool_import}
return
{tool_class}()
def {tool_id}(self)
-> 'tool_{tool_id}_{tool_class}'
:
...
#return tool_{tool_id}_
{tool_class}()
'''
)
source
=
[]
imports
=
[]
from
Acquisition
import
aq_base
for
m
in
self
.
objectValues
():
if
m
.
getPortalType
().
endswith
(
'Modul
e'
):
if
hasattr
(
aq_base
(
m
),
'getPortalTyp
e'
):
source
.
extend
(
module_stub_template
.
format
(
module_id
=
m
.
getId
(),
...
...
@@ -1036,33 +1288,40 @@ def ERP5Site_getPortalStub(self):
else
:
tool_class
=
safe_python_identifier
(
m
.
__class__
.
__name__
)
tool_import
=
'from {} import {}'
.
format
(
m
.
__class__
.
__module__
,
tool_class
)
tool_import
=
'from {tool_module} import {tool_class} as tool_{tool_id}_{tool_class}'
.
format
(
tool_module
=
m
.
__class__
.
__module__
,
tool_class
=
tool_class
,
tool_id
=
m
.
getId
(),
)
if
0
:
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'
imports
.
append
(
tool_import
)
source
.
extend
(
tool_stub_template
.
format
(
tool_id
=
m
.
getId
(),
tool_class
=
tool_class
,
tool_import
=
tool_import
).
splitlines
())
tool_id
=
m
.
getId
(),
tool_class
=
tool_class
,
).
splitlines
())
# TODO: tools with at least base categories for CategoryTool
return
textwrap
.
dedent
(
'''
from Products.ERP5.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
from erp5.skins_tool
.ERP5Site
import ERP5Site as skins_tool_ERP5Site
from erp5.skins_tool
.Base
import Base as skins_tool_Base
{imports}
class ERP5Site(ERP5Site_parent_ERP5Site, skins_tool_ERP5Site, skins_tool_Base):
{}
def getPortalObject(self):
{
source
}
def getPortalObject(self)
-> 'ERP5Site'
:
return self
'''
).
format
(
'
\
n
'
.
join
(
source
))
'''
).
format
(
imports
=
'
\
n
'
.
join
(
imports
),
source
=
'
\
n
'
.
join
(
source
))
def
ERP5Site_dumpModuleCode
(
self
,
component_or_script
=
None
):
...
...
@@ -1073,22 +1332,41 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
to files.
"""
def
mkdir_p
(
path
):
# type: (str) -> None
if
not
os
.
path
.
exists
(
path
):
os
.
mkdir
(
path
,
0o700
)
def
writeFile
(
path
,
content
):
# type: (str, str) -> None
"""Write file at `path` with `content`, only if content is different.
"""
if
os
.
path
.
exists
(
path
):
with
open
(
path
)
as
existing_f
:
if
content
==
existing_f
.
read
():
return
with
open
(
path
,
'w'
)
as
f
:
f
.
write
(
content
)
portal
=
self
.
getPortalObject
()
module_dir
=
'/tmp/ahaha/erp5/'
# TODO
mkdir_p
(
module_dir
)
# generate erp5/__init__.py
with
open
(
# mypy wants __init__.pyi jedi wants __init__.py so we generate both
writeFile
(
os
.
path
.
join
(
module_dir
,
'__init__.py'
),
'w'
,)
as
erp5__init__f
:
"# empty __init__ for jedi ... mypy will use __init__.pyi"
)
with
open
(
os
.
path
.
join
(
module_dir
,
'__init__.pyi'
),
'w'
,
)
as
erp5__init__f
:
for
module
in
(
'portal_type'
,
'accessor_holder'
,
'skins_tool'
,
'component'
,):
'component'
,
):
erp5__init__f
.
write
(
'from . import {module}
\
n
'
.
format
(
module
=
module
))
mkdir_p
(
os
.
path
.
join
(
module_dir
,
module
))
if
module
==
'portal_type'
:
...
...
@@ -1098,14 +1376,36 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
os
.
path
.
join
(
module_dir
,
module
,
'__init__.py'
,),
'w'
,)
as
module_f
:
'__init__.pyi'
,
),
'w'
,
)
as
module_f
:
# header
module_f
.
write
(
"# coding: utf-8
\
n
"
)
module_f
.
write
(
'TranslatedMessage = str # TODO: this is type for translations ( Products.ERP5Type.Message.translateString should return this )
\
n
'
)
# ERP5Site
module_f
.
write
(
ERP5Site_getPortalStub
(
self
.
getPortalObject
()))
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
(
'
# from
{class_name} import {class_name}
\
n
'
.
format
(
class_name
=
class_name
))
try
:
stub_code
=
ti
.
TypeInformation_getStub
().
encode
(
'utf-8'
)
except
Exception
as
e
:
logger
.
exception
(
"Could not generate code for %s"
,
ti
.
getId
())
stub_code
=
"""class {class_name}:
\
n
{error}"""
.
format
(
class_name
=
class_name
,
error
=
safe_docstring
(
"Error trying to create {}: {} {}"
.
format
(
ti
.
getId
(),
e
.
__class__
,
e
)))
module_f
.
write
(
stub_code
)
if
0
:
with
open
(
os
.
path
.
join
(
module_dir
,
...
...
@@ -1120,15 +1420,32 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
logger
.
exception
(
"Could not generate code for %s"
,
ti
.
getId
())
stub_code
=
"""class {class_name}:
\
n
{error}"""
.
format
(
class_name
=
class_name
,
error
=
safe_docstring
(
"Error trying to create {}: {} {}"
.
format
(
ti
.
getId
(),
e
.
__class__
,
e
))
)
error
=
safe_docstring
(
"Error trying to create {}: {} {}"
.
format
(
ti
.
getId
(),
e
.
__class__
,
e
)))
type_information_f
.
write
(
stub_code
)
# generate missing classes without portal type
for
class_name
,
klass
in
inspect
.
getmembers
(
erp5
.
portal_type
,
inspect
.
isclass
,
):
if
class_name
not
in
portal
.
portal_types
:
# TODO: use a better base class from klass mro
del
klass
stub_code
=
textwrap
.
dedent
(
"""
class {safe_class_name}(Products_ERP5Type_Base_Base):
'''Warning: {class_name} has no portal type.
'''
"""
).
format
(
safe_class_name
=
safe_python_identifier
(
class_name
),
class_name
=
class_name
,
)
module_f
.
write
(
stub_code
)
# portal type groups ( useful ? used in Simulation Tool only )
if
0
:
portal_types_by_group
=
defaultdict
(
list
)
for
ti_for_group
in
portal
.
portal_types
.
contentValues
():
for
group
in
ti_for_group
.
getTypeGroupList
():
...
...
@@ -1138,61 +1455,65 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
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
))
'from
{} import {}
\
n
'
.
format
(
group_class
,
group_class
))
with
open
(
os
.
path
.
join
(
module_dir
,
module
,
'{}.pyi'
.
format
(
group_class
),),
'{}.pyi'
.
format
(
group_class
),
),
'w'
,
)
as
group_f
:
group_f
.
write
(
textwrap
.
dedent
(
'''
{imports}
import erp5.portal_type
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
),
bases
=
',
\
n
'
.
join
(
'erp5.portal_type.{}'
.
format
(
c
)
for
c
in
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
(
os
.
path
.
join
(
module_dir
,
module
,
'ICatalogTool.pyi'
,),
'w'
,)
as
portal_f
:
'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
#
from erp5.portal_type import Type_AnyPortalTypeCatalogBrainList
from typing import Any
class ICatalogTool(ERP5CatalogTool):
def searchResults(self) -> Type_AnyPortalTypeCatalogBrainList:
def searchResults(self) ->
Any: #
Type_AnyPortalTypeCatalogBrainList:
"""Search Catalog"""
def __call__(self) -> Type_AnyPortalTypeCatalogBrainList:
def __call__(self) ->
Any: #
Type_AnyPortalTypeCatalogBrainList:
"""Search Catalog"""
'''
))
module_f
.
write
(
'from .ISimulationTool import ISimulationTool
\
n
'
)
if
0
:
# TODO
module_f
.
write
(
'from ISimulationTool import ISimulationTool
\
n
'
)
with
open
(
os
.
path
.
join
(
module_dir
,
module
,
'ISimulationTool.pyi'
,),
'w'
,)
as
portal_f
:
'ISimulationTool.pyi'
,
),
'w'
,
)
as
portal_f
:
portal_f
.
write
(
textwrap
.
dedent
(
'''
...
...
@@ -1206,23 +1527,28 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
'''
))
# portal object
module_f
.
write
(
'from .
ERP5Site import ERP5Site
\
n
'
)
module_f
.
write
(
'from
ERP5Site import ERP5Site
\
n
'
)
with
open
(
os
.
path
.
join
(
module_dir
,
module
,
'ERP5Site.pyi'
,),
'w'
,)
as
portal_f
:
'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
'
)
if
0
:
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
:
'Type_CatalogBrain.pyi'
,
),
'w'
,
)
as
catalog_brain_f
:
catalog_brain_f
.
write
(
textwrap
.
dedent
(
'''
...
...
@@ -1236,12 +1562,13 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
...
'''
))
module_f
.
write
(
'from .
Type_InventoryListBrain import Type_InventoryListBrain
\
n
'
)
'from
Type_InventoryListBrain import Type_InventoryListBrain
\
n
'
)
with
open
(
os
.
path
.
join
(
module_dir
,
module
,
'Type_InventoryListBrain.pyi'
,),
'Type_InventoryListBrain.pyi'
,
),
'w'
,
)
as
catalog_brain_f
:
catalog_brain_f
.
write
(
...
...
@@ -1249,9 +1576,8 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
'''
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
from DateTime.DateTime import DateTime as DateTime
import erp5.portal_type
T = TypeVar('T')
class Type_InventoryListBrain(Generic[T], InventoryListBrain):
...
...
@@ -1261,16 +1587,15 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
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
node_value: 'erp5.portal_type.Organisation' # TODO
mirror_node_value: 'erp5.portal_type.Organisation'
section_value: 'erp5.portal_type.Organisation'
mirror_section_value: 'erp5.portal_type.Organisation'
resource_value: 'erp5.portal_type.Product' # TODO
date: DateTime
mirror_date: DateTime
...
...
@@ -1283,49 +1608,57 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
total_price: float
path: str
stock_uid: uid
stock_uid: int
def getObject(self) -> T:
...
'''
))
module_f
.
write
(
'from typing import
List
, Union
\
n
'
)
module_f
.
write
(
'from typing import
Sequence
, 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
),
))
for
portal_type_class
in
all_portal_type_class_names
),
))
# TODO: Union[Sequence] or Sequence[Union] ?
module_f
.
write
(
'Type_AnyPortalTypeList = Union[
\
n
{}]
\
n
'
.
format
(
',
\
n
'
.
join
(
'
List
[{}]'
.
format
(
portal_type_class
)
'
Sequence
[{}]'
.
format
(
portal_type_class
)
for
portal_type_class
in
all_portal_type_class_names
)))
if
0
:
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
),
))
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
),
))
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
:
os
.
path
.
join
(
module_dir
,
module
,
'__init__.pyi'
),
'w'
,
)
as
accessor_holder_f
:
accessor_holder_f
.
write
(
textwrap
.
dedent
(
"""
\
# coding: utf-8
\
n
from typing import Optional, List, Any, Sequence
from Products.ERP5Type.Base import Base as Products_ERP5Type_Base_Base
import erp5.portal_type
from DateTime import DateTime
"""
))
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
))
accessor_holder_f
.
write
(
ps
.
PropertySheet_getStub
().
encode
(
'utf-8'
))
if
0
:
with
open
(
os
.
path
.
join
(
module_dir
,
...
...
@@ -1334,29 +1667,31 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
),
'w'
,
)
as
property_sheet_f
:
property_sheet_f
.
write
(
ps
.
PropertySheet_getStub
().
encode
(
'utf-8'
))
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
:
os
.
path
.
join
(
module_dir
,
module
,
'__init__.pyi'
),
'w'
,
)
as
skins_tool_f
:
skins_tool_f
.
write
(
"# coding: utf-8
\
n
"
)
for
class_name
in
SkinsTool_getClassSet
(
skins_tool
):
skins_tool_f
.
write
(
'from {class_name} import {class_name}
\
n
'
.
format
(
class_name
=
class_name
))
w
ith
open
(
w
riteFile
(
os
.
path
.
join
(
module_dir
,
module
,
'{}.pyi'
.
format
(
class_name
),),
'w'
,
)
as
skin_f
:
skin_f
.
write
(
'{}.pyi'
.
format
(
class_name
),
),
SkinsTool_getStubForClass
(
skins_tool
,
class_name
,).
encode
(
'utf-8'
))
class_name
,
).
encode
(
'utf-8'
))
elif
module
==
'component'
:
# TODO: component versions ?
module_to_component_portal_type_mapping
=
{
'test'
:
'Test Component'
,
'document'
:
'Document Component'
,
...
...
@@ -1367,11 +1702,21 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
}
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
():
'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
))
# TODO: write actual version, not always erp5_version !
mkdir_p
(
os
.
path
.
join
(
module_dir
,
module
,
sub_module
,
'erp5_version'
,
))
with
open
(
os
.
path
.
join
(
module_dir
,
module
,
sub_module
,
'__init__.py'
),
'w'
,
...
...
@@ -1379,17 +1724,32 @@ def ERP5Site_dumpModuleCode(self, component_or_script=None):
for
brain
in
portal
.
portal_catalog
(
portal_type
=
portal_type
,
validation_state
=
(
'validated'
,)):
component
=
brain
.
getObject
()
# TODO write __init__ for erp5_version as well
component_sub_module_init_f
.
write
(
"from {component_reference} import {component_reference}
\
n
"
.
format
(
component_reference
=
component
.
getReference
()))
w
ith
open
(
w
riteFile
(
os
.
path
.
join
(
module_dir
,
module
,
sub_module
,
'{}.py'
.
format
(
component
.
getReference
()),
),
'w'
,
)
as
component_f
:
component_f
.
write
(
component
.
getTextContent
())
#.encode('utf-8'))
),
component
.
getTextContent
())
writeFile
(
os
.
path
.
join
(
module_dir
,
module
,
sub_module
,
'erp5_version'
,
'{}.py'
.
format
(
component
.
getReference
()),
),
component
.
getTextContent
())
# TODO: not like this !
with
open
(
os
.
path
.
join
(
module_dir
,
module
,
sub_module
,
'__init__.py'
),
'r'
,
)
as
component_sub_module_init_f
:
writeFile
(
os
.
path
.
join
(
module_dir
,
module
,
sub_module
,
'erp5_version'
,
'__init__.py'
),
component_sub_module_init_f
.
read
())
return
'done'
bt5/erp5_monaco_editor/ExtensionTemplateItem/portal_components/extension.erp5.Jedi.xml
View file @
39fd2694
...
...
@@ -100,11 +100,13 @@
</record>
<record
id=
"4"
aka=
"AAAAAAAAAAQ="
>
<pickle>
<global
name=
"WorkflowHistoryList"
module=
"Products.ERP5Type.
patches.WorkflowTool
"
/>
<global
name=
"WorkflowHistoryList"
module=
"Products.ERP5Type.
Workflow
"
/>
</pickle>
<pickle>
<tuple>
<none/>
<dictionary>
<item>
<key>
<string>
_log
</string>
</key>
<value>
<list>
<dictionary>
<item>
...
...
@@ -117,7 +119,9 @@
</item>
</dictionary>
</list>
</tuple>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_monaco_editor/ExtensionTemplateItem/portal_components/extension.erp5.YAPF.py
View file @
39fd2694
...
...
@@ -2,6 +2,9 @@ from yapf.yapflib import yapf_api
import
json
import
tempfile
import
textwrap
import
logging
logger
=
logging
.
getLogger
(
__name__
)
def
ERP5Site_formatPythonSourceCode
(
self
,
data
,
REQUEST
=
None
):
...
...
@@ -31,6 +34,7 @@ def ERP5Site_formatPythonSourceCode(self, data, REQUEST=None):
formatted_code
,
changed
=
yapf_api
.
FormatCode
(
data
[
'code'
],
style_config
=
f
.
name
,
**
extra
)
except
SyntaxError
as
e
:
logger
.
exception
(
"Error in source code"
)
return
json
.
dumps
(
dict
(
error
=
True
,
error_line
=
e
.
lineno
))
if
REQUEST
is
not
None
:
...
...
bt5/erp5_monaco_editor/ExtensionTemplateItem/portal_components/extension.erp5.YAPF.xml
View file @
39fd2694
...
...
@@ -100,11 +100,13 @@
</record>
<record
id=
"4"
aka=
"AAAAAAAAAAQ="
>
<pickle>
<global
name=
"WorkflowHistoryList"
module=
"Products.ERP5Type.
patches.WorkflowTool
"
/>
<global
name=
"WorkflowHistoryList"
module=
"Products.ERP5Type.
Workflow
"
/>
</pickle>
<pickle>
<tuple>
<none/>
<dictionary>
<item>
<key>
<string>
_log
</string>
</key>
<value>
<list>
<dictionary>
<item>
...
...
@@ -117,7 +119,9 @@
</item>
</dictionary>
</list>
</tuple>
</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