Merge branch 'python-typehints'

This commit is contained in:
Matthias Koefferlein 2022-11-09 22:02:47 +01:00
commit 1edbd6232f
48 changed files with 49279 additions and 21678 deletions

2
.gitignore vendored
View File

@ -68,3 +68,5 @@ dist/
# Macos artifacts
*.dmg
*.dmg.md5
.DS_Store

View File

@ -1,5 +1,11 @@
#!/bin/bash -e
# Generates LVS and DRC documentation
#
# Run this script from a valid build below the repository root, e.g.
# <repo-root>/build-debug. It needs to have a "klayout" binary in
# current directory.
inst=$(realpath $(dirname $0))
scripts=${inst}/drc_lvs_doc
ld=$(realpath .)

57
scripts/make_stubs.sh Executable file
View File

@ -0,0 +1,57 @@
#!/bin/bash -e
# Generates LVS and DRC documentation
#
# Run this script from a valid build below the repository root, e.g.
# <repo-root>/build-debug. It needs to have a "pymod" installation in
# current directory.
inst=$(realpath $(dirname $0))
scripts=${inst}/drc_lvs_doc
ld=$(realpath .)
pymod="$ld/pymod"
export LD_LIBRARY_PATH=$ld
export PYTHONPATH=$pymod
pymod_src=$ld/../src/pymod
if ! [ -e $pymod_src ]; then
echo "*** ERROR: missing pymod sources ($pymod_src) - did you run the script from the build folder below the source tree?"
exit 1
fi
if ! [ -e $pymod ]; then
echo "*** ERROR: missing pymod folder ($pymod) - did you run the script from the build folder?"
exit 1
fi
python=
for try_python in python python3; do
if $try_python -c "import klayout.tl" >/dev/null 2>&1; then
python=$try_python
fi
done
if [ "$python" = "" ]; then
echo "*** ERROR: no functional python or pymod installation found."
exit 1
fi
echo "Generating stubs for tl .."
$python $inst/stubgen.py tl >$pymod_src/distutils_src/klayout/tlcore.pyi
echo "Generating stubs for db .."
$python $inst/stubgen.py db tl >$pymod_src/distutils_src/klayout/dbcore.pyi
echo "Generating stubs for rdb .."
$python $inst/stubgen.py rdb tl,db >$pymod_src/distutils_src/klayout/rdbcore.pyi
echo "Generating stubs for lay .."
$python $inst/stubgen.py lay tl,db,rdb >$pymod_src/distutils_src/klayout/laycore.pyi
echo "Generating stubs for lib .."
$python $inst/stubgen.py lib tl,db >$pymod_src/distutils_src/klayout/libcore.pyi
echo "Done."

465
scripts/stubgen.py Normal file
View File

@ -0,0 +1,465 @@
""" Stub file generation routines.
This module contains routines to generate stub files from klayout's python API.
This uses the `tl` module of the API, which offers an introspection layer to
the C-extension modules.
"""
from collections import Counter
from copy import copy
from dataclasses import dataclass, field
from functools import wraps
import functools
from sys import argv
from textwrap import indent
from typing import Any, List, Optional, Tuple, Union
import pya # initialize all modules
import klayout.tl as ktl
def qualified_name(_class: ktl.Class) -> str:
name = _class.name()
if _class.parent():
return f"{qualified_name(_class.parent())}.{name}"
else:
return name
def superclass(_class: ktl.Class) -> str:
if _class.base():
return superclass(_class.base())
else:
return _class.name()
def is_reserved_word(name: str) -> bool:
wordlist = [
"and",
"del",
"from",
"not",
"while",
"as",
"elif",
"global",
"or",
"with",
"assert",
"else",
"if",
"pass",
"yield",
"break",
"except",
"import",
"print",
"class",
"exec",
"in",
"raise",
"continue",
"finally",
"is",
"return",
"def",
"for",
"lambda",
"try",
"None",
]
return name in wordlist
_type_dict = dict()
_type_dict[ktl.ArgType.TypeBool] = "bool"
_type_dict[ktl.ArgType.TypeChar] = "str"
_type_dict[ktl.ArgType.TypeDouble] = "float"
_type_dict[ktl.ArgType.TypeFloat] = "float"
_type_dict[ktl.ArgType.TypeInt] = "int"
_type_dict[ktl.ArgType.TypeLong] = "int"
_type_dict[ktl.ArgType.TypeLongLong] = "int"
# _type_dict[ktl.ArgType.TypeMap] = None
# _type_dict[ktl.ArgType.TypeObject] = None
_type_dict[ktl.ArgType.TypeSChar] = "str"
_type_dict[ktl.ArgType.TypeShort] = "int"
_type_dict[ktl.ArgType.TypeString] = "str"
_type_dict[ktl.ArgType.TypeByteArray] = "bytes"
_type_dict[ktl.ArgType.TypeUChar] = "str"
_type_dict[ktl.ArgType.TypeUInt] = "int"
_type_dict[ktl.ArgType.TypeULong] = "int"
_type_dict[ktl.ArgType.TypeULongLong] = "int"
_type_dict[ktl.ArgType.TypeUShort] = "int"
_type_dict[ktl.ArgType.TypeVar] = "Any"
# _type_dict[ktl.ArgType.TypeVector] = None
_type_dict[ktl.ArgType.TypeVoid] = "None"
_type_dict[ktl.ArgType.TypeVoidPtr] = "None"
def _translate_type(
arg_type: ktl.ArgType, within_class: ktl.Class, is_return=False
) -> str:
"""Translates klayout's C-type to a type in Python.
This function is equivalent to the `type_to_s` in `pyaModule.cc`.
See also `type_to_s` in `layGSIHelpProvider.cc`"""
py_str: str = ""
if arg_type.type() == ktl.ArgType.TypeObject:
if within_class.module() and arg_type.cls().module() != within_class.module():
py_str = arg_type.cls().module() + "." + qualified_name(arg_type.cls())
else:
py_str = qualified_name(arg_type.cls())
elif arg_type.type() == ktl.ArgType.TypeMap:
inner_key = _translate_type(arg_type.inner_k(), within_class, is_return)
inner_val = _translate_type(arg_type.inner(), within_class, is_return)
py_str = f"Dict[{inner_key}, {inner_val}]"
elif arg_type.type() == ktl.ArgType.TypeVector:
if is_return:
py_str = f"List[{_translate_type(arg_type.inner(), within_class, is_return)}]"
else:
py_str = f"Sequence[{_translate_type(arg_type.inner(), within_class, is_return)}]"
else:
py_str = _type_dict[arg_type.type()]
if arg_type.is_iter():
py_str = f"Iterator[{py_str}]"
if arg_type.has_default():
py_str = f"Optional[{py_str}] = ..."
return py_str
@dataclass
class Stub:
signature: str
name: Any
docstring: str
indent_docstring: bool = True
child_stubs: List["Stub"] = field(default_factory=list)
decorator: str = ""
def __eq__(self, __o: object) -> bool:
if not isinstance(__o, Stub):
return False
return (
self.signature == __o.signature
and self.child_stubs == __o.child_stubs
and self.decorator == __o.decorator
)
def __lt__(self, other: "Stub") -> bool:
# mypy complains if an overload with float happens before int
self_signature = self.signature.replace(": int", "0").replace(": float", "1")
other_signature = other.signature.replace(": int", "0").replace(": float", "1")
self_sortkey = self.name, len(self.signature.split(",")), self_signature
other_sortkey = other.name, len(other.signature.split(",")), other_signature
return self_sortkey < other_sortkey
def __hash__(self):
return hash(self.format_stub(include_docstring=False))
def format_stub(self, include_docstring=True):
lines = []
lines.extend(self.decorator.splitlines())
if self.indent_docstring: # all but properties
if include_docstring or len(self.child_stubs) > 0:
lines.append(self.signature + ":")
else:
lines.append(self.signature + ": ...")
else:
lines.append(self.signature)
stub_str = "\n".join(lines)
lines = []
lines.append('r"""')
lines.extend(self.docstring.splitlines())
lines.append('"""')
doc_str = "\n".join(lines)
# indent only if it is required (methods, not properties)
if self.indent_docstring:
doc_str = indent(doc_str, " " * 4)
if include_docstring:
stub_str += "\n"
stub_str += doc_str
for stub in self.child_stubs:
stub_str += "\n"
stub_str += indent(
stub.format_stub(include_docstring=include_docstring), " " * 4
)
return stub_str
@dataclass(eq=False)
class MethodStub(Stub):
indent_docstring: bool = True
@dataclass(eq=False)
class PropertyStub(Stub):
indent_docstring: bool = False
@dataclass(eq=False)
class ClassStub(Stub):
indent_docstring: bool = True
def get_py_child_classes(c: ktl.Class):
for c_child in c.each_child_class():
yield c_child
def get_py_methods(
c: ktl.Class,
) -> List[Stub]:
translate_arg_type = functools.partial(_translate_type, within_class=c, is_return=False)
translate_ret_type = functools.partial(_translate_type, within_class=c, is_return=True)
def _get_arglist(
m: ktl.Method, self_str: str
) -> List[Tuple[str, Optional[ktl.ArgType]]]:
args: List[Tuple[str, Optional[ktl.ArgType]]] = [(self_str, None)]
for i, a in enumerate(m.each_argument()):
argname = a.name()
if is_reserved_word(argname):
argname += "_"
elif not argname:
argname = f"arg{i}"
args.append((argname, a))
return args
def _format_args(arglist: List[Tuple[str, Optional[str]]]):
args = []
for argname, argtype in arglist:
if argtype:
args.append(f"{argname}: {argtype}")
else:
args.append(argname)
return ", ".join(args)
def format_args(m: ktl.Method, self_str: str = "self") -> str:
arg_list = _get_arglist(m, self_str=self_str)
new_arglist: List[Tuple[str, Optional[str]]] = []
for argname, a in arg_list:
if a:
new_arglist.append((argname, translate_arg_type(a)))
else:
new_arglist.append((argname, None))
return _format_args(new_arglist)
# Collect all properties here
properties: List[Stub] = list()
# Extract all instance properties
for f in c.python_properties(False):
name = f.getter().name()
getter = None
if len(f.getter().methods()) > 0:
getter = f.getter().methods()[0]
setter = None
if len(f.setter().methods()) > 0:
setter = f.setter().methods()[0]
if getter and setter:
# Full property
ret_type = translate_ret_type(getter.ret_type())
doc = "Getter:\n" + getter.doc() + "\nSetter:\n" + setter.doc()
properties.append(
PropertyStub(
decorator="",
signature=f"{name}: {ret_type}",
name=name,
docstring=doc,
)
)
elif getter:
# Only getter
ret_type = translate_ret_type(getter.ret_type())
doc = getter.doc()
properties.append(
MethodStub(
decorator="@property",
signature=f"def {name}(self) -> {ret_type}",
name=name,
docstring=doc,
)
)
elif setter:
# Only setter
doc = "WARNING: This variable can only be set, not retrieved.\n" + setter.doc()
properties.append(
MethodStub(
decorator="@property",
signature=f"def {name}(self) -> None",
name=name,
docstring=doc,
)
)
# Extract all class properties (TODO: setters not supported currently)
for f in c.python_properties(True):
name = f.getter().name()
if len(f.getter().methods()) > 0:
getter = f.getter().methods()[0]
ret_type = translate_ret_type(getter.ret_type())
doc = getter.doc()
properties.append(
PropertyStub(
decorator="",
signature=f"{name}: ClassVar[{ret_type}]",
name=name,
docstring=doc,
)
)
# Extract all classmethods
classmethods: List[Stub] = list()
for f in c.python_methods(True):
name = f.name()
decorator = ""
if len(f.methods()) > 1:
decorator = "@overload\n"
decorator += "@classmethod"
for m in f.methods():
ret_type = translate_ret_type(m.ret_type())
classmethods.append(
MethodStub(
decorator=decorator,
signature=f"def {name}({format_args(m, 'cls')}) -> {ret_type}",
name=name,
docstring=m.doc(),
)
)
# Extract bound methods
boundmethods: List[Stub] = list()
for f in c.python_methods(False):
name = f.name()
decorator = ""
if len(f.methods()) > 1:
decorator = "@overload\n"
for m in f.methods():
if name == "__init__":
ret_type = "None"
else:
ret_type = translate_ret_type(m.ret_type())
arg_list = _get_arglist(m, "self")
# TODO: fix type errors
# Exceptions:
# For X.__eq__(self, a:X), treat second argument as type object instead of X
if name in ("__eq__", "__ne__"):
arg_list[1] = arg_list[1][0], "object"
# X._assign(self, other:X), mypy complains if _assign is defined at base class.
# We can't specialize other in this case.
elif name in ("_assign", "assign"):
assert arg_list[1][1] is not None
assert arg_list[1][1].type() == ktl.ArgType.TypeObject
arg_list[1] = arg_list[1][0], superclass(arg_list[1][1].cls())
else:
new_arg_list = []
for argname, a in arg_list:
if a:
new_arg_list.append((argname, translate_arg_type(a)))
else:
new_arg_list.append((argname, a))
arg_list = new_arg_list
formatted_args = _format_args(arg_list)
boundmethods.append(
MethodStub(
decorator=decorator,
signature=f"def {name}({formatted_args}) -> {ret_type}",
name=name,
docstring=m.doc(),
)
)
boundmethods = sorted(boundmethods)
properties = sorted(properties)
classmethods = sorted(classmethods)
return_list: List[Stub] = properties + classmethods + boundmethods
return return_list
def get_class_stub(
c: ktl.Class,
ignore: List[ktl.Class] = None,
module: str = "",
) -> ClassStub:
base = ""
if c.base():
base = f"({c.base().name()})"
if c.module() != module:
full_name = c.module() + "." + c.name()
else:
full_name = c.name()
_cstub = ClassStub(
signature="class " + full_name + base, docstring=c.doc(), name=full_name
)
child_attributes = get_py_methods(c)
for child_c in c.each_child_class():
_cstub.child_stubs.append(
get_class_stub(
child_c,
ignore=ignore,
module=c.module(),
)
)
for stub in child_attributes:
_cstub.child_stubs.append(stub)
return _cstub
def get_classes(module: str) -> List[ktl.Class]:
_classes = []
for c in ktl.Class.each_class():
if c.module() != module:
continue
_classes.append(c)
return _classes
def get_module_stubs(module: str) -> List[ClassStub]:
_stubs = []
_classes = get_classes(module)
for c in _classes:
_cstub = get_class_stub(c, ignore=_classes, module=module)
_stubs.append(_cstub)
return _stubs
def print_mod(module, dependencies):
print("from typing import Any, ClassVar, Dict, Sequence, List, Iterator, Optional")
print("from typing import overload")
for dep in dependencies:
print(f"import klayout.{dep} as {dep}")
for stub in get_module_stubs(module):
print(stub.format_stub(include_docstring=True) + "\n")
if __name__ == "__main__":
if len(argv) < 2:
print("Specity module in argument")
exit(1)
if len(argv) == 2:
print_mod(argv[1], [])
else:
print_mod(argv[1], argv[2].split(","))

634
setup.py
View File

@ -1,4 +1,3 @@
"""
KLayout standalone Python module setup script
@ -55,6 +54,7 @@ and won't find them. So we need to take away the path with
"-Wl,-soname" on Linux (see Config.link_args).
"""
from typing import List
from setuptools import setup, Distribution, find_packages
from setuptools.extension import Extension, Library
import glob
@ -70,17 +70,28 @@ import multiprocessing
# for Jenkins we do not want to be greedy
multicore = os.getenv("KLAYOUT_SETUP_MULTICORE")
if multicore:
N_cores = int(multicore)
N_cores = int(multicore)
else:
N_cores = multiprocessing.cpu_count()
N_cores = multiprocessing.cpu_count()
# monkey-patch for parallel compilation
# from https://stackoverflow.com/questions/11013851/speeding-up-build-process-with-distutils
def parallelCCompile(self, sources, output_dir=None, macros=None, include_dirs=None, debug=0, extra_preargs=None, extra_postargs=None, depends=None):
def parallelCCompile(
self,
sources,
output_dir=None,
macros=None,
include_dirs=None,
debug=0,
extra_preargs=None,
extra_postargs=None,
depends=None,
):
# those lines are copied from distutils.ccompiler.CCompiler directly
macros, objects, extra_postargs, pp_opts, build = self._setup_compile(
output_dir, macros, include_dirs, sources, depends, extra_postargs)
output_dir, macros, include_dirs, sources, depends, extra_postargs
)
cc_args = self._get_cc_args(pp_opts, debug, extra_preargs)
# parallel code
@ -104,6 +115,7 @@ def parallelCCompile(self, sources, output_dir=None, macros=None, include_dirs=N
print("Building", obj, "has failed. Trying again.")
else:
break
# convert to list, imap is evaluated on-demand
list(multiprocessing.pool.ThreadPool(N).map(_single_compile, objects))
return objects
@ -112,6 +124,7 @@ def parallelCCompile(self, sources, output_dir=None, macros=None, include_dirs=N
# only if python version > 2.6, somehow the travis compiler hangs in 2.6
if sys.version_info[0] * 100 + sys.version_info[1] > 206:
import distutils.ccompiler
distutils.ccompiler.CCompiler.compile = parallelCCompile
@ -119,7 +132,7 @@ if sys.version_info[0] * 100 + sys.version_info[1] > 206:
def quote_path(path):
# looks like disutils don't need path quoting in version >= 3.9:
if " " in path and sys.version_info[0] * 100 + sys.version_info[1] < 309:
return "\"" + path + "\""
return '"' + path + '"'
else:
return path
@ -127,6 +140,7 @@ def quote_path(path):
# TODO: delete (Obsolete)
# patch get_ext_filename
from distutils.command.build_ext import build_ext
_old_get_ext_filename = build_ext.get_ext_filename
@ -137,8 +151,8 @@ def patched_get_ext_filename(self, ext_name):
"""
filename = _old_get_ext_filename(self, ext_name)
# Making sure this matches qmake's default extension .dylib, instead of .so
if platform.system() == "Darwin" and '_dbpi' in ext_name:
filename = filename.replace('.so', '.dylib')
if platform.system() == "Darwin" and "_dbpi" in ext_name:
filename = filename.replace(".so", ".dylib")
return filename
@ -152,15 +166,18 @@ distutils.command.build_ext.build_ext.get_ext_filename = patched_get_ext_filenam
# hence the patch
from distutils.ccompiler import CCompiler
old_library_filename = CCompiler.library_filename
def patched_library_filename(self, libname, lib_type='static', # or 'shared'
strip_dir=0, output_dir=''):
if platform.system() == "Darwin" and '_dbpi' in libname:
lib_type = 'dylib'
return old_library_filename(self, libname, lib_type=lib_type,
strip_dir=strip_dir, output_dir=output_dir)
def patched_library_filename(
self, libname, lib_type="static", strip_dir=0, output_dir="" # or 'shared'
):
if platform.system() == "Darwin" and "_dbpi" in libname:
lib_type = "dylib"
return old_library_filename(
self, libname, lib_type=lib_type, strip_dir=strip_dir, output_dir=output_dir
)
distutils.ccompiler.CCompiler.library_filename = patched_library_filename
@ -172,16 +189,36 @@ distutils.ccompiler.CCompiler.library_filename = patched_library_filename
# link a shared object on Linux, but a static library and patches distutils
# for this ... We're patching this back now.
def always_link_shared_object(
self, objects, output_libname, output_dir=None, libraries=None,
library_dirs=None, runtime_library_dirs=None, export_symbols=None,
debug=0, extra_preargs=None, extra_postargs=None, build_temp=None,
target_lang=None):
self,
objects,
output_libname,
output_dir=None,
libraries=None,
library_dirs=None,
runtime_library_dirs=None,
export_symbols=None,
debug=0,
extra_preargs=None,
extra_postargs=None,
build_temp=None,
target_lang=None,
):
self.link(
self.SHARED_LIBRARY, objects, output_libname,
output_dir, libraries, library_dirs, runtime_library_dirs,
export_symbols, debug, extra_preargs, extra_postargs,
build_temp, target_lang
self.SHARED_LIBRARY,
objects,
output_libname,
output_dir,
libraries,
library_dirs,
runtime_library_dirs,
export_symbols,
debug,
extra_preargs,
extra_postargs,
build_temp,
target_lang,
)
@ -201,7 +238,7 @@ class Config(object):
# TODO: is this really how we get the build paths?
build_cmd = Distribution().get_command_obj('build')
build_cmd = Distribution().get_command_obj("build")
build_cmd.finalize_options()
self.build_platlib = build_cmd.build_platlib
self.build_temp = build_cmd.build_temp
@ -212,7 +249,7 @@ class Config(object):
else:
self.build_temp = os.path.join(self.build_temp, "Release")
build_ext_cmd = Distribution().get_command_obj('build_ext')
build_ext_cmd = Distribution().get_command_obj("build_ext")
build_ext_cmd.initialize_options()
build_ext_cmd.setup_shlib_compiler()
@ -230,7 +267,7 @@ class Config(object):
This code was extracted from the source in setuptools.command.build_ext.build_ext
"""
libtype = setuptools.command.build_ext.libtype
full_mod = self.root + '.' + mod
full_mod = self.root + "." + mod
if is_lib is True:
# Here we are guessing it is a library and that the filename
# is to be computed by shlib_compiler.
@ -246,8 +283,8 @@ class Config(object):
ext_filename = os.path.basename(ext_path)
# Exception for database plugins, which will always be dylib.
if platform.system() == "Darwin" and '_dbpi' in mod:
ext_filename = ext_filename.replace('.so', '.dylib')
if platform.system() == "Darwin" and "_dbpi" in mod:
ext_filename = ext_filename.replace(".so", ".dylib")
return ext_filename
def path_of(self, mod, mod_src_path):
@ -265,20 +302,25 @@ class Config(object):
"""
Gets additional compiler arguments
"""
args: List[str]
if platform.system() == "Windows":
bits = os.getenv("KLAYOUT_BITS")
if bits:
return [quote_path("-I" + os.path.join(bits, "zlib", "include")),
quote_path("-I" + os.path.join(bits, "ptw", "include")),
quote_path("-I" + os.path.join(bits, "png", "include")),
quote_path("-I" + os.path.join(bits, "expat", "include")),
quote_path("-I" + os.path.join(bits, "curl", "include"))]
args = [
quote_path("-I" + os.path.join(bits, "zlib", "include")),
quote_path("-I" + os.path.join(bits, "ptw", "include")),
quote_path("-I" + os.path.join(bits, "png", "include")),
quote_path("-I" + os.path.join(bits, "expat", "include")),
quote_path("-I" + os.path.join(bits, "curl", "include")),
]
else:
return []
args = []
else:
return ["-Wno-strict-aliasing", # Avoids many "type-punned pointer" warnings
"-std=c++11", # because we use unordered_map/unordered_set
]
args = [
"-Wno-strict-aliasing", # Avoids many "type-punned pointer" warnings
"-std=c++11", # because we use unordered_map/unordered_set
]
return args
def libraries(self, mod):
"""
@ -286,10 +328,10 @@ class Config(object):
"""
if platform.system() == "Windows":
if mod == "_tl":
return [ "libcurl", "expat", "pthreadVCE2", "zlib", "wsock32", "libpng16" ]
return ["libcurl", "expat", "pthreadVCE2", "zlib", "wsock32", "libpng16"]
else:
if mod == "_tl":
return [ "curl", "expat", "png" ]
return ["curl", "expat", "png"]
return []
def link_args(self, mod):
@ -300,19 +342,24 @@ class Config(object):
args = ["/DLL"]
bits = os.getenv("KLAYOUT_BITS")
if bits:
args += [quote_path("/LIBPATH:" + os.path.join(bits, "zlib", "lib")),
quote_path("/LIBPATH:" + os.path.join(bits, "ptw", "libraries")),
quote_path("/LIBPATH:" + os.path.join(bits, "png", "libraries")),
quote_path("/LIBPATH:" + os.path.join(bits, "expat", "libraries")),
quote_path("/LIBPATH:" + os.path.join(bits, "curl", "libraries"))]
args += [
quote_path("/LIBPATH:" + os.path.join(bits, "zlib", "libraries")),
quote_path("/LIBPATH:" + os.path.join(bits, "ptw", "libraries")),
quote_path("/LIBPATH:" + os.path.join(bits, "png", "libraries")),
quote_path("/LIBPATH:" + os.path.join(bits, "expat", "libraries")),
quote_path("/LIBPATH:" + os.path.join(bits, "curl", "libraries")),
]
return args
elif platform.system() == "Darwin":
# For the dependency modules, make sure we produce a dylib.
# We can only link against such, but the bundles produced otherwise.
args = []
if mod[0] == "_":
args += ["-Wl,-dylib", '-Wl,-install_name,@rpath/%s' % self.libname_of(mod, is_lib=True)]
args += ['-Wl,-rpath,@loader_path/']
args += [
"-Wl,-dylib",
"-Wl,-install_name,@rpath/%s" % self.libname_of(mod, is_lib=True),
]
args += ["-Wl,-rpath,@loader_path/"]
return args
else:
# this makes the libraries suitable for linking with a path -
@ -321,23 +368,30 @@ class Config(object):
# will look for the path-qualified library. But that's the
# build path and the loader will fail.
args = []
args += ['-Wl,-soname,' + self.libname_of(mod, is_lib=True)]
if '_dbpi' not in mod:
loader_path = '$ORIGIN'
args += ["-Wl,-soname," + self.libname_of(mod, is_lib=True)]
if "_dbpi" not in mod:
loader_path = "$ORIGIN"
else:
loader_path = '$ORIGIN/..'
args += ['-Wl,-rpath,' + loader_path]
loader_path = "$ORIGIN/.."
args += ["-Wl,-rpath," + loader_path]
# default linux shared object compilation uses the '-g' flag,
# which generates unnecessary debug information
# removing with strip-all during the linking stage
args += ['-Wl,--strip-all']
args += ["-Wl,--strip-all"]
return args
def macros(self):
"""
Returns the macros to use for building
"""
return [('HAVE_PNG', 1), ('HAVE_CURL', 1), ('HAVE_EXPAT', 1), ('KLAYOUT_MAJOR_VERSION', self.major_version()), ('KLAYOUT_MINOR_VERSION', self.minor_version())]
return [
("HAVE_PNG", 1),
("HAVE_CURL", 1),
("HAVE_EXPAT", 1),
("KLAYOUT_MAJOR_VERSION", self.major_version()),
("KLAYOUT_MINOR_VERSION", self.minor_version()),
("GSI_ALIAS_INSPECT", 1),
]
def minor_version(self):
"""
@ -349,7 +403,9 @@ class Config(object):
version_file = os.path.join(os.path.dirname(__file__), "version.sh")
with open(version_file, "r") as file:
version_txt = file.read()
rm = re.search(r"KLAYOUT_VERSION\s*=\s*\"(.*?)\.(.*?)(\..*)?\".*", version_txt)
rm = re.search(
r"KLAYOUT_VERSION\s*=\s*\"(.*?)\.(.*?)(\..*)?\".*", version_txt
)
if rm:
version_string = rm.group(2)
return version_string
@ -366,7 +422,9 @@ class Config(object):
version_file = os.path.join(os.path.dirname(__file__), "version.sh")
with open(version_file, "r") as file:
version_txt = file.read()
rm = re.search(r"KLAYOUT_VERSION\s*=\s*\"(.*?)\.(.*?)(\..*)?\".*", version_txt)
rm = re.search(
r"KLAYOUT_VERSION\s*=\s*\"(.*?)\.(.*?)(\..*)?\".*", version_txt
)
if rm:
version_string = rm.group(1)
return version_string
@ -407,13 +465,15 @@ _tl_sources.discard(os.path.join(_tl_path, "tlHttpStreamNoQt.cc"))
_tl_sources.discard(os.path.join(_tl_path, "tlFileSystemWatcher.cc"))
_tl_sources.discard(os.path.join(_tl_path, "tlDeferredExecutionQt.cc"))
_tl = Library(config.root + '._tl',
define_macros=config.macros() + [('MAKE_TL_LIBRARY', 1)],
language='c++',
libraries=config.libraries('_tl'),
extra_link_args=config.link_args('_tl'),
extra_compile_args=config.compile_args('_tl'),
sources=list(_tl_sources))
_tl = Library(
config.root + "._tl",
define_macros=config.macros() + [("MAKE_TL_LIBRARY", 1)],
language="c++",
libraries=config.libraries("_tl"),
extra_link_args=config.link_args("_tl"),
extra_compile_args=config.compile_args("_tl"),
sources=list(_tl_sources),
)
config.add_extension(_tl)
@ -423,15 +483,17 @@ config.add_extension(_tl)
_gsi_path = os.path.join("src", "gsi", "gsi")
_gsi_sources = set(glob.glob(os.path.join(_gsi_path, "*.cc")))
_gsi = Library(config.root + '._gsi',
define_macros=config.macros() + [('MAKE_GSI_LIBRARY', 1)],
include_dirs=[_tl_path],
extra_objects=[config.path_of('_tl', _tl_path)],
language='c++',
libraries=config.libraries('_gsi'),
extra_link_args=config.link_args('_gsi'),
extra_compile_args=config.compile_args('_gsi'),
sources=list(_gsi_sources))
_gsi = Library(
config.root + "._gsi",
define_macros=config.macros() + [("MAKE_GSI_LIBRARY", 1)],
include_dirs=[_tl_path],
extra_objects=[config.path_of("_tl", _tl_path)],
language="c++",
libraries=config.libraries('_gsi'),
extra_link_args=config.link_args("_gsi"),
extra_compile_args=config.compile_args("_gsi"),
sources=list(_gsi_sources),
)
config.add_extension(_gsi)
# ------------------------------------------------------------------
@ -440,15 +502,17 @@ config.add_extension(_gsi)
_pya_path = os.path.join("src", "pya", "pya")
_pya_sources = set(glob.glob(os.path.join(_pya_path, "*.cc")))
_pya = Library(config.root + '._pya',
define_macros=config.macros() + [('MAKE_PYA_LIBRARY', 1)],
include_dirs=[_tl_path, _gsi_path],
extra_objects=[config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path)],
language='c++',
libraries=config.libraries('_pya'),
extra_link_args=config.link_args('_pya'),
extra_compile_args=config.compile_args('_pya'),
sources=list(_pya_sources))
_pya = Library(
config.root + "._pya",
define_macros=config.macros() + [("MAKE_PYA_LIBRARY", 1)],
include_dirs=[_tl_path, _gsi_path],
extra_objects=[config.path_of("_tl", _tl_path), config.path_of("_gsi", _gsi_path)],
language="c++",
libraries=config.libraries('_pya'),
extra_link_args=config.link_args("_pya"),
extra_compile_args=config.compile_args("_pya"),
sources=list(_pya_sources),
)
config.add_extension(_pya)
# ------------------------------------------------------------------
@ -457,15 +521,17 @@ config.add_extension(_pya)
_rba_path = os.path.join("src", "rbastub")
_rba_sources = set(glob.glob(os.path.join(_rba_path, "*.cc")))
_rba = Library(config.root + '._rba',
define_macros=config.macros() + [('MAKE_RBA_LIBRARY', 1)],
include_dirs=[_tl_path, _gsi_path],
extra_objects=[config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path)],
language='c++',
libraries=config.libraries('_rba'),
extra_link_args=config.link_args('_rba'),
extra_compile_args=config.compile_args('_rba'),
sources=list(_rba_sources))
_rba = Library(
config.root + '._rba',
define_macros=config.macros() + [('MAKE_RBA_LIBRARY', 1)],
include_dirs=[_tl_path, _gsi_path],
extra_objects=[config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path)],
language='c++',
libraries=config.libraries('_rba'),
extra_link_args=config.link_args('_rba'),
extra_compile_args=config.compile_args('_rba'),
sources=list(_rba_sources)
)
config.add_extension(_rba)
# ------------------------------------------------------------------
@ -474,15 +540,17 @@ config.add_extension(_rba)
_db_path = os.path.join("src", "db", "db")
_db_sources = set(glob.glob(os.path.join(_db_path, "*.cc")))
_db = Library(config.root + '._db',
define_macros=config.macros() + [('MAKE_DB_LIBRARY', 1)],
include_dirs=[_tl_path, _gsi_path, _db_path],
extra_objects=[config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path)],
language='c++',
libraries=config.libraries('_db'),
extra_link_args=config.link_args('_db'),
extra_compile_args=config.compile_args('_db'),
sources=list(_db_sources))
_db = Library(
config.root + "._db",
define_macros=config.macros() + [("MAKE_DB_LIBRARY", 1)],
include_dirs=[_tl_path, _gsi_path, _db_path],
extra_objects=[config.path_of("_tl", _tl_path), config.path_of("_gsi", _gsi_path)],
language="c++",
libraries=config.libraries('_db'),
extra_link_args=config.link_args("_db"),
extra_compile_args=config.compile_args("_db"),
sources=list(_db_sources),
)
config.add_extension(_db)
# ------------------------------------------------------------------
@ -491,15 +559,21 @@ config.add_extension(_db)
_lib_path = os.path.join("src", "lib", "lib")
_lib_sources = set(glob.glob(os.path.join(_lib_path, "*.cc")))
_lib = Library(config.root + '._lib',
define_macros=config.macros() + [('MAKE_LIB_LIBRARY', 1)],
include_dirs=[_tl_path, _gsi_path, _db_path, _lib_path],
extra_objects=[config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path), config.path_of('_db', _db_path)],
language='c++',
libraries=config.libraries('_lib'),
extra_link_args=config.link_args('_lib'),
extra_compile_args=config.compile_args('_lib'),
sources=list(_lib_sources))
_lib = Library(
config.root + "._lib",
define_macros=config.macros() + [("MAKE_LIB_LIBRARY", 1)],
include_dirs=[_tl_path, _gsi_path, _db_path, _lib_path],
extra_objects=[
config.path_of("_tl", _tl_path),
config.path_of("_gsi", _gsi_path),
config.path_of("_db", _db_path),
],
language="c++",
libraries=config.libraries('_lib'),
extra_link_args=config.link_args("_lib"),
extra_compile_args=config.compile_args("_lib"),
sources=list(_lib_sources),
)
config.add_extension(_lib)
# ------------------------------------------------------------------
@ -508,15 +582,21 @@ config.add_extension(_lib)
_rdb_path = os.path.join("src", "rdb", "rdb")
_rdb_sources = set(glob.glob(os.path.join(_rdb_path, "*.cc")))
_rdb = Library(config.root + '._rdb',
define_macros=config.macros() + [('MAKE_RDB_LIBRARY', 1)],
include_dirs=[_db_path, _tl_path, _gsi_path],
extra_objects=[config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path), config.path_of('_db', _db_path)],
language='c++',
libraries=config.libraries('_rdb'),
extra_link_args=config.link_args('_rdb'),
extra_compile_args=config.compile_args('_rdb'),
sources=list(_rdb_sources))
_rdb = Library(
config.root + "._rdb",
define_macros=config.macros() + [("MAKE_RDB_LIBRARY", 1)],
include_dirs=[_db_path, _tl_path, _gsi_path],
extra_objects=[
config.path_of("_tl", _tl_path),
config.path_of("_gsi", _gsi_path),
config.path_of("_db", _db_path),
],
language="c++",
libraries=config.libraries('_rdb'),
extra_link_args=config.link_args("_rdb"),
extra_compile_args=config.compile_args("_rdb"),
sources=list(_rdb_sources),
)
config.add_extension(_rdb)
# ------------------------------------------------------------------
@ -525,15 +605,22 @@ config.add_extension(_rdb)
_laybasic_path = os.path.join("src", "laybasic", "laybasic")
_laybasic_sources = set(glob.glob(os.path.join(_laybasic_path, "*.cc")))
_laybasic = Library(config.root + '._laybasic',
define_macros=config.macros() + [('MAKE_LAYBASIC_LIBRARY', 1)],
include_dirs=[_rdb_path, _db_path, _tl_path, _gsi_path],
extra_objects=[config.path_of('_rdb', _rdb_path), config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path), config.path_of('_db', _db_path)],
language='c++',
libraries=config.libraries('_laybasic'),
extra_link_args=config.link_args('_laybasic'),
extra_compile_args=config.compile_args('_laybasic'),
sources=list(_laybasic_sources))
_laybasic = Library(
config.root + '._laybasic',
define_macros=config.macros() + [('MAKE_LAYBASIC_LIBRARY', 1)],
include_dirs=[_rdb_path, _db_path, _tl_path, _gsi_path],
extra_objects=[
config.path_of('_rdb', _rdb_path),
config.path_of('_tl', _tl_path),
config.path_of('_gsi', _gsi_path),
config.path_of('_db', _db_path)
],
language='c++',
libraries=config.libraries('_laybasic'),
extra_link_args=config.link_args('_laybasic'),
extra_compile_args=config.compile_args('_laybasic'),
sources=list(_laybasic_sources)
)
config.add_extension(_laybasic)
# ------------------------------------------------------------------
@ -542,15 +629,23 @@ config.add_extension(_laybasic)
_layview_path = os.path.join("src", "layview", "layview")
_layview_sources = set(glob.glob(os.path.join(_layview_path, "*.cc")))
_layview = Library(config.root + '._layview',
define_macros=config.macros() + [('MAKE_LAYVIEW_LIBRARY', 1)],
include_dirs=[_laybasic_path, _rdb_path, _db_path, _tl_path, _gsi_path],
extra_objects=[config.path_of('_laybasic', _laybasic_path), config.path_of('_rdb', _rdb_path), config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path), config.path_of('_db', _db_path)],
language='c++',
libraries=config.libraries('_layview'),
extra_link_args=config.link_args('_layview'),
extra_compile_args=config.compile_args('_layview'),
sources=list(_layview_sources))
_layview = Library(
config.root + '._layview',
define_macros=config.macros() + [('MAKE_LAYVIEW_LIBRARY', 1)],
include_dirs=[_laybasic_path, _rdb_path, _db_path, _tl_path, _gsi_path],
extra_objects=[
config.path_of('_laybasic', _laybasic_path),
config.path_of('_rdb', _rdb_path),
config.path_of('_tl', _tl_path),
config.path_of('_gsi', _gsi_path),
config.path_of('_db', _db_path)
],
language='c++',
libraries=config.libraries('_layview'),
extra_link_args=config.link_args('_layview'),
extra_compile_args=config.compile_args('_layview'),
sources=list(_layview_sources)
)
config.add_extension(_layview)
# ------------------------------------------------------------------
@ -559,15 +654,22 @@ config.add_extension(_layview)
_lym_path = os.path.join("src", "lym", "lym")
_lym_sources = set(glob.glob(os.path.join(_lym_path, "*.cc")))
_lym = Library(config.root + '._lym',
define_macros=config.macros() + [('MAKE_LYM_LIBRARY', 1)],
include_dirs=[_pya_path, _rba_path, _tl_path, _gsi_path],
extra_objects=[config.path_of('_rba', _rba_path), config.path_of('_pya', _pya_path), config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path)],
language='c++',
libraries=config.libraries('_lym'),
extra_link_args=config.link_args('_lym'),
extra_compile_args=config.compile_args('_lym'),
sources=list(_lym_sources))
_lym = Library(
config.root + '._lym',
define_macros=config.macros() + [('MAKE_LYM_LIBRARY', 1)],
include_dirs=[_pya_path, _rba_path, _tl_path, _gsi_path],
extra_objects=[
config.path_of('_rba', _rba_path),
config.path_of('_pya', _pya_path),
config.path_of('_tl', _tl_path),
config.path_of('_gsi', _gsi_path)
],
language='c++',
libraries=config.libraries('_lym'),
extra_link_args=config.link_args('_lym'),
extra_compile_args=config.compile_args('_lym'),
sources=list(_lym_sources)
)
config.add_extension(_lym)
# ------------------------------------------------------------------
@ -576,15 +678,24 @@ config.add_extension(_lym)
_ant_path = os.path.join("src", "ant", "ant")
_ant_sources = set(glob.glob(os.path.join(_ant_path, "*.cc")))
_ant = Library(config.root + '._ant',
define_macros=config.macros() + [('MAKE_ANT_LIBRARY', 1)],
include_dirs=[_laybasic_path, _layview_path, _rdb_path, _db_path, _tl_path, _gsi_path],
extra_objects=[config.path_of('_laybasic', _laybasic_path), config.path_of('_layview', _layview_path), config.path_of('_rdb', _rdb_path), config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path), config.path_of('_db', _db_path)],
language='c++',
libraries=config.libraries('_ant'),
extra_link_args=config.link_args('_ant'),
extra_compile_args=config.compile_args('_ant'),
sources=list(_ant_sources))
_ant = Library(
config.root + '._ant',
define_macros=config.macros() + [('MAKE_ANT_LIBRARY', 1)],
include_dirs=[_laybasic_path, _layview_path, _rdb_path, _db_path, _tl_path, _gsi_path],
extra_objects=[
config.path_of('_laybasic', _laybasic_path),
config.path_of('_layview', _layview_path),
config.path_of('_rdb', _rdb_path),
config.path_of('_tl', _tl_path),
config.path_of('_gsi', _gsi_path),
config.path_of('_db', _db_path)
],
language='c++',
libraries=config.libraries('_ant'),
extra_link_args=config.link_args('_ant'),
extra_compile_args=config.compile_args('_ant'),
sources=list(_ant_sources)
)
config.add_extension(_ant)
# ------------------------------------------------------------------
@ -593,15 +704,24 @@ config.add_extension(_ant)
_img_path = os.path.join("src", "img", "img")
_img_sources = set(glob.glob(os.path.join(_img_path, "*.cc")))
_img = Library(config.root + '._img',
define_macros=config.macros() + [('MAKE_IMG_LIBRARY', 1)],
include_dirs=[_laybasic_path, _layview_path, _rdb_path, _db_path, _tl_path, _gsi_path],
extra_objects=[config.path_of('_laybasic', _laybasic_path), config.path_of('_layview', _layview_path), config.path_of('_rdb', _rdb_path), config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path), config.path_of('_db', _db_path)],
language='c++',
libraries=config.libraries('_img'),
extra_link_args=config.link_args('_img'),
extra_compile_args=config.compile_args('_img'),
sources=list(_img_sources))
_img = Library(
config.root + '._img',
define_macros=config.macros() + [('MAKE_IMG_LIBRARY', 1)],
include_dirs=[_laybasic_path, _layview_path, _rdb_path, _db_path, _tl_path, _gsi_path],
extra_objects=[
config.path_of('_laybasic', _laybasic_path),
config.path_of('_layview', _layview_path),
config.path_of('_rdb', _rdb_path),
config.path_of('_tl', _tl_path),
config.path_of('_gsi', _gsi_path),
config.path_of('_db', _db_path)
],
language='c++',
libraries=config.libraries('_img'),
extra_link_args=config.link_args('_img'),
extra_compile_args=config.compile_args('_img'),
sources=list(_img_sources)
)
config.add_extension(_img)
# ------------------------------------------------------------------
@ -610,15 +730,24 @@ config.add_extension(_img)
_edt_path = os.path.join("src", "edt", "edt")
_edt_sources = set(glob.glob(os.path.join(_edt_path, "*.cc")))
_edt = Library(config.root + '._edt',
define_macros=config.macros() + [('MAKE_EDT_LIBRARY', 1)],
include_dirs=[_laybasic_path, _layview_path, _rdb_path, _db_path, _tl_path, _gsi_path],
extra_objects=[config.path_of('_laybasic', _laybasic_path), config.path_of('_layview', _layview_path), config.path_of('_rdb', _rdb_path), config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path), config.path_of('_db', _db_path)],
language='c++',
libraries=config.libraries('_edt'),
extra_link_args=config.link_args('_edt'),
extra_compile_args=config.compile_args('_edt'),
sources=list(_edt_sources))
_edt = Library(
config.root + '._edt',
define_macros=config.macros() + [('MAKE_EDT_LIBRARY', 1)],
include_dirs=[_laybasic_path, _layview_path, _rdb_path, _db_path, _tl_path, _gsi_path],
extra_objects=[
config.path_of('_laybasic', _laybasic_path),
config.path_of('_layview', _layview_path),
config.path_of('_rdb', _rdb_path),
config.path_of('_tl', _tl_path),
config.path_of('_gsi', _gsi_path),
config.path_of('_db', _db_path)
],
language='c++',
libraries=config.libraries('_edt'),
extra_link_args=config.link_args('_edt'),
extra_compile_args=config.compile_args('_edt'),
sources=list(_edt_sources)
)
config.add_extension(_edt)
# ------------------------------------------------------------------
@ -635,15 +764,25 @@ for pi in dbpi_dirs:
pi_sources = glob.glob(os.path.join(pi, "*.cc"))
pi_ext = Library(config.root + '.db_plugins.' + mod_name,
define_macros=config.macros() + [('MAKE_DB_PLUGIN_LIBRARY', 1)],
include_dirs=[os.path.join("src", "plugins", "common"),
_db_path, _tl_path, _gsi_path],
extra_objects=[config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path), config.path_of('_db', _db_path)],
language='c++',
extra_link_args=config.link_args(mod_name),
extra_compile_args=config.compile_args(mod_name),
sources=pi_sources)
pi_ext = Library(
config.root + ".db_plugins." + mod_name,
define_macros=config.macros() + [("MAKE_DB_PLUGIN_LIBRARY", 1)],
include_dirs=[
os.path.join("src", "plugins", "common"),
_db_path,
_tl_path,
_gsi_path,
],
extra_objects=[
config.path_of("_tl", _tl_path),
config.path_of("_gsi", _gsi_path),
config.path_of("_db", _db_path),
],
language="c++",
extra_link_args=config.link_args(mod_name),
extra_compile_args=config.compile_args(mod_name),
sources=pi_sources,
)
db_plugins.append(pi_ext)
config.add_extension(pi_ext)
@ -654,13 +793,19 @@ for pi in dbpi_dirs:
tl_path = os.path.join("src", "pymod", "tl")
tl_sources = set(glob.glob(os.path.join(tl_path, "*.cc")))
tl = Extension(config.root + '.tlcore',
define_macros=config.macros(),
include_dirs=[_tl_path, _gsi_path, _pya_path],
extra_objects=[config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path), config.path_of('_pya', _pya_path)],
extra_link_args=config.link_args('tlcore'),
extra_compile_args=config.compile_args('tlcore'),
sources=list(tl_sources))
tl = Extension(
config.root + ".tlcore",
define_macros=config.macros(),
include_dirs=[_tl_path, _gsi_path, _pya_path],
extra_objects=[
config.path_of("_tl", _tl_path),
config.path_of("_gsi", _gsi_path),
config.path_of("_pya", _pya_path),
],
extra_link_args=config.link_args("tlcore"),
extra_compile_args=config.compile_args("tlcore"),
sources=list(tl_sources),
)
# ------------------------------------------------------------------
# db extension library
@ -668,13 +813,20 @@ tl = Extension(config.root + '.tlcore',
db_path = os.path.join("src", "pymod", "db")
db_sources = set(glob.glob(os.path.join(db_path, "*.cc")))
db = Extension(config.root + '.dbcore',
define_macros=config.macros(),
include_dirs=[_db_path, _tl_path, _gsi_path, _pya_path],
extra_objects=[config.path_of('_db', _db_path), config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path), config.path_of('_pya', _pya_path)],
extra_link_args=config.link_args('dbcore'),
extra_compile_args=config.compile_args('dbcore'),
sources=list(db_sources))
db = Extension(
config.root + ".dbcore",
define_macros=config.macros(),
include_dirs=[_db_path, _tl_path, _gsi_path, _pya_path],
extra_objects=[
config.path_of("_db", _db_path),
config.path_of("_tl", _tl_path),
config.path_of("_gsi", _gsi_path),
config.path_of("_pya", _pya_path),
],
extra_link_args=config.link_args("dbcore"),
extra_compile_args=config.compile_args("dbcore"),
sources=list(db_sources),
)
# ------------------------------------------------------------------
# lib extension library
@ -682,13 +834,20 @@ db = Extension(config.root + '.dbcore',
lib_path = os.path.join("src", "pymod", "lib")
lib_sources = set(glob.glob(os.path.join(lib_path, "*.cc")))
lib = Extension(config.root + '.libcore',
define_macros=config.macros(),
include_dirs=[_lib_path, _tl_path, _gsi_path, _pya_path],
extra_objects=[config.path_of('_lib', _lib_path), config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path), config.path_of('_pya', _pya_path)],
extra_link_args=config.link_args('libcore'),
extra_compile_args=config.compile_args('libcore'),
sources=list(lib_sources))
lib = Extension(
config.root + ".libcore",
define_macros=config.macros(),
include_dirs=[_lib_path, _tl_path, _gsi_path, _pya_path],
extra_objects=[
config.path_of("_lib", _lib_path),
config.path_of("_tl", _tl_path),
config.path_of("_gsi", _gsi_path),
config.path_of("_pya", _pya_path),
],
extra_link_args=config.link_args("libcore"),
extra_compile_args=config.compile_args("libcore"),
sources=list(lib_sources),
)
# ------------------------------------------------------------------
# rdb extension library
@ -696,13 +855,20 @@ lib = Extension(config.root + '.libcore',
rdb_path = os.path.join("src", "pymod", "rdb")
rdb_sources = set(glob.glob(os.path.join(rdb_path, "*.cc")))
rdb = Extension(config.root + '.rdbcore',
define_macros=config.macros(),
include_dirs=[_rdb_path, _tl_path, _gsi_path, _pya_path],
extra_objects=[config.path_of('_rdb', _rdb_path), config.path_of('_tl', _tl_path), config.path_of('_gsi', _gsi_path), config.path_of('_pya', _pya_path)],
extra_link_args=config.link_args('rdbcore'),
extra_compile_args=config.compile_args('rdbcore'),
sources=list(rdb_sources))
rdb = Extension(
config.root + ".rdbcore",
define_macros=config.macros(),
include_dirs=[_rdb_path, _tl_path, _gsi_path, _pya_path],
extra_objects=[
config.path_of("_rdb", _rdb_path),
config.path_of("_tl", _tl_path),
config.path_of("_gsi", _gsi_path),
config.path_of("_pya", _pya_path),
],
extra_link_args=config.link_args("rdbcore"),
extra_compile_args=config.compile_args("rdbcore"),
sources=list(rdb_sources),
)
# ------------------------------------------------------------------
# lay extension library
@ -737,28 +903,34 @@ lay = Extension(config.root + '.laycore',
# ------------------------------------------------------------------
# Core setup function
if __name__ == '__main__':
setup(name=config.root,
version=config.version(),
license='GNU GPLv3',
description='KLayout standalone Python package',
long_description='This package is a standalone distribution of KLayout\'s Python API.\n\nFor more details see here: https://www.klayout.org/klayout-pypi',
author='Matthias Koefferlein',
author_email='matthias@klayout.de',
classifiers=[
# Recommended classifiers
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: MacOS :: MacOS X",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX :: Linux",
# Optional classifiers
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
],
url='https://github.com/klayout/klayout',
packages=find_packages('src/pymod/distutils_src'),
package_dir={'': 'src/pymod/distutils_src'}, # https://github.com/pypa/setuptools/issues/230
package_data={config.root: ["src/pymod/distutils_src/klayout/*.pyi"]},
include_package_data=True,
ext_modules=[_tl, _gsi, _pya, _rba, _db, _lib, _rdb, _lym, _laybasic, _layview, _ant, _edt, _img] + db_plugins + [tl, db, lib, rdb, lay])
if __name__ == "__main__":
setup(
name=config.root,
version=config.version(),
license="GNU GPLv3",
description="KLayout standalone Python package",
long_description="This package is a standalone distribution of KLayout's Python API.\n\nFor more details see here: https://www.klayout.org/klayout-pypi",
author="Matthias Koefferlein",
author_email="matthias@klayout.de",
classifiers=[
# Recommended classifiers
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: MacOS :: MacOS X",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX :: Linux",
# Optional classifiers
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
],
url="https://github.com/klayout/klayout",
packages=find_packages("src/pymod/distutils_src"),
package_dir={
"": "src/pymod/distutils_src"
}, # https://github.com/pypa/setuptools/issues/230
package_data={config.root: ["src/pymod/distutils_src/klayout/*.pyi"]},
include_package_data=True,
ext_modules=[_tl, _gsi, _pya, _rba, _db, _lib, _rdb, _lym, _laybasic, _layview, _ant, _edt, _img]
+ db_plugins
+ [tl, db, lib, rdb, lay]
)

View File

@ -37,7 +37,7 @@ namespace gsi
// db::Library binding
/**
* @brief A basic implementation of the library
* @brief A basic implementation of the library
*/
static db::Library *new_lib ()
{
@ -173,15 +173,15 @@ LibraryClass decl_Library ("db", "Library",
"\n"
"This method has been introduced in version 0.25.\n"
) +
gsi::method ("name", &db::Library::get_name,
gsi::method ("name", &db::Library::get_name,
"@brief Returns the libraries' name\n"
"The name is set when the library is registered and cannot be changed\n"
) +
gsi::method ("id", &db::Library::get_id,
gsi::method ("id", &db::Library::get_id,
"@brief Returns the library's ID\n"
"The ID is set when the library is registered and cannot be changed \n"
) +
gsi::method ("description", &db::Library::get_description,
gsi::method ("description", &db::Library::get_description,
"@brief Returns the libraries' description text\n"
) +
gsi::method ("description=", &db::Library::set_description, gsi::arg ("description"),
@ -232,7 +232,7 @@ LibraryClass decl_Library ("db", "Library",
gsi::method ("layout_const", (const db::Layout &(db::Library::*)() const) &db::Library::layout,
"@brief The layout object where the cells reside that this library defines (const version)\n"
) +
gsi::method ("layout", (db::Layout &(db::Library::*)()) &db::Library::layout,
gsi::method ("layout", (db::Layout &(db::Library::*)()) &db::Library::layout,
"@brief The layout object where the cells reside that this library defines\n"
) +
gsi::method ("refresh", &db::Library::refresh,
@ -396,7 +396,7 @@ class PCellDeclarationImpl
: public db::PCellDeclaration
{
public:
// dummy implementation to provide the signature
// dummy implementation to provide the signature
virtual std::vector<db::LayerProperties> get_layer_declarations_impl (const db::pcell_parameters_type &) const
{
return std::vector<db::LayerProperties> ();
@ -433,7 +433,7 @@ public:
}
}
// dummy implementation to provide the signature
// dummy implementation to provide the signature
virtual db::pcell_parameters_type coerce_parameters_impl (const db::Layout & /*layout*/, const db::pcell_parameters_type &input) const
{
return input;
@ -581,7 +581,7 @@ Class<PCellDeclarationImpl> decl_PCellDeclaration (decl_PCellDeclaration_Native,
"This method receives the PCell parameters which allows it to deduce layers\n"
"from the parameters."
) +
gsi::callback ("get_parameters", &PCellDeclarationImpl::get_parameter_declarations, &PCellDeclarationImpl::cb_get_parameter_declarations,
gsi::callback ("get_parameters", &PCellDeclarationImpl::get_parameter_declarations, &PCellDeclarationImpl::cb_get_parameter_declarations,
"@brief Returns a list of parameter declarations\n"
"Reimplement this method to return a list of parameters used in that PCell \n"
"implementation. A parameter declaration is a PCellParameterDeclaration object\n"
@ -824,33 +824,33 @@ Class<db::PCellParameterDeclaration> decl_PCellParameterDeclaration ("db", "PCel
"@param default The default (initial) value\n"
"@param unit The unit string\n"
) +
gsi::method ("name", &db::PCellParameterDeclaration::get_name,
gsi::method ("name", &db::PCellParameterDeclaration::get_name,
"@brief Gets the name\n"
) +
gsi::method ("name=", &db::PCellParameterDeclaration::set_name, gsi::arg ("value"),
"@brief Sets the name\n"
) +
gsi::method ("unit", &db::PCellParameterDeclaration::get_unit,
gsi::method ("unit", &db::PCellParameterDeclaration::get_unit,
"@brief Gets the unit string\n"
) +
gsi::method ("unit=", &db::PCellParameterDeclaration::set_unit, gsi::arg ("unit"),
"@brief Sets the unit string\n"
"The unit string is shown right to the edit fields for numeric parameters.\n"
) +
gsi::method_ext ("type", &get_type,
gsi::method_ext ("type", &get_type,
"@brief Gets the type\n"
"The type is one of the T... constants."
) +
gsi::method_ext ("type=", &set_type, gsi::arg ("type"),
"@brief Sets the type\n"
) +
gsi::method ("description", &db::PCellParameterDeclaration::get_description,
gsi::method ("description", &db::PCellParameterDeclaration::get_description,
"@brief Gets the description text\n"
) +
gsi::method ("description=", &db::PCellParameterDeclaration::set_description, gsi::arg ("description"),
"@brief Sets the description\n"
) +
gsi::method ("hidden?", &db::PCellParameterDeclaration::is_hidden,
gsi::method ("hidden?", &db::PCellParameterDeclaration::is_hidden,
"@brief Returns true, if the parameter is a hidden parameter that should not be shown in the user interface\n"
"By making a parameter hidden, it is possible to create internal parameters which cannot be\n"
"edited.\n"
@ -858,7 +858,7 @@ Class<db::PCellParameterDeclaration> decl_PCellParameterDeclaration ("db", "PCel
gsi::method ("hidden=", &db::PCellParameterDeclaration::set_hidden, gsi::arg ("flag"),
"@brief Makes the parameter hidden if this attribute is set to true\n"
) +
gsi::method ("readonly?", &db::PCellParameterDeclaration::is_readonly,
gsi::method ("readonly?", &db::PCellParameterDeclaration::is_readonly,
"@brief Returns true, if the parameter is a read-only parameter\n"
"By making a parameter read-only, it is shown but cannot be\n"
"edited.\n"
@ -866,7 +866,7 @@ Class<db::PCellParameterDeclaration> decl_PCellParameterDeclaration ("db", "PCel
gsi::method ("readonly=", &db::PCellParameterDeclaration::set_readonly, gsi::arg ("flag"),
"@brief Makes the parameter read-only if this attribute is set to true\n"
) +
gsi::method_ext ("clear_choices", &clear_choices,
gsi::method_ext ("clear_choices", &clear_choices,
"@brief Clears the list of choices\n"
) +
gsi::method_ext ("add_choice", &add_choice, gsi::arg ("description"), gsi::arg ("value"),
@ -875,13 +875,13 @@ Class<db::PCellParameterDeclaration> decl_PCellParameterDeclaration ("db", "PCel
"choices. If choices are defined, KLayout will show a drop-down box instead of an\n"
"entry field in the parameter user interface.\n"
) +
gsi::method ("choice_values", &db::PCellParameterDeclaration::get_choices,
gsi::method ("choice_values", &db::PCellParameterDeclaration::get_choices,
"@brief Returns a list of choice values\n"
) +
gsi::method ("choice_descriptions", &db::PCellParameterDeclaration::get_choice_descriptions,
gsi::method ("choice_descriptions", &db::PCellParameterDeclaration::get_choice_descriptions,
"@brief Returns a list of choice descriptions\n"
) +
gsi::method ("default", &db::PCellParameterDeclaration::get_default,
gsi::method ("default", &db::PCellParameterDeclaration::get_default,
"@brief Gets the default value\n"
) +
gsi::method ("default=", &db::PCellParameterDeclaration::set_default, gsi::arg ("value"),
@ -910,4 +910,3 @@ Class<db::PCellParameterDeclaration> decl_PCellParameterDeclaration ("db", "PCel
);
}

View File

@ -33,7 +33,7 @@ namespace gsi
// point binding
template <class C>
struct point_defs
struct point_defs
{
typedef typename C::coord_type coord_type;
@ -141,6 +141,14 @@ struct point_defs
"\n"
"Starting with version 0.25, this method renders a vector."
) +
method ("-", (C (C::*) (const db::vector<coord_type> &) const) &C::subtract, gsi::arg ("v"),
"@brief Subtract one vector from a point\n"
"\n"
"\n"
"Subtract vector v from from self by subtracting the coordinates. This renders a point.\n"
"\n"
"This method has been added in version 0.27."
) +
method ("<", &C::less, gsi::arg ("p"),
"@brief \"less\" comparison operator\n"
"\n"

View File

@ -523,6 +523,13 @@ Class<db::RecursiveShapeIterator> decl_RecursiveShapeIterator ("db", "RecursiveS
"The flags must be specified before the shapes are being retrieved.\n"
"Settings the shapes flags will reset the iterator.\n"
) +
gsi::method ("shape_flags", (unsigned int (db::RecursiveShapeIterator::*)() const) &db::RecursiveShapeIterator::shape_flags,
"@brief Gets the shape selection flags\n"
"\n"
"See \\shape_flags= for a description of that property.\n"
"\n"
"This getter has been introduced in version 0.28.\n"
) +
gsi::method ("trans|#itrans", &db::RecursiveShapeIterator::trans,
"@brief Gets the current transformation by which the shapes must be transformed into the initial cell\n"
"\n"

View File

@ -752,7 +752,7 @@ extern Class<db::ShapeCollection> decl_dbShapeCollection;
Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
constructor ("new", &new_v,
constructor ("new", &new_v,
"@brief Default constructor\n"
"\n"
"This constructor creates an empty region.\n"
@ -798,8 +798,8 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"@code\n"
"layout = ... # a layout\n"
"cell = ... # the index of the initial cell\n"
"layer = ... # the index of the layer from where to take the shapes from\n"
"cell = ... # the index of the initial cell\n"
"layer = ... # the index of the layer from where to take the shapes from\n"
"r = RBA::Region::new(layout.begin_shapes(cell, layer))\n"
"@/code\n"
) +
@ -814,8 +814,8 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"@code\n"
"layout = ... # a layout\n"
"cell = ... # the index of the initial cell\n"
"layer = ... # the index of the layer from where to take the shapes from\n"
"cell = ... # the index of the initial cell\n"
"layer = ... # the index of the layer from where to take the shapes from\n"
"dbu = 0.1 # the target database unit\n"
"r = RBA::Region::new(layout.begin_shapes(cell, layer), RBA::ICplxTrans::new(layout.dbu / dbu))\n"
"@/code\n"
@ -925,11 +925,11 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"as single regions and artificial edges such as cut-lines will not be considered.\n"
"Merged semantics thus is equivalent to considering coherent areas rather than\n"
"single polygons\n"
) +
) +
method ("merged_semantics?", &db::Region::merged_semantics,
"@brief Gets a flag indicating whether merged semantics is enabled\n"
"See \\merged_semantics= for a description of this attribute.\n"
) +
) +
method ("strict_handling=", &db::Region::set_strict_handling, gsi::arg ("f"),
"@brief Enables or disables strict handling\n"
"\n"
@ -941,7 +941,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"Strict handling is disabled by default and optimization is in place.\n"
"\n"
"This method has been introduced in version 0.23.2."
) +
) +
method ("strict_handling?", &db::Region::strict_handling,
"@brief Gets a flag indicating whether merged semantics is enabled\n"
"See \\strict_handling= for a description of this attribute.\n"
@ -957,11 +957,11 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"created which touch at a corner).\n"
"\n"
"The default setting is maximum coherence (min_coherence = false).\n"
) +
) +
method ("min_coherence?", &db::Region::min_coherence,
"@brief Gets a flag indicating whether minimum coherence is selected\n"
"See \\min_coherence= for a description of this attribute.\n"
) +
) +
method_ext ("complex_op", &complex_op, gsi::arg ("node"),
"@brief Executes a complex operation (see \\CompoundRegionOperationNode for details)\n"
"\n"
@ -1337,7 +1337,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"of these boxes for example.\n"
"\n"
"Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n"
) +
) +
method_ext ("extents", &extents1, gsi::arg ("d"),
"@brief Returns a region with the enlarged bounding boxes of the polygons\n"
"This method will return a region consisting of the bounding boxes of the polygons enlarged by the given distance d.\n"
@ -1346,7 +1346,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"of these boxes for example.\n"
"\n"
"Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n"
) +
) +
method_ext ("extents", &extents2, gsi::arg ("dx"), gsi::arg ("dy"),
"@brief Returns a region with the enlarged bounding boxes of the polygons\n"
"This method will return a region consisting of the bounding boxes of the polygons enlarged by the given distance dx in x direction and dy in y direction.\n"
@ -1355,7 +1355,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"of these boxes for example.\n"
"\n"
"Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n"
) +
) +
method_ext ("extent_refs", &extent_refs,
"@hide\n"
"This method is provided for DRC implementation.\n"
@ -1589,7 +1589,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"This method returns the sized region (see \\size), but does not modify self.\n"
"\n"
"Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n"
) +
) +
method_ext ("andnot", &andnot, gsi::arg ("other"),
"@brief Returns the boolean AND and NOT between self and the other region\n"
"\n"
@ -1607,7 +1607,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"This method will compute the boolean AND (intersection) between two regions. "
"The result is often but not necessarily always merged.\n"
) +
) +
method ("&=", &db::Region::operator&=, gsi::arg ("other"),
"@brief Performs the boolean AND between self and the other region\n"
"\n"
@ -1615,7 +1615,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"This method will compute the boolean AND (intersection) between two regions. "
"The result is often but not necessarily always merged.\n"
) +
) +
method ("-", &db::Region::operator-, gsi::arg ("other"),
"@brief Returns the boolean NOT between self and the other region\n"
"\n"
@ -1623,7 +1623,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"This method will compute the boolean NOT (intersection) between two regions. "
"The result is often but not necessarily always merged.\n"
) +
) +
method ("-=", &db::Region::operator-=, gsi::arg ("other"),
"@brief Performs the boolean NOT between self and the other region\n"
"\n"
@ -1631,15 +1631,15 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"This method will compute the boolean NOT (intersection) between two regions. "
"The result is often but not necessarily always merged.\n"
) +
) +
method ("^", &db::Region::operator^, gsi::arg ("other"),
"@brief Returns the boolean NOT between self and the other region\n"
"@brief Returns the boolean XOR between self and the other region\n"
"\n"
"@return The result of the boolean XOR operation\n"
"\n"
"This method will compute the boolean XOR (intersection) between two regions. "
"The result is often but not necessarily always merged.\n"
) +
) +
method ("^=", &db::Region::operator^=, gsi::arg ("other"),
"@brief Performs the boolean XOR between self and the other region\n"
"\n"
@ -1647,7 +1647,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"This method will compute the boolean XOR (intersection) between two regions. "
"The result is often but not necessarily always merged.\n"
) +
) +
method ("\\|", &db::Region::operator|, gsi::arg ("other"),
"@brief Returns the boolean OR between self and the other region\n"
"\n"
@ -1655,7 +1655,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"The boolean OR is implemented by merging the polygons of both regions. To simply join the regions "
"without merging, the + operator is more efficient."
) +
) +
method ("\\|=", &db::Region::operator|=, gsi::arg ("other"),
"@brief Performs the boolean OR between self and the other region\n"
"\n"
@ -1663,7 +1663,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"The boolean OR is implemented by merging the polygons of both regions. To simply join the regions "
"without merging, the + operator is more efficient."
) +
) +
method ("+", &db::Region::operator+, gsi::arg ("other"),
"@brief Returns the combined region of self and the other region\n"
"\n"
@ -1671,7 +1671,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"This operator adds the polygons of the other region to self and returns a new combined region. "
"This usually creates unmerged regions and polygons may overlap. Use \\merge if you want to ensure the result region is merged.\n"
) +
) +
method ("+=", &db::Region::operator+=, gsi::arg ("other"),
"@brief Adds the polygons of the other region to self\n"
"\n"
@ -1679,7 +1679,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"This operator adds the polygons of the other region to self. "
"This usually creates unmerged regions and polygons may overlap. Use \\merge if you want to ensure the result region is merged.\n"
) +
) +
method ("covering", &db::Region::selected_enclosing, gsi::arg ("other"), gsi::arg ("min_count", size_t (1)), gsi::arg ("max_count", size_t (std::numeric_limits<size_t>::max ()), "unlimited"),
"@brief Returns the polygons of this region which are completely covering polygons from the other region\n"
"\n"
@ -1740,14 +1740,14 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"@return A new region containing the polygons which are inside polygons from the other region\n"
"\n"
"Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n"
) +
) +
method ("not_inside", &db::Region::selected_not_inside, gsi::arg ("other"),
"@brief Returns the polygons of this region which are not completely inside polygons from the other region\n"
"\n"
"@return A new region containing the polygons which are not inside polygons from the other region\n"
"\n"
"Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n"
) +
) +
method_ext ("split_inside", &split_inside, gsi::arg ("other"),
"@brief Returns the polygons of this region which are completely inside polygons from the other region and the ones which are not at the same time\n"
"\n"
@ -1764,28 +1764,28 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"@return The region after the polygons have been selected (self)\n"
"\n"
"Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n"
) +
) +
method ("select_not_inside", &db::Region::select_not_inside, gsi::arg ("other"),
"@brief Selects the polygons of this region which are not completely inside polygons from the other region\n"
"\n"
"@return The region after the polygons have been selected (self)\n"
"\n"
"Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n"
) +
) +
method ("outside", &db::Region::selected_outside, gsi::arg ("other"),
"@brief Returns the polygons of this region which are completely outside polygons from the other region\n"
"\n"
"@return A new region containing the polygons which are outside polygons from the other region\n"
"\n"
"Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n"
) +
) +
method ("not_outside", &db::Region::selected_not_outside, gsi::arg ("other"),
"@brief Returns the polygons of this region which are not completely outside polygons from the other region\n"
"\n"
"@return A new region containing the polygons which are not outside polygons from the other region\n"
"\n"
"Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n"
) +
) +
method_ext ("split_outside", &split_outside, gsi::arg ("other"),
"@brief Returns the polygons of this region which are completely outside polygons from the other region and the ones which are not at the same time\n"
"\n"
@ -1802,14 +1802,14 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"@return The region after the polygons have been selected (self)\n"
"\n"
"Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n"
) +
) +
method ("select_not_outside", &db::Region::select_not_outside, gsi::arg ("other"),
"@brief Selects the polygons of this region which are not completely outside polygons from the other region\n"
"\n"
"@return The region after the polygons have been selected (self)\n"
"\n"
"Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n"
) +
) +
method ("interacting", (db::Region (db::Region::*) (const db::Region &, size_t, size_t) const) &db::Region::selected_interacting, gsi::arg ("other"), gsi::arg ("min_count", size_t (1)), gsi::arg ("max_count", size_t (std::numeric_limits<size_t>::max ()), "unlimited"),
"@brief Returns the polygons of this region which overlap or touch polygons from the other region\n"
"\n"
@ -2020,7 +2020,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n"
"\n"
"The count options have been introduced in version 0.27."
) +
) +
method ("not_overlapping", &db::Region::selected_not_overlapping, gsi::arg ("other"), gsi::arg ("min_count", size_t (1)), gsi::arg ("max_count", size_t (std::numeric_limits<size_t>::max ()), "unlimited"),
"@brief Returns the polygons of this region which do not overlap polygons from the other region\n"
"\n"
@ -2129,7 +2129,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"possibilities of the edge collection.\n"
"\n"
"Merged semantics applies for this method (see \\merged_semantics= for a description of this concept)\n"
) +
) +
factory_ext ("decompose_convex", &decompose_convex<db::Shapes>, gsi::arg ("preferred_orientation", po_any (), "\\Polygon#PO_any"),
"@brief Decomposes the region into convex pieces.\n"
"\n"
@ -2166,7 +2166,7 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
method ("swap", &db::Region::swap, gsi::arg ("other"),
"@brief Swap the contents of this region with the contents of another region\n"
"This method is useful to avoid excessive memory allocation in some cases. "
"For managed memory languages such as Ruby, those cases will be rare. "
"For managed memory languages such as Ruby, those cases will be rare. "
) +
method ("holes", &db::Region::holes,
"@brief Returns the holes of the region\n"
@ -3091,4 +3091,3 @@ gsi::EnumIn<db::Region, db::OppositeFilter> decl_Region_OppositeFilter ("db", "O
gsi::ClassExt<db::Region> inject_OppositeFilter_in_parent (decl_Region_OppositeFilter.defs ());
}

View File

@ -304,7 +304,7 @@ struct trans_defs
"\n"
"This convenience method has been introduced in version 0.25."
) +
method ("*", &C::concat, arg ("t"),
method ("*!", &C::concat, arg ("t"),
"@brief Returns the concatenated transformation\n"
"\n"
"The * operator returns self*t (\"t is applied before this transformation\").\n"
@ -829,7 +829,7 @@ struct cplx_trans_defs
"\n"
"This convenience method has been introduced in version 0.25."
) +
method ("*", (C (C::*) (const C &c) const) &C::concat_same, arg ("t"),
method ("*!", (C (C::*) (const C &c) const) &C::concat_same, arg ("t"),
"@brief Returns the concatenated transformation\n"
"\n"
"The * operator returns self*t (\"t is applied before this transformation\").\n"
@ -1047,7 +1047,7 @@ Class<db::DCplxTrans> decl_DCplxTrans ("db", "DCplxTrans",
"\n"
"This method has been introduced in version 0.25."
) +
method ("*", (db::CplxTrans (db::DCplxTrans::*) (const db::CplxTrans &) const) &db::DCplxTrans::concat, gsi::arg ("t"),
method ("*!", (db::CplxTrans (db::DCplxTrans::*) (const db::CplxTrans &) const) &db::DCplxTrans::concat, gsi::arg ("t"),
"@brief Multiplication (concatenation) of transformations\n"
"\n"
"The * operator returns self*t (\"t is applied before this transformation\").\n"
@ -1126,7 +1126,7 @@ Class<db::CplxTrans> decl_CplxTrans ("db", "CplxTrans",
"\n"
"This method has been introduced in version 0.25."
) +
method ("*", (db::DCplxTrans (db::CplxTrans::*) (const db::VCplxTrans &) const) &db::CplxTrans::concat, gsi::arg ("t"),
method ("*!", (db::DCplxTrans (db::CplxTrans::*) (const db::VCplxTrans &) const) &db::CplxTrans::concat, gsi::arg ("t"),
"@brief Multiplication (concatenation) of transformations\n"
"\n"
"The * operator returns self*t (\"t is applied before this transformation\").\n"
@ -1134,7 +1134,7 @@ Class<db::CplxTrans> decl_CplxTrans ("db", "CplxTrans",
"@param t The transformation to apply before\n"
"@return The modified transformation\n"
) +
method ("*", (db::CplxTrans (db::CplxTrans::*) (const db::ICplxTrans &) const) &db::CplxTrans::concat, gsi::arg ("t"),
method ("*!", (db::CplxTrans (db::CplxTrans::*) (const db::ICplxTrans &) const) &db::CplxTrans::concat, gsi::arg ("t"),
"@brief Multiplication (concatenation) of transformations\n"
"\n"
"The * operator returns self*t (\"t is applied before this transformation\").\n"
@ -1216,7 +1216,7 @@ Class<db::ICplxTrans> decl_ICplxTrans ("db", "ICplxTrans",
"\n"
"This method has been introduced in version 0.25."
) +
method ("*", (db::VCplxTrans (db::ICplxTrans::*) (const db::VCplxTrans &) const) &db::ICplxTrans::concat, gsi::arg ("t"),
method ("*!", (db::VCplxTrans (db::ICplxTrans::*) (const db::VCplxTrans &) const) &db::ICplxTrans::concat, gsi::arg ("t"),
"@brief Multiplication (concatenation) of transformations\n"
"\n"
"The * operator returns self*t (\"t is applied before this transformation\").\n"
@ -1288,7 +1288,7 @@ Class<db::VCplxTrans> decl_VCplxTrans ("db", "VCplxTrans",
"\n"
"This method has been introduced in version 0.25."
) +
method ("*", (db::VCplxTrans (db::VCplxTrans::*) (const db::DCplxTrans &) const) &db::VCplxTrans::concat, gsi::arg ("t"),
method ("*!", (db::VCplxTrans (db::VCplxTrans::*) (const db::DCplxTrans &) const) &db::VCplxTrans::concat, gsi::arg ("t"),
"@brief Multiplication (concatenation) of transformations\n"
"\n"
"The * operator returns self*t (\"t is applied before this transformation\").\n"
@ -1296,7 +1296,7 @@ Class<db::VCplxTrans> decl_VCplxTrans ("db", "VCplxTrans",
"@param t The transformation to apply before\n"
"@return The modified transformation\n"
) +
method ("*", (db::ICplxTrans (db::VCplxTrans::*) (const db::CplxTrans &) const) &db::VCplxTrans::concat, gsi::arg ("t"),
method ("*!", (db::ICplxTrans (db::VCplxTrans::*) (const db::CplxTrans &) const) &db::VCplxTrans::concat, gsi::arg ("t"),
"@brief Multiplication (concatenation) of transformations\n"
"\n"
"The * operator returns self*t (\"t is applied before this transformation\").\n"

View File

@ -246,6 +246,12 @@ struct vector_defs
"\n"
"The scalar product of a and b is defined as: vp = ax*bx+ay*by.\n"
) +
method_ext ("*", &sprod, gsi::arg ("v"),
"@brief Computes the scalar product between self and the given vector\n"
"\n"
"\n"
"The scalar product of a and b is defined as: vp = ax*bx+ay*by.\n"
) +
method_ext ("sprod_sign", &sprod_sign, gsi::arg ("v"),
"@brief Computes the scalar product between self and the given vector and returns a value indicating the sign of the product\n"
"\n"

View File

@ -49,6 +49,7 @@ static int t_double () { return T_double; }
static int t_float () { return T_float; }
static int t_var () { return T_var; }
static int t_string () { return T_string; }
static int t_byte_array () { return T_byte_array; }
static int t_void_ptr () { return T_void_ptr; }
static int t_object () { return T_object; }
static int t_vector () { return T_vector; }
@ -77,30 +78,31 @@ static tl::Variant default_value (const ArgType *t)
}
Class<ArgType> decl_ArgType ("tl", "ArgType",
gsi::method ("TypeVoid|#t_void", &t_void) +
gsi::method ("TypeBool|#t_bool", &t_bool) +
gsi::method ("TypeChar|#t_char", &t_char) +
gsi::method ("TypeSChar|#t_schar", &t_schar) +
gsi::method ("TypeUChar|#t_uchar", &t_uchar) +
gsi::method ("TypeShort|#t_short", &t_short) +
gsi::method ("TypeUShort|#t_ushort", &t_ushort) +
gsi::method ("TypeInt|#t_int", &t_int) +
gsi::method ("TypeUInt|#t_uint", &t_uint) +
gsi::method ("TypeLong|#t_long", &t_long) +
gsi::method ("TypeULong|#t_ulong", &t_ulong) +
gsi::method ("TypeLongLong|#t_longlong", &t_longlong) +
gsi::method ("TypeULongLong|#t_ulonglong", &t_ulonglong) +
gsi::method ("TypeVoid", &t_void) +
gsi::method ("TypeBool", &t_bool) +
gsi::method ("TypeChar", &t_char) +
gsi::method ("TypeSChar", &t_schar) +
gsi::method ("TypeUChar", &t_uchar) +
gsi::method ("TypeShort", &t_short) +
gsi::method ("TypeUShort", &t_ushort) +
gsi::method ("TypeInt", &t_int) +
gsi::method ("TypeUInt", &t_uint) +
gsi::method ("TypeLong", &t_long) +
gsi::method ("TypeULong", &t_ulong) +
gsi::method ("TypeLongLong", &t_longlong) +
gsi::method ("TypeULongLong", &t_ulonglong) +
#if defined(HAVE_64BIT_COORD)
gsi::method ("TypeInt128|#t_int128", &t_int128) +
#endif
gsi::method ("TypeDouble|#t_double", &t_double) +
gsi::method ("TypeFloat|#t_float", &t_float) +
gsi::method ("TypeVar|#t_var", &t_var) +
gsi::method ("TypeString|#t_string", &t_string) +
gsi::method ("TypeVoidPtr|#t_void_ptr", &t_void_ptr) +
gsi::method ("TypeObject|#t_object", &t_object) +
gsi::method ("TypeVector|#t_vector", &t_vector) +
gsi::method ("TypeMap|#t_map", &t_map) +
gsi::method ("TypeDouble", &t_double) +
gsi::method ("TypeFloat", &t_float) +
gsi::method ("TypeVar", &t_var) +
gsi::method ("TypeByteArray", &t_byte_array) +
gsi::method ("TypeString", &t_string) +
gsi::method ("TypeVoidPtr", &t_void_ptr) +
gsi::method ("TypeObject", &t_object) +
gsi::method ("TypeVector", &t_vector) +
gsi::method ("TypeMap", &t_map) +
gsi::method_ext ("type", &type,
"@brief Return the basic type (see t_.. constants)\n"
) +

View File

@ -199,6 +199,9 @@ private:
for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
if (syn->is_setter) {
add_method (syn->name + "=", *m);
} else if (syn->name == "*!") {
// non-commutative multiplication
add_method ("*", *m);
} else {
add_method (syn->name, *m);
}

View File

@ -85,7 +85,7 @@ void MethodBase::parse_name (const std::string &name)
{
const char *n = name.c_str ();
if (*n == '*' && n[1] && n[1] != '*' && n[1] != '=') {
if (*n == '*' && n[1] && n[1] != '*' && n[1] != '!' && n[1] != '=') {
m_protected = true;
++n;
}

View File

@ -589,3 +589,12 @@ TEST(10)
EXPECT_EQ (v.to_string (), std::string ("2"));
}
TEST(11)
{
tl::Eval e;
tl::Variant v;
// mapping of *! to *:
v = e.parse ("var b = Trans.new(1)*Trans.new(Vector.new(10, 20))").execute ();
EXPECT_EQ (v.to_string (), std::string ("r90 -20,10"));
}

View File

@ -149,6 +149,8 @@ full_name (const gsi::MethodBase::MethodSynonym &syn)
return syn.name + "?";
} else if (syn.is_setter) {
return syn.name + "=";
} else if (syn.name == "*!") {
return "*";
} else {
return syn.name;
}
@ -1411,7 +1413,7 @@ GSIHelpProvider::produce_class_doc (const std::string &cls) const
if (! pydoc.empty ()) {
os << "<p><b>";
os << tl::to_string (QObject::tr ("Python specific notes: "));
os << "</b><br/>" << escape_xml (pydoc) << "</p>" << std::endl;
os << "</b><br/>" << tl::replaced (escape_xml (pydoc), "\n\n", "<br/>") << "</p>" << std::endl;
}
os << "</td></tr>";

103
src/pya/pya/gsiDeclPya.cc Normal file
View File

@ -0,0 +1,103 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2022 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Python.h>
#include "gsiDecl.h"
#include "pyaInternal.h"
#include "pya.h"
namespace gsi
{
static const pya::MethodTableEntry *getter (std::pair<const pya::MethodTableEntry *, const pya::MethodTableEntry *> *p)
{
return p->second;
}
static const pya::MethodTableEntry *setter (std::pair<const pya::MethodTableEntry *, const pya::MethodTableEntry *> *p)
{
return p->first;
}
gsi::Class<std::pair<const pya::MethodTableEntry *, const pya::MethodTableEntry *> > decl_PythonGetterSetterPair ("tl", "PythonGetterSetterPair",
gsi::method_ext ("getter", &getter, "@brief Gets the getter function") +
gsi::method_ext ("setter", &setter, "@brief Gets the setter function"),
"@hide"
);
gsi::Class<pya::MethodTableEntry> decl_PythonFunction ("tl", "PythonFunction",
gsi::method ("methods", &pya::MethodTableEntry::methods, "@brief Gets the list of methods bound to this Python function") +
gsi::method ("name", &pya::MethodTableEntry::name, "@brief Gets the name of this Python function") +
gsi::method ("is_static", &pya::MethodTableEntry::is_static, "@brief Gets the value indicating whether this Python function is 'static' (class function)") +
gsi::method ("is_protected", &pya::MethodTableEntry::is_protected, "@brief Gets a value indicating whether this function is protected"),
"@hide"
);
static std::vector<const pya::MethodTableEntry *> get_python_methods (const gsi::ClassBase *cls, bool st)
{
const pya::MethodTable *mt = pya::MethodTable::method_table_by_class (cls);
std::vector<const pya::MethodTableEntry *> methods;
if (mt != 0) {
for (auto m = mt->method_table ().begin (); m != mt->method_table ().end (); ++m) {
if (m->is_enabled () && m->is_static () == st) {
methods.push_back (m.operator-> ());
}
}
}
return methods;
}
static std::vector<std::pair<const pya::MethodTableEntry *, const pya::MethodTableEntry *> > get_python_properties (const gsi::ClassBase *cls, bool st)
{
const pya::MethodTable *mt = pya::MethodTable::method_table_by_class (cls);
std::vector<std::pair<const pya::MethodTableEntry *, const pya::MethodTableEntry *> > methods;
if (mt != 0) {
for (auto m = mt->property_table ().begin (); m != mt->property_table ().end (); ++m) {
if (m->first.is_enabled () && m->first.is_static () == st) {
methods.push_back (std::make_pair (&m->first, &m->second));
}
}
}
return methods;
}
static
gsi::ClassExt<gsi::ClassBase> class_base_ext (
gsi::method_ext ("python_methods", &get_python_methods, gsi::arg ("static"), "@brief Gets the Python methods (static or non-static)") +
gsi::method_ext ("python_properties", &get_python_properties, gsi::arg ("static"), "@brief Gets the Python properties (static or non-static) as a list of getter/setter pairs\nNote that if a getter or setter is not available the list of Python functions for this part is empty."),
"@hide"
);
static
gsi::ClassExt<gsi::MethodBase> method_base_ext (
gsi::method_ext ("python_methods", &pya::PythonInterpreter::python_doc, "@brief Gets the Python specific documentation"),
"@hide"
);
}

View File

@ -68,7 +68,7 @@ namespace pya
} catch (...) { \
if (PythonInterpreter::instance ()) { PythonInterpreter::instance ()->end_execution (); } \
throw; \
}
}
/**
* @brief A class encapsulating a python exception
@ -92,7 +92,7 @@ class PYA_PUBLIC PythonInterpreter
{
public:
/**
* @brief The constructor
* @brief The constructor
*
* If embedded is true, the interpreter is an embedded one. Only in this case, the
* Python interpreter is initialized. Otherwise, it is assumed the interpreter
@ -101,7 +101,7 @@ public:
PythonInterpreter (bool embedded = true);
/**
* @brief The destructor
* @brief The destructor
*/
~PythonInterpreter ();
@ -160,10 +160,10 @@ public:
* @brief Implementation of gsi::Interpreter::eval_expr
*/
tl::Variant eval_expr (const char *string, const char *filename = 0, int line = 1, int context = -1);
/**
* @brief Implementation of gsi::Interpreter::eval_string_and_print
*/
*/
void eval_string_and_print (const char *string, const char *filename = 0, int line = 1, int context = -1);
/**
@ -172,7 +172,7 @@ public:
virtual gsi::Inspector *inspector (int context = -1);
/**
* @brief Defines a global variable with the given name and value
* @brief Defines a global variable with the given name and value
*/
void define_variable (const std::string &name, const tl::Variant &value);
@ -284,4 +284,3 @@ private:
}
#endif

View File

@ -11,13 +11,16 @@ SOURCES = \
pyaConvert.cc \
pyaHelpers.cc \
pyaInspector.cc \
pyaInternal.cc \
pyaCallables.cc \
pyaMarshal.cc \
pyaObject.cc \
pyaRefs.cc \
pyaUtils.cc \
pyaModule.cc \
pyaSignalHandler.cc \
pyaStatusChangedListener.cc
pyaStatusChangedListener.cc \
gsiDeclPya.cc
HEADERS += \
pya.h \
@ -25,6 +28,8 @@ HEADERS += \
pyaConvert.h \
pyaHelpers.h \
pyaInspector.h \
pyaInternal.h \
pyaCallables.h \
pyaMarshal.h \
pyaObject.h \
pyaRefs.h \

1739
src/pya/pya/pyaCallables.cc Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,71 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2022 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _HDR_pyaCallables
#define _HDR_pyaCallables
#include <Python.h>
#include "pyaCommon.h"
namespace pya
{
void pya_object_deallocate (PyObject *self);
int pya_object_init (PyObject * /*self*/, PyObject *args, PyObject *kwds);
PyObject *pya_object_new (PyTypeObject *type, PyObject * /*args*/, PyObject * /*kwds*/);
PyObject *object_default_ne_impl (PyObject *self, PyObject *args);
PyObject *object_default_ge_impl (PyObject *self, PyObject *args);
PyObject *object_default_le_impl (PyObject *self, PyObject *args);
PyObject *object_default_gt_impl (PyObject *self, PyObject *args);
PyObject *object_default_deepcopy_impl (PyObject *self, PyObject *args);
typedef PyObject *(*py_func_ptr_t) (PyObject *, PyObject *);
py_func_ptr_t get_method_adaptor (int n);
py_func_ptr_t get_property_getter_adaptor (int n);
py_func_ptr_t get_property_setter_adaptor (int n);
py_func_ptr_t get_method_init_adaptor (int n);
inline void *make_closure (int mid_getter, int mid_setter)
{
size_t g = mid_getter < 0 ? (size_t) 0 : (size_t) mid_getter;
size_t s = mid_setter < 0 ? (size_t) 0 : (size_t) mid_setter;
return (void *) ((s << 16) | g);
}
inline unsigned int getter_from_closure (void *closure)
{
return (unsigned int) (size_t (closure) & 0xffff);
}
inline unsigned int setter_from_closure (void *closure)
{
return (unsigned int) (size_t (closure) >> 16);
}
PyObject *property_getter_func (PyObject *self, void *closure);
int property_setter_func (PyObject *self, PyObject *value, void *closure);
}
#endif

View File

@ -45,14 +45,14 @@ long python2c_func<long>::operator() (PyObject *rval)
#if PY_MAJOR_VERSION < 3
if (PyInt_Check (rval)) {
return PyInt_AsLong (rval);
} else
} else
#endif
if (PyLong_Check (rval)) {
return PyLong_AsLong (rval);
} else if (PyFloat_Check (rval)) {
return (long) (PyFloat_AsDouble (rval));
} else {
throw tl::Exception (tl::to_string (tr ("Value cannot be converted to an integer")));
throw tl::TypeError (tl::to_string (tr ("Value cannot be converted to an integer")));
}
}
@ -68,14 +68,14 @@ char python2c_func<char>::operator() (PyObject *rval)
#if PY_MAJOR_VERSION < 3
if (PyInt_Check (rval)) {
return char (PyInt_AsLong (rval));
} else
} else
#endif
if (PyLong_Check (rval)) {
return char (PyLong_AsLong (rval));
} else if (PyFloat_Check (rval)) {
return char (PyFloat_AsDouble (rval));
} else {
throw tl::Exception (tl::to_string (tr ("Value cannot be converted to a character")));
throw tl::TypeError (tl::to_string (tr ("Value cannot be converted to a character")));
}
}
@ -85,14 +85,14 @@ unsigned long python2c_func<unsigned long>::operator() (PyObject *rval)
#if PY_MAJOR_VERSION < 3
if (PyInt_Check (rval)) {
return PyInt_AsUnsignedLongMask (rval);
} else
} else
#endif
if (PyLong_Check (rval)) {
return PyLong_AsUnsignedLongMask (rval);
} else if (PyFloat_Check (rval)) {
return (unsigned long) (PyFloat_AsDouble (rval));
} else {
throw tl::Exception (tl::to_string (tr ("Value cannot be converted to an integer")));
throw tl::TypeError (tl::to_string (tr ("Value cannot be converted to an integer")));
}
}
@ -102,14 +102,14 @@ long long python2c_func<long long>::operator() (PyObject *rval)
#if PY_MAJOR_VERSION < 3
if (PyInt_Check (rval)) {
return PyInt_AsLong (rval);
} else
} else
#endif
if (PyLong_Check (rval)) {
return PyLong_AsLongLong (rval);
} else if (PyFloat_Check (rval)) {
return (long long) (PyFloat_AsDouble (rval));
} else {
throw tl::Exception (tl::to_string (tr ("Value cannot be converted to an integer")));
throw tl::TypeError (tl::to_string (tr ("Value cannot be converted to an integer")));
}
}
@ -119,14 +119,14 @@ unsigned long long python2c_func<unsigned long long>::operator() (PyObject *rval
#if PY_MAJOR_VERSION < 3
if (PyInt_Check (rval)) {
return PyInt_AsUnsignedLongMask (rval);
} else
} else
#endif
if (PyLong_Check (rval)) {
return PyLong_AsUnsignedLongLongMask (rval);
} else if (PyFloat_Check (rval)) {
return (unsigned long long) (PyFloat_AsDouble (rval));
} else {
throw tl::Exception (tl::to_string (tr ("Value cannot be converted to an integer")));
throw tl::TypeError (tl::to_string (tr ("Value cannot be converted to an integer")));
}
}
@ -138,14 +138,14 @@ __int128 python2c_func<__int128>::operator() (PyObject *rval)
#if PY_MAJOR_VERSION < 3
if (PyInt_Check (rval)) {
return PyInt_AsLong (rval);
} else
} else
#endif
if (PyLong_Check (rval)) {
return PyLong_AsLongLong (rval);
} else if (PyFloat_Check (rval)) {
return PyFloat_AsDouble (rval);
} else {
throw tl::Exception (tl::to_string (tr ("Value cannot be converted to an integer")));
throw tl::TypeError (tl::to_string (tr ("Value cannot be converted to an integer")));
}
}
#endif
@ -156,14 +156,14 @@ double python2c_func<double>::operator() (PyObject *rval)
#if PY_MAJOR_VERSION < 3
if (PyInt_Check (rval)) {
return PyInt_AsLong (rval);
} else
} else
#endif
if (PyLong_Check (rval)) {
return PyLong_AsLongLong (rval);
} else if (PyFloat_Check (rval)) {
return PyFloat_AsDouble (rval);
} else {
throw tl::Exception (tl::to_string (tr ("Value cannot be converted to a floating-point value")));
throw tl::TypeError (tl::to_string (tr ("Value cannot be converted to a floating-point value")));
}
}
@ -173,11 +173,11 @@ std::string python2c_func<std::string>::operator() (PyObject *rval)
#if PY_MAJOR_VERSION < 3
if (PyString_Check (rval)) {
return std::string (PyString_AsString (rval), PyString_Size (rval));
} else
} else
#else
if (PyBytes_Check (rval)) {
return std::string (PyBytes_AsString (rval), PyBytes_Size (rval));
} else
} else
#endif
if (PyUnicode_Check (rval)) {
PythonRef ba (PyUnicode_AsUTF8String (rval));
@ -188,7 +188,7 @@ std::string python2c_func<std::string>::operator() (PyObject *rval)
} else if (PyByteArray_Check (rval)) {
return std::string (PyByteArray_AsString (rval), PyByteArray_Size (rval));
} else {
throw tl::Exception (tl::to_string (tr ("Value cannot be converted to a string")));
throw tl::TypeError (tl::to_string (tr ("Value cannot be converted to a string")));
}
}
@ -224,7 +224,7 @@ std::vector<char> python2c_func<std::vector<char> >::operator() (PyObject *rval)
Py_ssize_t sz = PyByteArray_Size (rval);
return std::vector<char> (cp, cp + sz);
} else {
throw tl::Exception (tl::to_string (tr ("Value cannot be converted to a byte array")));
throw tl::TypeError (tl::to_string (tr ("Value cannot be converted to a byte array")));
}
}
@ -235,11 +235,11 @@ QByteArray python2c_func<QByteArray>::operator() (PyObject *rval)
#if PY_MAJOR_VERSION < 3
if (PyString_Check (rval)) {
return QByteArray (PyString_AsString (rval), PyString_Size (rval));
} else
} else
#else
if (PyBytes_Check (rval)) {
return QByteArray (PyBytes_AsString (rval), PyBytes_Size (rval));
} else
} else
#endif
if (PyUnicode_Check (rval)) {
PythonRef ba (PyUnicode_AsUTF8String (rval));
@ -250,7 +250,7 @@ QByteArray python2c_func<QByteArray>::operator() (PyObject *rval)
} else if (PyByteArray_Check (rval)) {
return QByteArray (PyByteArray_AsString (rval), PyByteArray_Size (rval));
} else {
throw tl::Exception (tl::to_string (tr ("Value cannot be converted to a byte array")));
throw tl::TypeError (tl::to_string (tr ("Value cannot be converted to a byte array")));
}
}
@ -325,13 +325,13 @@ tl::Variant python2c_func<tl::Variant>::operator() (PyObject *rval)
return r;
} else {
const gsi::ClassBase *cls = PythonModule::cls_for_type (Py_TYPE (rval));
if (cls) {
PYAObjectBase *p = PYAObjectBase::from_pyobject (rval);
// employ the tl::Variant binding capabilities of the Expression binding to derive the
// employ the tl::Variant binding capabilities of the Expression binding to derive the
// variant value.
void *obj = p->obj ();
@ -519,7 +519,7 @@ PyObject *c2python_func<const tl::Variant &>::operator() (const tl::Variant &c)
PyDict_SetItem (ret, c2python (i->first), c2python (i->second));
}
return ret;
} else if (c.is_list ()) {
PyObject *ret = PyList_New (c.get_list ().size ());
@ -619,4 +619,3 @@ PyObject *c2python_func<const QString &>::operator() (const QString &qs)
#endif
}

885
src/pya/pya/pyaInternal.cc Normal file
View File

@ -0,0 +1,885 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2022 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Python.h>
#include "pya.h"
#include "pyaInternal.h"
#include "pyaModule.h"
#include "pyaObject.h"
#include "tlLog.h"
#include <cctype>
namespace pya
{
// -------------------------------------------------------------------
// MethodTableEntry implementation
MethodTableEntry::MethodTableEntry (const std::string &name, bool st, bool prot)
: m_name (name), m_is_static (st), m_is_protected (prot), m_is_enabled (true), m_is_init (false), m_fallback_not_implemented (false)
{ }
const std::string &
MethodTableEntry::name () const
{
return m_name;
}
void
MethodTableEntry::set_name (const std::string &n)
{
m_name = n;
}
void
MethodTableEntry::set_enabled (bool en)
{
m_is_enabled = en;
}
bool
MethodTableEntry::is_enabled () const
{
return m_is_enabled;
}
void
MethodTableEntry::set_fallback_not_implemented (bool f)
{
m_fallback_not_implemented = f;
}
bool
MethodTableEntry::fallback_not_implemented () const
{
return m_fallback_not_implemented;
}
void
MethodTableEntry::set_init (bool f)
{
m_is_init = f;
}
bool
MethodTableEntry::is_init () const
{
return m_is_init;
}
bool
MethodTableEntry::is_static () const
{
return m_is_static;
}
bool
MethodTableEntry::is_protected () const
{
return m_is_protected;
}
void
MethodTableEntry::add (const gsi::MethodBase *m)
{
m_methods.push_back (m);
}
void
MethodTableEntry::finish ()
{
// remove duplicate entries in the method list
std::vector<const gsi::MethodBase *> m = m_methods;
std::sort(m.begin (), m.end ());
m_methods.assign (m.begin (), std::unique (m.begin (), m.end ()));
}
MethodTableEntry::method_iterator
MethodTableEntry::begin () const
{
return m_methods.begin ();
}
MethodTableEntry::method_iterator
MethodTableEntry::end () const
{
return m_methods.end ();
}
// -------------------------------------------------------------------
// MethodTable implementation
MethodTable::MethodTable (const gsi::ClassBase *cls_decl, PythonModule *module)
: m_method_offset (0), m_property_offset (0), mp_cls_decl (cls_decl), mp_module (module)
{
if (cls_decl->base ()) {
const MethodTable *base_mt = method_table_by_class (cls_decl->base ());
tl_assert (base_mt);
m_method_offset = base_mt->top_mid ();
m_property_offset = base_mt->top_property_mid ();
}
// signals are translated into the setters and getters
for (gsi::ClassBase::method_iterator m = cls_decl->begin_methods (); m != cls_decl->end_methods (); ++m) {
if ((*m)->is_signal ()) {
for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
add_getter (syn->name, *m);
add_setter (syn->name, *m);
}
}
}
// first add getters and setters
for (gsi::ClassBase::method_iterator m = cls_decl->begin_methods (); m != cls_decl->end_methods (); ++m) {
if (! (*m)->is_callback ()) {
for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
if (syn->is_getter) {
add_getter (syn->name, *m);
} else if (syn->is_setter) {
add_setter (syn->name, *m);
}
}
}
}
// then add normal methods - on name clash with properties make them a getter
for (gsi::ClassBase::method_iterator m = cls_decl->begin_methods (); m != cls_decl->end_methods (); ++m) {
if (! (*m)->is_callback () && ! (*m)->is_signal ()) {
bool st = (*m)->is_static ();
bool no_args = ((*m)->end_arguments () == (*m)->begin_arguments ());
for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
if (! syn->is_getter && ! syn->is_setter) {
if (no_args && is_property_setter (st, syn->name) && ! is_property_getter (st, syn->name)) {
add_getter (syn->name, *m);
} else if (st && no_args && (isupper (syn->name [0]) || (*m)->is_const ())) {
// static methods without arguments which start with a capital letter are treated as constants
add_getter (syn->name, *m);
} else {
add_method (syn->name, *m);
}
}
}
}
}
// synthesize a getter from is_...? predicates (e.g. is_empty? -> empty getter)
for (gsi::ClassBase::method_iterator m = cls_decl->begin_methods (); m != cls_decl->end_methods (); ++m) {
if (! (*m)->is_callback () && ! (*m)->is_signal ()) {
bool st = (*m)->is_static ();
bool no_args = ((*m)->end_arguments () == (*m)->begin_arguments ());
for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
if (no_args && ! syn->is_getter && ! syn->is_setter && syn->is_predicate && std::string (syn->name, 0, 3) == "is_") {
std::string n = std::string (syn->name, 3, std::string::npos);
if (is_property_setter (st, n) && ! is_property_getter (st, n)) {
add_getter (n, *m);
}
}
}
}
}
}
size_t
MethodTable::bottom_mid () const
{
return m_method_offset;
}
size_t
MethodTable::top_mid () const
{
return m_method_offset + m_table.size ();
}
size_t
MethodTable::bottom_property_mid () const
{
return m_property_offset;
}
size_t
MethodTable::top_property_mid () const
{
return m_property_offset + m_property_table.size ();
}
std::pair<bool, size_t>
MethodTable::find_method (bool st, const std::string &name) const
{
std::map <std::pair<bool, std::string>, size_t>::const_iterator t = m_name_map.find (std::make_pair (st, name));
if (t != m_name_map.end ()) {
return std::make_pair (true, t->second + m_method_offset);
} else {
return std::make_pair (false, 0);
}
}
std::pair<bool, size_t>
MethodTable::find_property (bool st, const std::string &name) const
{
std::map <std::pair<bool, std::string>, size_t>::const_iterator t = m_property_name_map.find (std::make_pair (st, name));
if (t != m_property_name_map.end ()) {
return std::make_pair (true, t->second + m_property_offset);
} else {
return std::make_pair (false, 0);
}
}
bool
MethodTable::is_property_setter (bool st, const std::string &name)
{
std::pair<bool, size_t> p = find_property (st, name);
if (! p.first) {
return false;
}
return (begin_setters (p.second) != end_setters (p.second));
}
bool
MethodTable::is_property_getter (bool st, const std::string &name)
{
std::pair<bool, size_t> p = find_property (st, name);
if (! p.first) {
return false;
}
return (begin_getters (p.second) != end_getters (p.second));
}
/**
* @brief Returns true, if the name is a reserved keyword
*/
static bool is_reserved_word (const std::string &name)
{
return (name == "and" ||
name == "del" ||
name == "from" ||
name == "not" ||
name == "while" ||
name == "as" ||
name == "elif" ||
name == "global" ||
name == "or" ||
name == "with" ||
name == "assert" ||
name == "else" ||
name == "if" ||
name == "pass" ||
name == "yield" ||
name == "break" ||
name == "except" ||
name == "import" ||
name == "print" ||
name == "class" ||
name == "exec" ||
name == "in" ||
name == "raise" ||
name == "continue" ||
name == "finally" ||
name == "is" ||
name == "return" ||
name == "def" ||
name == "for" ||
name == "lambda" ||
name == "try" ||
name == "None");
}
/**
* @brief Extracts the Python name from a generic name
*
* Returns an empty string if no Python name could be generated.
*/
static std::string extract_python_name (const std::string &name)
{
// some operator replacements
if (name == "++") {
return "inc";
} else if (name == "--") {
return "dec";
} else if (name == "()") {
return "call";
} else if (name == "!") {
return "not";
} else if (name == "==") {
return "__eq__";
} else if (name == "!=") {
return "__ne__";
} else if (name == "<") {
return "__lt__";
} else if (name == "<=") {
return "__le__";
} else if (name == ">") {
return "__gt__";
} else if (name == ">=") {
return "__ge__";
} else if (name == "<=>") {
return "__cmp__";
} else if (name == "+") {
return "__add__";
} else if (name == "+@") {
return "__pos__";
} else if (name == "-") {
return "__sub__";
} else if (name == "-@") {
return "__neg__";
} else if (name == "/") {
#if PY_MAJOR_VERSION < 3
return "__div__";
#else
return "__truediv__";
#endif
} else if (name == "*" || name == "*!") {
return "__mul__";
} else if (name == "%") {
return "__mod__";
} else if (name == "<<") {
return "__lshift__";
} else if (name == ">>") {
return "__rshift__";
} else if (name == "~") {
return "__invert__";
} else if (name == "&") {
return "__and__";
} else if (name == "|") {
return "__or__";
} else if (name == "^") {
return "__xor__";
} else if (name == "+=") {
return "__iadd__";
} else if (name == "-=") {
return "__isub__";
} else if (name == "/=") {
#if PY_MAJOR_VERSION < 3
return "__idiv__";
#else
return "__itruediv__";
#endif
} else if (name == "*=") {
return "__imul__";
} else if (name == "%=") {
return "__imod__";
} else if (name == "<<=") {
return "__ilshift__";
} else if (name == ">>=") {
return "__irshift__";
} else if (name == "&=") {
return "__iand__";
} else if (name == "|=") {
return "__ior__";
} else if (name == "^=") {
return "__ixor__";
} else if (name == "[]") {
return "__getitem__";
} else if (name == "[]=") {
return "__setitem__";
} else {
const char *c = name.c_str ();
if (! isalnum (*c) && *c != '_') {
return std::string ();
}
// question-mark symbol and trailing = are removed.
size_t n = 0;
for ( ; *c; ++c) {
if (*c == '=' || *c == '?') {
if (! c[1]) {
if (*c == '=') {
// Normally, this method is replaced by an attribute.
// If that fails, we prepend a "set_" to make the name unique.
return "set_" + std::string (name, 0, n);
} else {
return std::string (name, 0, n);
}
} else {
return std::string ();
}
} else if (! isalnum (*c) && *c != '_') {
return std::string ();
} else {
++n;
}
}
return name;
}
}
/**
* @brief Returns true, if the method with the given name shall fallback to NotImplemented
*/
static bool is_method_with_fallback (const std::string &name)
{
if (name == "+") {
return true;
} else if (name == "-") {
return true;
} else if (name == "/") {
#if PY_MAJOR_VERSION < 3
return false;
#else
return true;
#endif
} else if (name == "*") {
return true;
} else if (name == "%") {
return true;
} else if (name == "<<") {
return true;
} else if (name == ">>") {
return true;
} else if (name == "&") {
return true;
} else if (name == "|") {
return true;
} else if (name == "^") {
return true;
} else {
return false;
}
}
void
MethodTable::add_method (const std::string &name, const gsi::MethodBase *mb)
{
if (is_reserved_word (name)) {
// drop non-standard names
if (tl::verbosity () >= 20) {
tl::warn << tl::to_string (tr ("Class ")) << mp_cls_decl->name () << ": " << tl::to_string (tr ("no Python mapping for method (reserved word) ")) << name;
}
std::string new_name = name + "_";
add_method_basic (new_name, mb);
mp_module->add_python_doc (mb, tl::sprintf (tl::to_string (tr ("This attribute is available as '%s' in Python")), new_name));
} else if (name == "new" && mb->ret_type ().type () == gsi::T_object && mb->ret_type ().pass_obj ()) {
add_method_basic (name, mb);
add_method_basic ("__init__", mb, true /*enabled*/, true /*constructor*/);
mp_module->add_python_doc (mb, tl::to_string (tr ("This method is the default initializer of the object")));
} else if (name == "to_s" && mb->compatible_with_num_args (0)) {
add_method_basic (name, mb);
// The str method is also routed via the tp_str implementation
add_method_basic ("__str__", mb);
#if defined(GSI_ALIAS_INSPECT)
// also alias to "__repr__" unless there is an explicit "inspect" method
bool alias_inspect = true;
for (gsi::ClassBase::method_iterator m = mp_cls_decl->begin_methods (); m != mp_cls_decl->end_methods () && alias_inspect; ++m) {
if (! (*m)->is_callback () && ! (*m)->is_signal ()) {
for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms () && alias_inspect; ++syn) {
if (! syn->is_getter && ! syn->is_setter && syn->name == "inspect") {
alias_inspect = false;
}
}
}
}
#else
bool alias_inspect = false;
#endif
if (alias_inspect) {
add_method_basic ("__repr__", mb);
mp_module->add_python_doc (mb, tl::to_string (tr ("This method is also available as 'str(object)' and 'repr(object)'")));
} else {
mp_module->add_python_doc (mb, tl::to_string (tr ("This method is also available as 'str(object)'")));
}
} else if (name == "hash" && mb->compatible_with_num_args (0)) {
// The hash method is also routed via the tp_hash implementation
add_method_basic ("__hash__", mb);
add_method_basic (name, mb);
mp_module->add_python_doc (mb, tl::to_string (tr ("This method is also available as 'hash(object)'")));
} else if (name == "inspect" && mb->compatible_with_num_args (0)) {
// The str method is also routed via the tp_str implementation
add_method_basic ("__repr__", mb);
add_method_basic (name, mb);
mp_module->add_python_doc (mb, tl::to_string (tr ("This method is also available as 'repr(object)'")));
} else if (name == "size" && mb->compatible_with_num_args (0)) {
// The size method is also routed via the sequence methods protocol if there
// is a [] function
add_method_basic ("__len__", mb);
add_method_basic (name, mb);
mp_module->add_python_doc (mb, tl::to_string (tr ("This method is also available as 'len(object)'")));
} else if (name == "each" && mb->compatible_with_num_args (0) && mb->ret_type ().is_iter ()) {
// each makes the object iterable
add_method_basic ("__iter__", mb);
add_method_basic (name, mb);
mp_module->add_python_doc (mb, tl::to_string (tr ("This method enables iteration of the object")));
} else if (name == "dup" && mb->compatible_with_num_args (0)) {
// If the object supports the dup method, then it is a good
// idea to define the __copy__ and __deepcopy__ method.
add_method_basic ("__copy__", mb);
add_method_basic ("__deepcopy__", mb);
add_method_basic (name, mb);
mp_module->add_python_doc (mb, tl::to_string (tr ("This method also implements '__copy__' and '__deepcopy__'")));
} else {
std::string py_name = extract_python_name (name);
if (py_name.empty ()) {
// drop non-standard names
if (tl::verbosity () >= 20) {
tl::warn << tl::to_string (tr ("Class ")) << mp_cls_decl->name () << ": " << tl::to_string (tr ("no Python mapping for method ")) << name;
}
add_method_basic (name, mb, false);
mp_module->add_python_doc (mb, tl::to_string (tr ("This method is not available for Python")));
} else {
bool fb = is_method_with_fallback (name);
add_method_basic (py_name, mb, true, false, fb);
if (name == "*") {
// Supply a commutative multiplication version unless the operator is "*!"
add_method_basic ("__rmul__", mb);
mp_module->add_python_doc (mb, tl::to_string (tr ("This method also implements '__rmul__'")));
}
}
}
}
void
MethodTable::add_setter (const std::string &name, const gsi::MethodBase *setter)
{
if (is_reserved_word (name)) {
std::string new_name = name + "_";
add_setter_basic (new_name, setter);
mp_module->add_python_doc (setter, tl::sprintf (tl::to_string (tr ("This member is available as '%s' in Python")), new_name));
} else {
add_setter_basic (name, setter);
}
}
void
MethodTable::add_setter_basic (const std::string &name, const gsi::MethodBase *setter)
{
bool st = setter->is_static ();
std::map<std::pair<bool, std::string>, size_t>::iterator n = m_property_name_map.find (std::make_pair (st, name));
if (n == m_property_name_map.end ()) {
m_property_name_map.insert (std::make_pair (std::make_pair(st, name), m_property_table.size ()));
m_property_table.push_back (std::make_pair (MethodTableEntry (name, st, false), MethodTableEntry (name, st, false)));
m_property_table.back ().first.add (setter);
} else {
m_property_table [n->second].first.add (setter);
}
}
void
MethodTable::add_getter (const std::string &name, const gsi::MethodBase *getter)
{
if (is_reserved_word (name)) {
std::string new_name = name + "_";
add_getter_basic (new_name, getter);
mp_module->add_python_doc (getter, tl::sprintf (tl::to_string (tr ("This member is available as '%s' in Python")), new_name));
} else {
add_getter_basic (name, getter);
}
}
void
MethodTable::add_getter_basic (const std::string &name, const gsi::MethodBase *getter)
{
bool st = getter->is_static ();
std::map<std::pair<bool, std::string>, size_t>::iterator n = m_property_name_map.find (std::make_pair (st, name));
if (n == m_property_name_map.end ()) {
m_property_name_map.insert (std::make_pair (std::make_pair(st, name), m_property_table.size ()));
m_property_table.push_back (std::make_pair (MethodTableEntry (name, st, false), MethodTableEntry (name, st, false)));
m_property_table.back ().second.add (getter);
} else {
m_property_table [n->second].second.add (getter);
}
}
bool
MethodTable::is_enabled (size_t mid) const
{
return m_table [mid - m_method_offset].is_enabled ();
}
void
MethodTable::set_enabled (size_t mid, bool en)
{
m_table [mid - m_method_offset].set_enabled (en);
}
bool
MethodTable::fallback_not_implemented (size_t mid) const
{
return m_table [mid - m_method_offset].fallback_not_implemented ();
}
void
MethodTable::set_fallback_not_implemented (size_t mid, bool f)
{
m_table [mid - m_method_offset].set_fallback_not_implemented (f);
}
bool
MethodTable::is_init(size_t mid) const
{
return m_table [mid - m_method_offset].is_init ();
}
void
MethodTable::set_init (size_t mid, bool f)
{
m_table [mid - m_method_offset].set_init (f);
}
bool
MethodTable::is_static (size_t mid) const
{
return m_table [mid - m_method_offset].is_static ();
}
bool
MethodTable::is_protected (size_t mid) const
{
return m_table [mid - m_method_offset].is_protected ();
}
void
MethodTable::alias (size_t mid, const std::string &new_name)
{
bool st = is_static (mid);
auto nm = m_name_map.find (std::make_pair (st, new_name));
tl_assert (nm == m_name_map.end ());
m_table.push_back (m_table [mid - m_method_offset]);
m_table.back ().set_name (new_name);
m_name_map.insert (std::make_pair (std::make_pair (st, new_name), m_table.size () - 1 - m_method_offset));
}
void
MethodTable::rename (size_t mid, const std::string &new_name)
{
std::string old_name = name (mid);
bool st = is_static (mid);
m_table [mid - m_method_offset].set_name (new_name);
auto nm = m_name_map.find (std::make_pair (st, old_name));
if (nm != m_name_map.end ()) {
m_name_map.erase (nm);
m_name_map.insert (std::make_pair (std::make_pair (st, new_name), mid));
}
}
const std::string &
MethodTable::name (size_t mid) const
{
return m_table [mid - m_method_offset].name ();
}
const std::string &
MethodTable::property_name (size_t mid) const
{
return m_property_table [mid - m_property_offset].first.name ();
}
MethodTableEntry::method_iterator
MethodTable::begin_setters (size_t mid) const
{
return m_property_table[mid - m_property_offset].first.begin ();
}
MethodTableEntry::method_iterator
MethodTable::end_setters (size_t mid) const
{
return m_property_table[mid - m_property_offset].first.end ();
}
MethodTableEntry::method_iterator
MethodTable::begin_getters (size_t mid) const
{
return m_property_table[mid - m_property_offset].second.begin ();
}
MethodTableEntry::method_iterator
MethodTable::end_getters (size_t mid) const
{
return m_property_table[mid - m_property_offset].second.end ();
}
MethodTableEntry::method_iterator
MethodTable::begin (size_t mid) const
{
return m_table[mid - m_method_offset].begin ();
}
MethodTableEntry::method_iterator
MethodTable::end (size_t mid) const
{
return m_table[mid - m_method_offset].end ();
}
void
MethodTable::finish ()
{
for (std::vector<MethodTableEntry>::iterator m = m_table.begin (); m != m_table.end (); ++m) {
m->finish ();
if (m->is_enabled ()) {
// disable methods which are also present as properties
if (m_property_name_map.find (std::make_pair (m->is_static (), m->name ())) != m_property_name_map.end ()) {
m->set_enabled (false);
}
}
}
for (std::vector<std::pair<MethodTableEntry, MethodTableEntry> >::iterator m = m_property_table.begin (); m != m_property_table.end (); ++m) {
m->first.finish ();
m->second.finish ();
}
}
void
MethodTable::add_method_basic (const std::string &name, const gsi::MethodBase *mb, bool enabled, bool init, bool fallback_not_implemented)
{
bool st = mb->is_static () && ! init;
std::map<std::pair<bool, std::string>, size_t>::iterator n = m_name_map.find (std::make_pair (st, name));
if (n == m_name_map.end ()) {
m_name_map.insert (std::make_pair (std::make_pair (st, name), m_table.size ()));
m_table.push_back (MethodTableEntry (name, st, mb->is_protected ()));
if (! enabled) {
m_table.back ().set_enabled (false);
}
if (init) {
m_table.back ().set_init (true);
}
if (fallback_not_implemented) {
m_table.back ().set_fallback_not_implemented (true);
}
m_table.back ().add (mb);
} else {
if (m_table [n->second].is_protected () != mb->is_protected ()) {
tl::warn << "Class " << mp_cls_decl->name () << ": method '" << name << " is both a protected and non-protected";
}
m_table [n->second].add (mb);
if (! enabled) {
m_table [n->second].set_enabled (false);
}
if (init) {
tl_assert (m_table [n->second].is_init ());
}
if (fallback_not_implemented) {
m_table.back ().set_fallback_not_implemented (true);
}
}
}
MethodTable *
MethodTable::method_table_by_class (const gsi::ClassBase *cls_decl)
{
PythonClassClientData *cd = dynamic_cast<PythonClassClientData *>(cls_decl->data (gsi::ClientIndex::Python));
return cd ? &cd->method_table : 0;
}
// -------------------------------------------------------------------
// PythonClassClientData implementation
PythonClassClientData::PythonClassClientData (const gsi::ClassBase *_cls, PyTypeObject *_py_type, PyTypeObject *_py_type_static, PythonModule *module)
: py_type_object ((PyObject *) _py_type), py_type_object_static ((PyObject *) _py_type_static), method_table (_cls, module)
{
// .. nothing yet ..
}
PyTypeObject *
PythonClassClientData::py_type (const gsi::ClassBase &cls_decl, bool as_static)
{
PythonClassClientData *cd = dynamic_cast<PythonClassClientData *>(cls_decl.data (gsi::ClientIndex::Python));
return (PyTypeObject *) (cd ? (as_static ? cd->py_type_object_static.get () : cd->py_type_object.get ()) : 0);
}
void
PythonClassClientData::initialize (const gsi::ClassBase &cls_decl, PyTypeObject *py_type, bool as_static)
{
PythonClassClientData *cd = dynamic_cast<PythonClassClientData *>(cls_decl.data (gsi::ClientIndex::Python));
if (cd) {
if (as_static) {
cd->py_type_object_static = (PyObject *) py_type;
} else {
cd->py_type_object = (PyObject *) py_type;
}
} else {
cls_decl.set_data (gsi::ClientIndex::Python, new PythonClassClientData (&cls_decl, as_static ? NULL : py_type, as_static ? py_type : NULL));
}
}
}

316
src/pya/pya/pyaInternal.h Normal file
View File

@ -0,0 +1,316 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2022 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _HDR_pyaInternal
#define _HDR_pyaInternal
#include <Python.h>
#include "pyaCommon.h"
#include "pyaRefs.h"
#include "gsiClassBase.h"
#include <map>
#include <list>
#include <vector>
#include <string>
namespace gsi
{
class MethodBase;
}
namespace pya
{
class PythonModule;
// -------------------------------------------------------------------
// The lookup table for the method overload resolution
/**
* @brief A single entry in the method table
* This class provides an entry for one name. It provides flags
* (ctor, static, protected) for the method and a list of implementations
* (gsi::MethodBase objects)
*/
class MethodTableEntry
{
public:
typedef std::vector<const gsi::MethodBase *>::const_iterator method_iterator;
MethodTableEntry (const std::string &name, bool st, bool prot);
const std::string &name () const;
void set_name (const std::string &n);
void set_enabled (bool en);
bool is_enabled () const;
void set_fallback_not_implemented (bool en);
bool fallback_not_implemented () const;
void set_init(bool f);
bool is_init () const;
bool is_static () const;
bool is_protected () const;
void add (const gsi::MethodBase *m);
void finish ();
method_iterator begin () const;
method_iterator end () const;
const std::vector<const gsi::MethodBase *> &methods () const
{
return m_methods;
}
private:
std::string m_name;
bool m_is_static : 1;
bool m_is_protected : 1;
bool m_is_enabled : 1;
bool m_is_init : 1;
bool m_fallback_not_implemented : 1;
std::vector<const gsi::MethodBase *> m_methods;
};
/**
* @brief The method table for a class
* The method table will provide the methods associated with a native method, i.e.
* a certain name. It only provides the methods, not a overload resolution strategy.
*/
class MethodTable
{
public:
/**
* @brief Constructor
* This constructor will create a method table for the given class and register
* this table under this class.
*/
MethodTable (const gsi::ClassBase *cls_decl, PythonModule *module);
/**
* @brief Returns the lowest method ID within the space of this table
* Method IDs below this one are reserved for base class methods
*/
size_t bottom_mid () const;
/**
* @brief Returns the topmost + 1 method ID.
*/
size_t top_mid () const;
/**
* @brief Returns the lowest property method ID within the space of this table
* Method IDs below this one are reserved for base class methods
*/
size_t bottom_property_mid () const;
/**
* @brief Returns the topmost + 1 property method ID.
*/
size_t top_property_mid () const;
/**
* @brief Find a method with the given name and static flag
* Returns true or false in the first part (true, if found) and
* the MID in the second part.
*/
std::pair<bool, size_t> find_method (bool st, const std::string &name) const;
/**
* @brief Find a property with the given name and static flag
* Returns true or false in the first part (true, if found) and
* the MID in the second part.
*/
std::pair<bool, size_t> find_property (bool st, const std::string &name) const;
/**
* @brief Adds a method to the table
*/
void add_method (const std::string &name, const gsi::MethodBase *mb);
/**
* @brief Adds a setter with the given name
*/
void add_setter (const std::string &name, const gsi::MethodBase *setter);
/**
* @brief Adds a getter with the given name
*/
void add_getter (const std::string &name, const gsi::MethodBase *getter);
/**
* @brief Returns true if the method is enabled
*/
bool is_enabled (size_t mid) const;
/**
* @brief Enables or disables a method
*/
void set_enabled (size_t mid, bool en);
/**
* @brief Returns true if the method has a NotImplemented fallback
*/
bool fallback_not_implemented (size_t mid) const;
/**
* @brief Sets a value indicating that the method has a fallback to NotImplemented for non-matching arguments
*/
void set_fallback_not_implemented (size_t mid, bool f);
/**
* @brief Returns true if the method is an initializer
*/
bool is_init (size_t mid) const;
/**
* @brief Sets initializer
*/
void set_init (size_t mid, bool f);
/**
* @brief Returns true if the method with the given ID is static
*/
bool is_static (size_t mid) const;
/**
* @brief Returns true if the method with the given ID is protected
*/
bool is_protected (size_t mid) const;
/**
* @brief Creates an alias for the given method
*/
void alias (size_t mid, const std::string &new_name);
/**
* @brief Renames a method
*/
void rename (size_t mid, const std::string &new_name);
/**
* @brief Returns the name of the method with the given ID
*/
const std::string &name (size_t mid) const;
/**
* @brief Returns the name of the property with the given ID
*/
const std::string &property_name (size_t mid) const;
/**
* @brief Begins iteration of the overload variants for setter of property ID mid
*/
MethodTableEntry::method_iterator begin_setters (size_t mid) const;
/**
* @brief Ends iteration of the overload variants for setter of property ID mid
*/
MethodTableEntry::method_iterator end_setters (size_t mid) const;
/**
* @brief Begins iteration of the overload variants for getter of property ID mid
*/
MethodTableEntry::method_iterator begin_getters (size_t mid) const;
/**
* @brief Ends iteration of the overload variants for getter of property ID mid
*/
MethodTableEntry::method_iterator end_getters (size_t mid) const;
/**
* @brief Begins iteration of the overload variants for method ID mid
*/
MethodTableEntry::method_iterator begin (size_t mid) const;
/**
* @brief Ends iteration of the overload variants for method ID mid
*/
MethodTableEntry::method_iterator end (size_t mid) const;
/**
* @brief Finishes construction of the table
* This method must be called after the add_method calls have been used
* to fill the table. It will remove duplicate entries and clean up memory.
*/
void finish ();
/**
* @brief Obtain a method table for a given class
*/
static MethodTable *method_table_by_class (const gsi::ClassBase *cls_decl);
/**
* @brief Gets the method table
*/
const std::vector<MethodTableEntry> &method_table () const
{
return m_table;
}
/**
* @brief Gets the property table
*/
const std::vector<std::pair<MethodTableEntry, MethodTableEntry> > &property_table () const
{
return m_property_table;
}
private:
size_t m_method_offset;
size_t m_property_offset;
const gsi::ClassBase *mp_cls_decl;
std::map<std::pair<bool, std::string>, size_t> m_name_map;
std::map<std::pair<bool, std::string>, size_t> m_property_name_map;
std::vector<MethodTableEntry> m_table;
std::vector<std::pair<MethodTableEntry, MethodTableEntry> > m_property_table;
PythonModule *mp_module;
void add_method_basic (const std::string &name, const gsi::MethodBase *mb, bool enabled = true, bool init = false, bool fallback_not_implemented = false);
void add_setter_basic (const std::string &name, const gsi::MethodBase *setter);
void add_getter_basic (const std::string &name, const gsi::MethodBase *getter);
bool is_property_setter (bool st, const std::string &name);
bool is_property_getter (bool st, const std::string &name);
};
struct PythonClassClientData
: public gsi::PerClassClientSpecificData
{
PythonClassClientData (const gsi::ClassBase *_cls, PyTypeObject *_py_type, PyTypeObject *_py_type_static, PythonModule *module);
PythonPtr py_type_object;
PythonPtr py_type_object_static;
MethodTable method_table;
static PyTypeObject *py_type (const gsi::ClassBase &cls_decl, bool as_static);
static void initialize (const gsi::ClassBase &cls_decl, PyTypeObject *py_type, bool as_static, PythonModule *module);
};
}
#endif

View File

@ -469,7 +469,7 @@ struct writer<gsi::ObjectType>
const gsi::ClassBase *cls_decl = PythonModule::cls_for_type (Py_TYPE (arg));
if (! cls_decl) {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s, got %s)")), atype.cls ()->name (), Py_TYPE (arg)->tp_name));
throw tl::TypeError (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s, got %s)")), atype.cls ()->name (), Py_TYPE (arg)->tp_name));
}
if (cls_decl->is_derived_from (atype.cls ())) {
@ -494,14 +494,14 @@ struct writer<gsi::ObjectType>
aa->write<void *> (new_obj);
} else {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s, got %s)")), atype.cls ()->name (), cls_decl->name ()));
throw tl::TypeError (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s, got %s)")), atype.cls ()->name (), cls_decl->name ()));
}
} else {
const gsi::ClassBase *cls_decl = PythonModule::cls_for_type (Py_TYPE (arg));
if (! cls_decl) {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s, got %s)")), atype.cls ()->name (), Py_TYPE (arg)->tp_name));
throw tl::TypeError (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s, got %s)")), atype.cls ()->name (), Py_TYPE (arg)->tp_name));
}
if (cls_decl->is_derived_from (atype.cls ())) {
@ -521,7 +521,7 @@ struct writer<gsi::ObjectType>
aa->write<void *> (atype.cls ()->create_obj_from (cls_decl, p->obj ()));
} else {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s, got %s)")), atype.cls ()->name (), cls_decl->name ()));
throw tl::TypeError (tl::sprintf (tl::to_string (tr ("Unexpected object type (expected argument of class %s, got %s)")), atype.cls ()->name (), cls_decl->name ()));
}
}

View File

@ -43,7 +43,7 @@ class PYAObjectBase;
* @param arg The argument to serialize (a Python object)
* @param heap A heap for temporary objects
*
* The heap collects objects created while filling the buffer.
* The heap collects objects created while filling the buffer.
* The stack must persist as long as the serial buffer is used.
*/
void
@ -84,4 +84,3 @@ void correct_constness (PyObject *obj, bool const_required);
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -45,7 +45,6 @@ namespace gsi
namespace pya
{
class PYAObjectBase;
class Callee;
class StatusChangedListener;

View File

@ -29,6 +29,8 @@
namespace pya
{
/**
* Some helper macros that translate C++ exceptions into Python errors
*/
@ -43,6 +45,10 @@ namespace pya
} catch (std::exception &ex) { \
std::string msg = std::string(ex.what ()) + tl::to_string (tr (" in ")) + (where); \
PyErr_SetString (PyExc_RuntimeError, msg.c_str ()); \
} catch (tl::TypeError &ex) { \
std::string msg; \
msg = ex.msg () + tl::to_string (tr (" in ")) + (where); \
PyErr_SetString (PyExc_TypeError, msg.c_str ()); \
} catch (tl::Exception &ex) { \
std::string msg; \
msg = ex.msg () + tl::to_string (tr (" in ")) + (where); \
@ -73,4 +79,3 @@ void check_error ();
}
#endif

View File

@ -1,6 +1,7 @@
TARGET = dbcore
REALMODULE = db
PYI = dbcore.pyi
include($$PWD/../pymod.pri)

View File

@ -1,6 +1,20 @@
import functools
from typing import Type
import klayout.dbcore
from klayout.dbcore import *
from klayout.db.pcell_declaration_helper import PCellDeclarationHelper
__all__ = klayout.dbcore.__all__ + ['PCellDeclarationHelper']
__all__ = klayout.dbcore.__all__ + ["PCellDeclarationHelper"] # type: ignore
# If class has from_s, to_s, and assign, use them to
# enable serialization.
for name, cls in klayout.dbcore.__dict__.items():
if not isinstance(cls, type):
continue
if hasattr(cls, 'from_s') and hasattr(cls, 'to_s') and hasattr(cls, 'assign'):
cls.__getstate__ = cls.to_s # type: ignore
def _setstate(self, str):
cls = self.__class__
self.assign(cls.from_s(str))
cls.__setstate__ = _setstate # type: ignore

View File

@ -63,7 +63,17 @@ class _PCellDeclarationHelper(PCellDeclaration):
self.layer = None
self.cell = None
def param(self, name, value_type, description, hidden=False, readonly=False, unit=None, default=None, choices=None):
def param(
self,
name,
value_type,
description,
hidden=False,
readonly=False,
unit=None,
default=None,
choices=None,
):
"""
Defines a parameter
name -> the short name of the parameter
@ -84,11 +94,16 @@ class _PCellDeclarationHelper(PCellDeclaration):
# create accessor methods for the parameters
param_index = len(self._param_decls)
setattr(type(self), name, _PCellDeclarationHelperParameterDescriptor(param_index))
setattr(
type(self), name, _PCellDeclarationHelperParameterDescriptor(param_index)
)
if value_type == type(self).TypeLayer:
setattr(type(self), name + "_layer",
_PCellDeclarationHelperLayerDescriptor(len(self._layer_param_index)))
setattr(
type(self),
name + "_layer",
_PCellDeclarationHelperLayerDescriptor(len(self._layer_param_index)),
)
self._layer_param_index.append(param_index)
# store the parameter declarations
@ -104,10 +119,16 @@ class _PCellDeclarationHelper(PCellDeclaration):
pdecl.unit = unit
if not (choices is None):
if not isinstance(choices, list) and not isinstance(choices, tuple):
raise Exception("choices value must be an list/tuple of two-element arrays (description, value)")
raise Exception(
"choices value must be an list/tuple of two-element arrays (description, value)"
)
for c in choices:
if (not isinstance(choices, list) and not isinstance(choices, tuple)) or len(c) != 2:
raise Exception("choices value must be an list/tuple of two-element arrays (description, value)")
if (
not isinstance(choices, list) and not isinstance(choices, tuple)
) or len(c) != 2:
raise Exception(
"choices value must be an list/tuple of two-element arrays (description, value)"
)
pdecl.add_choice(c[0], c[1])
# return the declaration object for further operations

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
from typing import Any, ClassVar, Dict, Sequence, List, Iterator, Optional
from typing import overload
import klayout.tl as tl
import klayout.db as db

View File

@ -1,5 +1,6 @@
from typing import Any, ClassVar, Dict, Iterable, Optional
from typing import Any, ClassVar, Dict, Sequence, List, Iterator, Optional
from typing import overload
import klayout.tl as tl
import klayout.db as db
class RdbReference:
r"""
@ -8,17 +9,32 @@ class RdbReference:
"""
parent_cell_id: int
r"""
Getter:
@brief Gets parent cell ID for this reference
@return The parent cell ID
Setter:
@brief Sets the parent cell ID for this reference
"""
trans: db.DCplxTrans
r"""
Getter:
@brief Gets the transformation for this reference
The transformation describes the transformation of the child cell into the parent cell. In that sense that is the usual transformation of a cell reference.
@return The transformation
Setter:
@brief Sets the transformation for this reference
"""
@classmethod
def new(cls, trans: db.DCplxTrans, parent_cell_id: int) -> RdbReference:
r"""
@brief Creates a reference with a given transformation and parent cell ID
"""
def __copy__(self) -> RdbReference:
r"""
@brief Creates a copy of self
"""
def __init__(self, trans: db.DCplxTrans, parent_cell_id: int) -> None:
r"""
@brief Creates a reference with a given transformation and parent cell ID
@ -64,22 +80,50 @@ class RdbReference:
r"""
@brief Assigns another object to self
"""
def create(self) -> None:
r"""
@brief Ensures the C++ object is created
Use this method to ensure the C++ object is created, for example to ensure that resources are allocated. Usually C++ objects are created on demand and not necessarily when the script object is created.
"""
def database(self) -> ReportDatabase:
r"""
@brief Gets the database object that category is associated with
This method has been introduced in version 0.23.
"""
def destroy(self) -> None:
r"""
@brief Explicitly destroys the object
Explicitly destroys the object on C++ side if it was owned by the script interpreter. Subsequent access to this object will throw an exception.
If the object is not owned by the script, this method will do nothing.
"""
def destroyed(self) -> bool:
r"""
@brief Returns a value indicating whether the object was already destroyed
This method returns true, if the object was destroyed, either explicitly or by the C++ side.
The latter may happen, if the object is owned by a C++ object which got destroyed itself.
"""
def dup(self) -> RdbReference:
r"""
@brief Creates a copy of self
"""
def is_const_object(self) -> bool:
r"""
@brief Returns a value indicating whether the reference is a const reference
This method returns true, if self is a const reference.
In that case, only const methods may be called on self.
"""
class RdbCell:
r"""
@brief A cell inside the report database
This class represents a cell in the report database. There is not necessarily a 1:1 correspondence of RDB cells and layout database cells. Cells have an ID, a name, optionally a variant name and a set of references which describe at least one example instantiation in some parent cell. The references do not necessarily map to references or cover all references in the layout database.
"""
@classmethod
def new(cls) -> RdbCell:
r"""
@brief Creates a new object of this class
"""
def __init__(self) -> None:
r"""
@brief Creates a new object of this class
@ -130,22 +174,45 @@ class RdbCell:
r"""
@brief Removes all references from this cell
"""
def create(self) -> None:
r"""
@brief Ensures the C++ object is created
Use this method to ensure the C++ object is created, for example to ensure that resources are allocated. Usually C++ objects are created on demand and not necessarily when the script object is created.
"""
def database(self) -> ReportDatabase:
r"""
@brief Gets the database object that category is associated with
This method has been introduced in version 0.23.
"""
def each_item(self) -> Iterable[RdbItem]:
def destroy(self) -> None:
r"""
@brief Explicitly destroys the object
Explicitly destroys the object on C++ side if it was owned by the script interpreter. Subsequent access to this object will throw an exception.
If the object is not owned by the script, this method will do nothing.
"""
def destroyed(self) -> bool:
r"""
@brief Returns a value indicating whether the object was already destroyed
This method returns true, if the object was destroyed, either explicitly or by the C++ side.
The latter may happen, if the object is owned by a C++ object which got destroyed itself.
"""
def each_item(self) -> Iterator[RdbItem]:
r"""
@brief Iterates over all items inside the database which are associated with this cell
This method has been introduced in version 0.23.
"""
def each_reference(self) -> Iterable[RdbReference]:
def each_reference(self) -> Iterator[RdbReference]:
r"""
@brief Iterates over all references
"""
def is_const_object(self) -> bool:
r"""
@brief Returns a value indicating whether the reference is a const reference
This method returns true, if self is a const reference.
In that case, only const methods may be called on self.
"""
def name(self) -> str:
r"""
@brief Gets the cell name
@ -184,11 +251,19 @@ class RdbCategory:
"""
description: str
r"""
Getter:
@brief Gets the category description
@return The description string
Setter:
@brief Sets the category description
@param description The description string
"""
@classmethod
def new(cls) -> RdbCategory:
r"""
@brief Creates a new object of this class
"""
def __init__(self) -> None:
r"""
@brief Creates a new object of this class
@ -230,22 +305,45 @@ class RdbCategory:
Usually it's not required to call this method. It has been introduced in version 0.24.
"""
def create(self) -> None:
r"""
@brief Ensures the C++ object is created
Use this method to ensure the C++ object is created, for example to ensure that resources are allocated. Usually C++ objects are created on demand and not necessarily when the script object is created.
"""
def database(self) -> ReportDatabase:
r"""
@brief Gets the database object that category is associated with
This method has been introduced in version 0.23.
"""
def each_item(self) -> Iterable[RdbItem]:
def destroy(self) -> None:
r"""
@brief Explicitly destroys the object
Explicitly destroys the object on C++ side if it was owned by the script interpreter. Subsequent access to this object will throw an exception.
If the object is not owned by the script, this method will do nothing.
"""
def destroyed(self) -> bool:
r"""
@brief Returns a value indicating whether the object was already destroyed
This method returns true, if the object was destroyed, either explicitly or by the C++ side.
The latter may happen, if the object is owned by a C++ object which got destroyed itself.
"""
def each_item(self) -> Iterator[RdbItem]:
r"""
@brief Iterates over all items inside the database which are associated with this category
This method has been introduced in version 0.23.
"""
def each_sub_category(self) -> Iterable[RdbCategory]:
def each_sub_category(self) -> Iterator[RdbCategory]:
r"""
@brief Iterates over all sub-categories
"""
def is_const_object(self) -> bool:
r"""
@brief Returns a value indicating whether the reference is a const reference
This method returns true, if self is a const reference.
In that case, only const methods may be called on self.
"""
def name(self) -> str:
r"""
@brief Gets the category name
@ -356,11 +454,14 @@ class RdbItemValue:
"""
tag_id: int
r"""
Getter:
@brief Gets the tag ID if the value is a tagged value or 0 if not
@return The tag ID
See \tag_id= for details about tagged values.
Tagged values have been added in version 0.24.
Setter:
@brief Sets the tag ID to make the value a tagged value or 0 to reset it
@param id The tag ID
To get a tag ID, use \RdbDatabase#user_tag_id (preferred) or \RdbDatabase#tag_id (for internal use).
@ -368,6 +469,70 @@ class RdbItemValue:
This variant has been introduced in version 0.24
"""
@classmethod
def from_s(cls, s: str) -> RdbItemValue:
r"""
@brief Creates a value object from a string
The string format is the same than obtained by the to_s method.
"""
@overload
@classmethod
def new(cls, b: db.DBox) -> RdbItemValue:
r"""
@brief Creates a value representing a DBox object
"""
@overload
@classmethod
def new(cls, e: db.DEdge) -> RdbItemValue:
r"""
@brief Creates a value representing a DEdge object
"""
@overload
@classmethod
def new(cls, ee: db.DEdgePair) -> RdbItemValue:
r"""
@brief Creates a value representing a DEdgePair object
"""
@overload
@classmethod
def new(cls, f: float) -> RdbItemValue:
r"""
@brief Creates a value representing a numeric value
This variant has been introduced in version 0.24
"""
@overload
@classmethod
def new(cls, p: db.DPath) -> RdbItemValue:
r"""
@brief Creates a value representing a DPath object
This method has been introduced in version 0.22.
"""
@overload
@classmethod
def new(cls, p: db.DPolygon) -> RdbItemValue:
r"""
@brief Creates a value representing a DPolygon object
"""
@overload
@classmethod
def new(cls, s: str) -> RdbItemValue:
r"""
@brief Creates a value representing a string
"""
@overload
@classmethod
def new(cls, t: db.DText) -> RdbItemValue:
r"""
@brief Creates a value representing a DText object
This method has been introduced in version 0.22.
"""
def __copy__(self) -> RdbItemValue:
r"""
@brief Creates a copy of self
"""
@overload
def __init__(self, b: db.DBox) -> None:
r"""
@ -466,6 +631,23 @@ class RdbItemValue:
@brief Gets the box if the value represents one.
@return The \DBox object or nil
"""
def create(self) -> None:
r"""
@brief Ensures the C++ object is created
Use this method to ensure the C++ object is created, for example to ensure that resources are allocated. Usually C++ objects are created on demand and not necessarily when the script object is created.
"""
def destroy(self) -> None:
r"""
@brief Explicitly destroys the object
Explicitly destroys the object on C++ side if it was owned by the script interpreter. Subsequent access to this object will throw an exception.
If the object is not owned by the script, this method will do nothing.
"""
def destroyed(self) -> bool:
r"""
@brief Returns a value indicating whether the object was already destroyed
This method returns true, if the object was destroyed, either explicitly or by the C++ side.
The latter may happen, if the object is owned by a C++ object which got destroyed itself.
"""
def dup(self) -> RdbItemValue:
r"""
@brief Creates a copy of self
@ -486,15 +668,16 @@ class RdbItemValue:
@return The numeric value or 0
This method has been introduced in version 0.24.
"""
def from_s(self, s: str) -> RdbItemValue:
r"""
@brief Creates a value object from a string
The string format is the same than obtained by the to_s method.
"""
def is_box(self) -> bool:
r"""
@brief Returns true if the value object represents a box
"""
def is_const_object(self) -> bool:
r"""
@brief Returns a value indicating whether the reference is a const reference
This method returns true, if self is a const reference.
In that case, only const methods may be called on self.
"""
def is_edge(self) -> bool:
r"""
@brief Returns true if the value object represents an edge
@ -562,13 +745,43 @@ class RdbItem:
@brief An item inside the report database
An item is the basic information entity in the RDB. It is associated with a cell and a category. It can be assigned values which encapsulate other objects such as strings and geometrical objects. In addition, items can be assigned an image (i.e. a screenshot image) and tags which are basically boolean flags that can be defined freely.
"""
@property
def image(self) -> None:
r"""
WARNING: This variable can only be set, not retrieved.
@brief Sets the attached image from a PixelBuffer object
This method has been added in version 0.28.
"""
image_str: str
r"""
Getter:
@brief Gets the image associated with this item as a string
@return A base64-encoded image file (in PNG format)
Setter:
@brief Sets the image from a string
@param image A base64-encoded image file (preferably in PNG format)
"""
tags_str: str
r"""
Getter:
@brief Returns a string listing all tags of this item
@return A comma-separated list of tags
Setter:
@brief Sets the tags from a string
@param tags A comma-separated list of tags
"""
@classmethod
def new(cls) -> RdbItem:
r"""
@brief Creates a new object of this class
"""
def __copy__(self) -> RdbItem:
r"""
@brief Creates a copy of self
"""
def __init__(self) -> None:
r"""
@brief Creates a new object of this class
@ -674,6 +887,10 @@ class RdbItem:
This method has been introduced in version 0.25.3.
"""
def assign(self, other: RdbItem) -> None:
r"""
@brief Assigns another object to self
"""
def category_id(self) -> int:
r"""
@brief Gets the category ID
@ -690,21 +907,61 @@ class RdbItem:
r"""
@brief Removes all values from this item
"""
def create(self) -> None:
r"""
@brief Ensures the C++ object is created
Use this method to ensure the C++ object is created, for example to ensure that resources are allocated. Usually C++ objects are created on demand and not necessarily when the script object is created.
"""
def database(self) -> ReportDatabase:
r"""
@brief Gets the database object that item is associated with
This method has been introduced in version 0.23.
"""
def each_value(self) -> Iterable[RdbItemValue]:
def destroy(self) -> None:
r"""
@brief Explicitly destroys the object
Explicitly destroys the object on C++ side if it was owned by the script interpreter. Subsequent access to this object will throw an exception.
If the object is not owned by the script, this method will do nothing.
"""
def destroyed(self) -> bool:
r"""
@brief Returns a value indicating whether the object was already destroyed
This method returns true, if the object was destroyed, either explicitly or by the C++ side.
The latter may happen, if the object is owned by a C++ object which got destroyed itself.
"""
def dup(self) -> RdbItem:
r"""
@brief Creates a copy of self
"""
def each_value(self) -> Iterator[RdbItemValue]:
r"""
@brief Iterates over all values
"""
def has_image(self) -> bool:
r"""
@brief Gets a value indicating that the item has an image attached
See \image_str how to obtain the image.
This method has been introduced in version 0.28.
"""
def has_tag(self, tag_id: int) -> bool:
r"""
@brief Returns a value indicating whether the item has a tag with the given ID
@return True, if the item has a tag with the given ID
"""
def image_pixels(self) -> lay.PixelBuffer:
r"""
@brief Gets the attached image as a PixelBuffer object
This method has been added in version 0.28.
"""
def is_const_object(self) -> bool:
r"""
@brief Returns a value indicating whether the reference is a const reference
This method returns true, if self is a const reference.
In that case, only const methods may be called on self.
"""
def is_visited(self) -> bool:
r"""
@brief Gets a value indicating whether the item was already visited
@ -725,35 +982,54 @@ class ReportDatabase:
"""
description: str
r"""
Getter:
@brief Gets the databases description
The description is a general purpose string that is supposed to further describe the database and it's content in a human-readable form.
@return The description string
Setter:
@brief Sets the databases description
@param desc The description string
"""
generator: str
r"""
Getter:
@brief Gets the databases generator
The generator string describes how the database was created, i.e. DRC tool name and tool options.
In a later version this will allow re-running the tool that created the report.
@return The generator string
Setter:
@brief Sets the generator string
@param generator The generator string
"""
original_file: str
r"""
Getter:
@brief Gets the original file name and path
The original file name is supposed to describe the file from which this report database was generated. @return The original file name and path
Setter:
@brief Sets the original file name and path
@param path The path
"""
top_cell_name: str
r"""
Getter:
@brief Gets the top cell name
The top cell name identifies the top cell of the design for which the report was generated. This property must be set to establish a proper hierarchical context for a hierarchical report database. @return The top cell name
Setter:
@brief Sets the top cell name string
@param cell_name The top cell name
"""
@classmethod
def new(cls, name: str) -> ReportDatabase:
r"""
@brief Creates a report database
@param name The name of the database
The name of the database will be used in the user interface to refer to a certain database.
"""
def __init__(self, name: str) -> None:
r"""
@brief Creates a report database
@ -820,6 +1096,11 @@ class ReportDatabase:
@param qname The qualified name of the cell (name plus variant name optionally)
@return The cell object or nil if no such cell exists
"""
def create(self) -> None:
r"""
@brief Ensures the C++ object is created
Use this method to ensure the C++ object is created, for example to ensure that resources are allocated. Usually C++ objects are created on demand and not necessarily when the script object is created.
"""
@overload
def create_category(self, name: str) -> RdbCategory:
r"""
@ -892,7 +1173,7 @@ class ReportDatabase:
@param iter The iterator (a \RecursiveShapeIterator object) from which to take the items
"""
@overload
def create_items(self, cell_id: int, category_id: int, trans: db.CplxTrans, array: Iterable[db.EdgePair]) -> None:
def create_items(self, cell_id: int, category_id: int, trans: db.CplxTrans, array: Sequence[db.EdgePair]) -> None:
r"""
@brief Creates new edge pair items for the given cell/category combination
For each edge pair a single item will be created. The value of the item will be this edge pair.
@ -906,7 +1187,7 @@ class ReportDatabase:
@param edge_pairs The list of edge_pairs for which the items are created
"""
@overload
def create_items(self, cell_id: int, category_id: int, trans: db.CplxTrans, array: Iterable[db.Edge]) -> None:
def create_items(self, cell_id: int, category_id: int, trans: db.CplxTrans, array: Sequence[db.Edge]) -> None:
r"""
@brief Creates new edge items for the given cell/category combination
For each edge a single item will be created. The value of the item will be this edge.
@ -920,7 +1201,7 @@ class ReportDatabase:
@param edges The list of edges for which the items are created
"""
@overload
def create_items(self, cell_id: int, category_id: int, trans: db.CplxTrans, array: Iterable[db.Polygon]) -> None:
def create_items(self, cell_id: int, category_id: int, trans: db.CplxTrans, array: Sequence[db.Polygon]) -> None:
r"""
@brief Creates new polygon items for the given cell/category combination
For each polygon a single item will be created. The value of the item will be this polygon.
@ -995,29 +1276,41 @@ class ReportDatabase:
@param shapes The shape container from which to take the items
@param trans The transformation to apply
"""
def each_category(self) -> Iterable[RdbCategory]:
def destroy(self) -> None:
r"""
@brief Explicitly destroys the object
Explicitly destroys the object on C++ side if it was owned by the script interpreter. Subsequent access to this object will throw an exception.
If the object is not owned by the script, this method will do nothing.
"""
def destroyed(self) -> bool:
r"""
@brief Returns a value indicating whether the object was already destroyed
This method returns true, if the object was destroyed, either explicitly or by the C++ side.
The latter may happen, if the object is owned by a C++ object which got destroyed itself.
"""
def each_category(self) -> Iterator[RdbCategory]:
r"""
@brief Iterates over all top-level categories
"""
def each_cell(self) -> Iterable[RdbCell]:
def each_cell(self) -> Iterator[RdbCell]:
r"""
@brief Iterates over all cells
"""
def each_item(self) -> Iterable[RdbItem]:
def each_item(self) -> Iterator[RdbItem]:
r"""
@brief Iterates over all items inside the database
"""
def each_item_per_category(self, category_id: int) -> Iterable[RdbItem]:
def each_item_per_category(self, category_id: int) -> Iterator[RdbItem]:
r"""
@brief Iterates over all items inside the database which are associated with the given category
@param category_id The ID of the category for which all associated items should be retrieved
"""
def each_item_per_cell(self, cell_id: int) -> Iterable[RdbItem]:
def each_item_per_cell(self, cell_id: int) -> Iterator[RdbItem]:
r"""
@brief Iterates over all items inside the database which are associated with the given cell
@param cell_id The ID of the cell for which all associated items should be retrieved
"""
def each_item_per_cell_and_category(self, cell_id: int, category_id: int) -> Iterable[RdbItem]:
def each_item_per_cell_and_category(self, cell_id: int, category_id: int) -> Iterator[RdbItem]:
r"""
@brief Iterates over all items inside the database which are associated with the given cell and category
@param cell_id The ID of the cell for which all associated items should be retrieved
@ -1029,6 +1322,12 @@ class ReportDatabase:
This property is set when a database is saved or loaded. It cannot be set manually.
@return The file name and path
"""
def is_const_object(self) -> bool:
r"""
@brief Returns a value indicating whether the reference is a const reference
This method returns true, if self is a const reference.
In that case, only const methods may be called on self.
"""
def is_modified(self) -> bool:
r"""
@brief Returns a value indicating whether the database has been modified
@ -1129,7 +1428,7 @@ class ReportDatabase:
This method has been added in version 0.24.
"""
def variants(self, name: str) -> Iterable[int]:
def variants(self, name: str) -> List[int]:
r"""
@brief Gets the variants for a given cell name
@param name The basic name of the cell

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
TARGET = laycore
REALMODULE = lay
PYI = laycore.pyi
include($$PWD/../pymod.pri)

View File

@ -1,6 +1,7 @@
TARGET = libcore
REALMODULE = lib
PYI = libcore.pyi
include($$PWD/../pymod.pri)

View File

@ -49,6 +49,18 @@ msvc {
}
INSTALLS = lib_target
!equals(PYI, "") {
msvc {
QMAKE_POST_LINK += && $(COPY) $$shell_path($$PWD/distutils_src/klayout/$$PYI) $$shell_path($$DESTDIR_PYMOD)
} else {
QMAKE_POST_LINK += && $(MKDIR) $$DESTDIR_PYMOD/$$REALMODULE && $(COPY) $$PWD/distutils_src/klayout/$$PYI $$DESTDIR_PYMOD
}
POST_TARGETDEPS += $$PWD/distutils_src/klayout/$$PYI
}
!equals(REALMODULE, "") {
msvc {

View File

@ -1,6 +1,7 @@
TARGET = rdbcore
REALMODULE = rdb
PYI = rdbcore.pyi
include($$PWD/../pymod.pri)

View File

@ -1,700 +0,0 @@
""" Stub file generation routines.
This module contains routines to generate stub files from klayout's python API.
This uses the `tl` module of the API, which offers an introspection layer to
the C-extension modules.
"""
from collections import Counter
from copy import copy
from dataclasses import dataclass, field
from functools import wraps
import functools
from sys import argv
from textwrap import indent
from typing import Any, List, Optional, Tuple, Union
import pya # initialize all modules
import klayout.tl as ktl
def qualified_name(_class: ktl.Class) -> str:
name = _class.name()
if _class.parent():
return f"{qualified_name(_class.parent())}.{name}"
else:
return name
def superclass(_class: ktl.Class) -> str:
if _class.base():
return superclass(_class.base())
else:
return _class.name()
def is_reserved_word(name: str) -> bool:
wordlist = [
"and",
"del",
"from",
"not",
"while",
"as",
"elif",
"global",
"or",
"with",
"assert",
"else",
"if",
"pass",
"yield",
"break",
"except",
"import",
"print",
"class",
"exec",
"in",
"raise",
"continue",
"finally",
"is",
"return",
"def",
"for",
"lambda",
"try",
"None",
]
return name in wordlist
def translate_methodname(name: str) -> str:
"""
Should be the same as pyaModule.cc:extract_python_name function
* The name string encodes some additional information, specifically:
* "*..." The method is protected
* "x|y" Aliases (synonyms)
* "x|#y" y is deprecated
* "x=" x is a setter
* ":x" x is a getter
* "x?" x is a predicate
* Backslashes can be used to escape the special characters, like "*" and "|".
"""
if name == "new":
new_name = "__init__"
elif name == "++":
new_name = "inc"
elif name == "--":
new_name = "dec"
elif name == "()":
new_name = "call"
elif name == "!":
new_name = "not"
elif name == "==":
new_name = "__eq__"
elif name == "!=":
new_name = "__ne__"
elif name == "<":
new_name = "__lt__"
elif name == "<=":
new_name = "__le__"
elif name == ">":
new_name = "__gt__"
elif name == ">=":
new_name = "__ge__"
elif name == "<=>":
new_name = "__cmp__"
elif name == "+":
new_name = "__add__"
elif name == "+@":
new_name = "__pos__"
elif name == "-":
new_name = "__sub__"
elif name == "-@":
new_name = "__neg__"
elif name == "/":
new_name = "__truediv__"
elif name == "*":
new_name = "__mul__"
elif name == "%":
new_name = "__mod__"
elif name == "<<":
new_name = "__lshift__"
elif name == ">>":
new_name = "__rshift__"
elif name == "~":
new_name = "__invert__"
elif name == "&":
new_name = "__and__"
elif name == "|":
new_name = "__or__"
elif name == "^":
new_name = "__xor__"
elif name == "+=":
new_name = "__iadd__"
elif name == "-=":
new_name = "__isub__"
elif name == "/=":
new_name = "__itruediv__"
elif name == "*=":
new_name = "__imul__"
elif name == "%=":
new_name = "__imod__"
elif name == "<<=":
new_name = "__ilshift__"
elif name == ">>=":
new_name = "__irshift__"
elif name == "&=":
new_name = "__iand__"
elif name == "|=":
new_name = "__ior__"
elif name == "^=":
new_name = "__ixor__"
elif name == "[]":
new_name = "__getitem__"
elif name == "[]=":
new_name = "__setitem__"
else:
# Ignore other conversions for now.
if name.startswith("*"):
print(name)
new_name = name
if is_reserved_word(new_name):
new_name = new_name + "_"
return new_name
_type_dict = dict()
_type_dict[ktl.ArgType.TypeBool] = "bool"
_type_dict[ktl.ArgType.TypeChar] = "str"
_type_dict[ktl.ArgType.TypeDouble] = "float"
_type_dict[ktl.ArgType.TypeFloat] = "float"
_type_dict[ktl.ArgType.TypeInt] = "int"
_type_dict[ktl.ArgType.TypeLong] = "int"
_type_dict[ktl.ArgType.TypeLongLong] = "int"
# _type_dict[ktl.ArgType.TypeMap] = None
# _type_dict[ktl.ArgType.TypeObject] = None
_type_dict[ktl.ArgType.TypeSChar] = "str"
_type_dict[ktl.ArgType.TypeShort] = "int"
_type_dict[ktl.ArgType.TypeString] = "str"
_type_dict[ktl.ArgType.TypeUChar] = "str"
_type_dict[ktl.ArgType.TypeUInt] = "int"
_type_dict[ktl.ArgType.TypeULong] = "int"
_type_dict[ktl.ArgType.TypeULongLong] = "int"
_type_dict[ktl.ArgType.TypeUShort] = "int"
_type_dict[ktl.ArgType.TypeVar] = "Any"
# _type_dict[ktl.ArgType.TypeVector] = None
_type_dict[ktl.ArgType.TypeVoid] = "None"
_type_dict[ktl.ArgType.TypeVoidPtr] = "None"
def _translate_type(arg_type: ktl.ArgType, within_class: ktl.Class) -> str:
"""Translates klayout's C-type to a type in Python.
This function is equivalent to the `type_to_s` in `pyaModule.cc`.
See also `type_to_s` in `layGSIHelpProvider.cc`"""
py_str: str = ""
if arg_type.type() == ktl.ArgType.TypeObject:
if within_class.module() and arg_type.cls().module() != within_class.module():
py_str = arg_type.cls().module() + "." + qualified_name(arg_type.cls())
else:
py_str = qualified_name(arg_type.cls())
elif arg_type.type() == ktl.ArgType.TypeMap:
inner_key = _translate_type(arg_type.inner_k(), within_class)
inner_val = _translate_type(arg_type.inner(), within_class)
py_str = f"Dict[{inner_key}, {inner_val}]"
elif arg_type.type() == ktl.ArgType.TypeVector:
py_str = f"Iterable[{_translate_type(arg_type.inner(), within_class)}]"
else:
py_str = _type_dict[arg_type.type()]
if arg_type.is_iter():
py_str = f"Iterable[{py_str}]"
if arg_type.has_default():
py_str = f"Optional[{py_str}] = ..."
return py_str
@dataclass
class _Method:
name: str
is_setter: bool
is_getter: bool
is_classvar: bool
is_classmethod: bool
doc: str
m: ktl.Method
@dataclass
class Stub:
signature: str
name: Any
docstring: str
indent_docstring: bool = True
child_stubs: List["Stub"] = field(default_factory=list)
decorator: str = ""
def __eq__(self, __o: object) -> bool:
if not isinstance(__o, Stub):
return False
return (
self.signature == __o.signature
and self.child_stubs == __o.child_stubs
and self.decorator == __o.decorator
)
def __lt__(self, other: "Stub") -> bool:
# mypy complains if an overload with float happens before int
self_signature = self.signature.replace(": int", "0").replace(": float", "1")
other_signature = other.signature.replace(": int", "0").replace(": float", "1")
self_sortkey = self.name, len(self.signature.split(",")), self_signature
other_sortkey = other.name, len(other.signature.split(",")), other_signature
return self_sortkey < other_sortkey
def __hash__(self):
return hash(self.format_stub(include_docstring=False))
def format_stub(self, include_docstring=True):
lines = []
lines.extend(self.decorator.splitlines())
if self.indent_docstring: # all but properties
if include_docstring or len(self.child_stubs) > 0:
lines.append(self.signature + ":")
else:
lines.append(self.signature + ": ...")
else:
lines.append(self.signature)
stub_str = "\n".join(lines)
lines = []
lines.append('r"""')
lines.extend(self.docstring.splitlines())
lines.append('"""')
doc_str = "\n".join(lines)
# indent only if it is required (methods, not properties)
if self.indent_docstring:
doc_str = indent(doc_str, " " * 4)
if include_docstring:
stub_str += "\n"
stub_str += doc_str
for stub in self.child_stubs:
stub_str += "\n"
stub_str += indent(
stub.format_stub(include_docstring=include_docstring), " " * 4
)
return stub_str
@dataclass(eq=False)
class MethodStub(Stub):
indent_docstring: bool = True
@dataclass(eq=False)
class PropertyStub(Stub):
indent_docstring: bool = False
@dataclass(eq=False)
class ClassStub(Stub):
indent_docstring: bool = True
def get_c_methods(c: ktl.Class) -> List[_Method]:
"""
Iterates over all methods defined in the C API, sorting
properties, class methods and bound methods.
"""
method_list: List[_Method] = list()
setters = set()
def primary_synonym(m: ktl.Method) -> ktl.MethodOverload:
for ms in m.each_overload():
if ms.name() == m.primary_name():
return ms
raise ("Primary synonym not found for method " + m.name())
for m in c.each_method():
if m.is_signal():
# ignore signals as they do not have arguments and are neither setters nor getters.
continue
method_def = primary_synonym(m)
if method_def.is_setter():
setters.add(method_def.name())
for m in c.each_method():
num_args = len([True for a in m.each_argument()])
method_def = primary_synonym(m)
# extended definition of "getter" for Python
is_getter = (num_args == 0) and (
method_def.is_getter()
or (not method_def.is_setter() and method_def.name() in setters)
)
is_setter = (num_args == 1) and method_def.is_setter()
is_classvar = (num_args == 0) and (m.is_static() and not m.is_constructor())
method_list.append(
_Method(
name=method_def.name(),
is_setter=is_setter,
is_getter=is_getter or is_classvar,
is_classmethod=m.is_constructor(),
is_classvar=is_classvar,
doc=m.doc(),
m=m,
)
)
# print(f"{m.name()}: {m.is_static()=}, {m.is_constructor()=}, {m.is_const_object()=}")
return method_list
def get_py_child_classes(c: ktl.Class):
for c_child in c.each_child_class():
return c_child
def get_py_methods(
c: ktl.Class,
) -> List[Stub]:
c_methods = get_c_methods(c)
# extract properties
_c_methods = copy(c_methods)
# Helper functions
def find_setter(c_methods: List[_Method], name: str):
"""Finds a setter method in c_methods list with a given name.f"""
for m in c_methods:
if m.name == name and m.is_setter:
return m
return None
def find_getter(c_methods: List[_Method], name: str):
"""Finds a getter method in c_methods list with a given name.f"""
for m in c_methods:
if m.name == name and m.is_getter:
return m
return None
translate_type = functools.partial(_translate_type, within_class=c)
def _get_arglist(m: ktl.Method, self_str) -> List[Tuple[str, ktl.ArgType]]:
args = [(self_str, None)]
for i, a in enumerate(m.each_argument()):
argname = a.name()
if is_reserved_word(argname):
argname += "_"
elif not argname:
argname = f"arg{i}"
args.append((argname, a))
return args
def _format_args(arglist: List[Tuple[str, Optional[str]]]):
args = []
for argname, argtype in arglist:
if argtype:
args.append(f"{argname}: {argtype}")
else:
args.append(argname)
return ", ".join(args)
def format_args(m: ktl.Method, self_str: str = "self") -> str:
arg_list = _get_arglist(m, self_str=self_str)
new_arglist: List[Tuple[str, Optional[str]]] = []
for argname, a in arg_list:
if a:
new_arglist.append((argname, translate_type(a)))
else:
new_arglist.append((argname, None))
return _format_args(new_arglist)
# Extract all properties (methods that have getters and/or setters)
properties: List[Stub] = list()
for m in copy(_c_methods):
ret_type = translate_type(m.m.ret_type())
if m.is_getter:
m_setter = find_setter(c_methods, m.name)
if m_setter is not None: # full property
doc = m.doc + m_setter.doc
properties.append(
PropertyStub(
decorator="",
signature=f"{translate_methodname(m.name)}: {ret_type}",
name=f"{translate_methodname(m.name)}",
docstring=doc,
)
)
# _c_methods.remove(m_setter)
elif m.is_classvar:
properties.append(
PropertyStub(
decorator="",
signature=f"{translate_methodname(m.name)}: ClassVar[{ret_type}]",
name=f"{translate_methodname(m.name)}",
docstring=m.doc,
)
)
else: # only getter
properties.append(
MethodStub(
decorator="@property",
signature=f"def {translate_methodname(m.name)}(self) -> {ret_type}",
name=f"{translate_methodname(m.name)}",
docstring=m.doc,
)
)
_c_methods.remove(m)
elif m.is_setter and not find_getter(
c_methods, m.name
): # include setter-only properties as full properties
doc = "WARNING: This variable can only be set, not retrieved.\n" + m.doc
properties.append(
PropertyStub(
decorator="",
signature=f"{translate_methodname(m.name)}: {ret_type}",
name=f"{translate_methodname(m.name)}",
docstring=doc,
)
)
_c_methods.remove(m)
for m in copy(_c_methods):
if m.is_setter:
_c_methods.remove(m)
def get_altnames(c_name: str):
names = [c_name]
if c_name == "to_s":
names.append("__str__")
return names
# Extract all classmethods
classmethods: List[Stub] = list()
for m in copy(_c_methods):
if m.is_classmethod:
# Exception: if it is an __init__ constructor, ignore.
# Will be treated by the bound method logic below.
if translate_methodname(m.name) == "__init__":
continue
decorator = "@classmethod"
ret_type = translate_type(m.m.ret_type())
for name in get_altnames(m.name):
classmethods.append(
MethodStub(
decorator=decorator,
signature=f"def {translate_methodname(name)}({format_args(m.m, 'cls')}) -> {ret_type}",
name=f"{translate_methodname(name)}",
docstring=m.doc,
)
)
_c_methods.remove(m)
# Extract bound methods
boundmethods: List[Stub] = list()
for m in copy(_c_methods):
decorator = ""
translated_name = translate_methodname(m.name)
if translated_name in [s.name for s in properties]:
translated_name += "_"
if translated_name == "__init__":
ret_type = "None"
else:
ret_type = translate_type(m.m.ret_type())
arg_list = _get_arglist(m.m, "self")
# Exceptions:
# For X.__eq__(self, a:X), treat second argument as type object instead of X
if translated_name in ("__eq__", "__ne__"):
arg_list[1] = arg_list[1][0], "object"
# X._assign(self, other:X), mypy complains if _assign is defined at base class.
# We can't specialize other in this case.
elif translated_name in ("_assign", "assign"):
assert arg_list[1][1].type() == ktl.ArgType.TypeObject
arg_list[1] = arg_list[1][0], superclass(arg_list[1][1].cls())
else:
new_arg_list = []
for argname, a in arg_list:
if a:
new_arg_list.append((argname, translate_type(a)))
else:
new_arg_list.append((argname, a))
arg_list = new_arg_list
formatted_args = _format_args(arg_list)
for name in get_altnames(translated_name):
boundmethods.append(
MethodStub(
decorator=decorator,
signature=f"def {name}({formatted_args}) -> {ret_type}",
name=f"{name}",
docstring=m.doc,
)
)
_c_methods.remove(m)
def add_overload_decorator(stublist: List[Stub]):
stubnames = [stub.name for stub in stublist]
for stub in stublist:
has_duplicate = stubnames.count(stub.name) > 1
if has_duplicate:
stub.decorator = "@overload\n" + stub.decorator
return stublist
boundmethods = sorted(set(boundmethods)) # sometimes duplicate methods are defined.
add_overload_decorator(boundmethods)
properties = sorted(set(properties))
classmethods = sorted(classmethods)
add_overload_decorator(classmethods)
return_list: List[Stub] = properties + classmethods + boundmethods
return return_list
def get_class_stub(
c: ktl.Class,
ignore: List[ktl.Class] = None,
module: str = "",
) -> ClassStub:
base = ""
if c.base():
base = f"({c.base().name()})"
if c.module() != module:
full_name = c.module() + "." + c.name()
else:
full_name = c.name()
_cstub = ClassStub(
signature="class " + full_name + base, docstring=c.doc(), name=full_name
)
child_attributes = get_py_methods(c)
for child_c in c.each_child_class():
_cstub.child_stubs.append(
get_class_stub(
child_c,
ignore=ignore,
module=c.module(),
)
)
for stub in child_attributes:
_cstub.child_stubs.append(stub)
return _cstub
def get_classes(module: str) -> List[ktl.Class]:
_classes = []
for c in ktl.Class.each_class():
if c.module() != module:
continue
_classes.append(c)
return _classes
def get_module_stubs(module:str) -> List[ClassStub]:
_stubs = []
_classes = get_classes(module)
for c in _classes:
_cstub = get_class_stub(c, ignore=_classes, module=module)
_stubs.append(_cstub)
return _stubs
def print_db():
print("from typing import Any, ClassVar, Dict, Iterable, Optional")
print("from typing import overload")
print("import klayout.rdb as rdb")
print("import klayout.tl as tl")
for stub in get_module_stubs("db"):
print(stub.format_stub(include_docstring=True) + "\n")
def print_rdb():
print("from typing import Any, ClassVar, Dict, Iterable, Optional")
print("from typing import overload")
print("import klayout.db as db")
for stub in get_module_stubs("rdb"):
print(stub.format_stub(include_docstring=True) + "\n")
def print_tl():
print("from typing import Any, ClassVar, Dict, Iterable, Optional")
print("from typing import overload")
for stub in get_module_stubs("tl"):
print(stub.format_stub(include_docstring=True) + "\n")
def test_v1():
db_classes = get_classes("db")
for c in db_classes:
if c.name() != "Region":
continue
print(
get_class_stub(c, ignore=db_classes, module="db").format_stub(
include_docstring=False
)
)
def test_v2():
db_classes = get_classes("db")
for c in db_classes:
if c.name() != "DPoint":
continue
print(
get_class_stub(c, ignore=db_classes, module="db").format_stub(
include_docstring=True
)
)
def test_v3():
db_classes = get_classes("db")
for c in db_classes:
if c.name() != "Instance":
continue
print(
get_class_stub(c, ignore=db_classes, module="db").format_stub(
include_docstring=False
)
)
def test_v4():
db_classes = get_classes("db")
for c in db_classes:
if c.name() != "Region":
continue
for cclass in get_py_child_classes(c):
print(
get_class_stub(cclass, ignore=db_classes, module="db").format_stub(
include_docstring=False
)
)
if __name__ == "__main__":
if len(argv) < 2:
print("Specity module in argument: 'db', 'rdb', 'tl'")
exit(1)
if argv[1] == "db":
print_db()
elif argv[1] == "rdb":
print_rdb()
elif argv[1] == "tl":
print_tl()
else:
# print_rdb()
# test_v4()
print("Wrong arguments")

View File

@ -1,6 +1,7 @@
TARGET = tlcore
REALMODULE = tl
PYI = tlcore.pyi
include($$PWD/../pymod.pri)

View File

@ -1,24 +0,0 @@
Author: Thomas Ferreira de Lima
email: thomas@tlima.me
## Notes
To use the stubgen script for the three main modules, run the following from the root folder of the repository:
`$ python ./src/pymod/stubgen.py db >! src/pymod/distutils_src/klayout/dbcore.pyi`
`$ python ./src/pymod/stubgen.py rdb >! src/pymod/distutils_src/klayout/rdbcore.pyi`
`$ python ./src/pymod/stubgen.py tl >! src/pymod/distutils_src/klayout/tlcore.pyi`
To compare the generated stubs with a python self-inspection of the klayout module, try the following:
Navigate to `./src/pymod/distutils_src`.
Run, for example:
`$ stubtest klayout.tlcore`
TODO:
- [ ] Integrate above scripts with CI
## Old notes
CHECKLIST:
- [x] 1. Use klayout.tl to inspect all classes and methods in pya.
- [x] 2. Figure out last few bugs.
- DPoint has a method with "=" when it should have been "*=". There must be an issue with the gsiDeclInternal algorithms.
- Some inner classes, e.g. LogicalOp inside CompoundRegionOperationNode are not returning
- [x] 3. Manually check and compare to mypy's output.
- Looks good, but there are a few discrepancies between actual python module and stubs. Namely, deprecated methods were not included in the stub. The opposite is sometimes true as well, though for newer, experimental classes e.g. `klayout.dbcore.GenericDeviceCombiner.combine_devices`.

View File

@ -1556,6 +1556,17 @@ rba_add_path (const std::string &path)
}
}
static std::string
ruby_name (const std::string &n)
{
if (n == "*!") {
// non-commutative multiplication
return "*";
} else {
return n;
}
}
namespace
{
@ -1672,7 +1683,7 @@ public:
} else if (syn->is_setter) {
mt->add_method (syn->name + "=", *m);
} else {
mt->add_method (syn->name, *m);
mt->add_method (ruby_name (syn->name), *m);
}
}
@ -1709,7 +1720,7 @@ public:
} else {
mt->add_method (syn->name, *m);
mt->add_method (ruby_name (syn->name), *m);
}
}

View File

@ -36,13 +36,13 @@ namespace tl
/**
* @brief The unspecific exception class
*
* This class is the base class for all exceptions in this
* framework. It does not carry further information except
* a message string that can be created through different
* This class is the base class for all exceptions in this
* framework. It does not carry further information except
* a message string that can be created through different
* constructor methods.
*/
class TL_PUBLIC Exception
class TL_PUBLIC Exception
{
public:
Exception (const std::string &msg)
@ -169,6 +169,15 @@ private:
void init (const std::string &fmt, const std::vector<tl::Variant> &a);
};
/**
* @brief An exception thrown when the wrong type is provided as argument.
*/
struct TL_PUBLIC TypeError
: public Exception
{
using Exception::Exception;
};
/**
* @brief A "neutral" exception thrown to terminate some operation
* This exception is not shown.
@ -195,4 +204,3 @@ struct TL_PUBLIC InternalException
} // namespace tl
#endif

View File

@ -48,5 +48,3 @@ if __name__ == '__main__':
if not unittest.TextTestRunner(verbosity = 1).run(suite).wasSuccessful():
sys.exit(1)

View File

@ -21,6 +21,7 @@ import unittest
import os
import sys
import gc
import copy
# Set this to True to disable some tests involving exceptions
leak_check = "TEST_LEAK_CHECK" in os.environ
@ -3073,6 +3074,36 @@ class BasicTest(unittest.TestCase):
go = None
self.assertEqual(pya.GObject.g_inst_count(), gc)
# fallback to __rmul__ for not implemented __mul__
def test_90(self):
class RMulObject:
def __init__(self, factor):
self.factor = factor
def __rmul__(self, point):
return point * self.factor
def __radd__(self, point):
return point + pya.Vector(1,1) * self.factor
p = pya.Point(1, 0)
fac2 = RMulObject(2)
self.assertEqual(p * 2, p * fac2) # p.__mul__(fac2) should return NotImplemented, which will call fac2.__rmul__(p)
self.assertEqual(pya.Point(3,2), p + fac2)
# copy and deepcopy
def test_91(self):
p = pya.Point(1, 0)
pc = copy.copy(p)
pdc = copy.deepcopy(p)
pdc.x = 4
pc.x = 3
p.x = 2
self.assertEqual(p.x, 2)
self.assertEqual(pc.x, 3)
self.assertEqual(pdc.x, 4)
# run unit tests
if __name__ == '__main__':