Skip to content

setuptools

Module diagram

classDiagram
  class setuptools {
  }
  class cythonize_setup {
  }
  class import_parser {
  }
  cythonize_setup --> import_parser

setuptools

cythonize_setup

make_extension

make_extension(ext_name: str) -> Extension
Source code in SaigeToolkit/setuptools/cythonize_setup.py
def make_extension(ext_name: str) -> Extension:
    ext_path = ext_name.replace(".", os.path.sep) + ".py"
    return Extension(
        ext_name,
        [ext_path],
        include_dirs=["."],
    )

cythonize_setup

cythonize_setup(target: str, name: str, **setup_args: Dict[str, Any]) -> None

cythonize and setup target python files

Parameters:

  • target (str) –

    base setup target file, which is input to parsing function

  • name (str) –

    library name

  • setup_args (Dict[str, Any], default: {} ) –

    other library setup keywords

Example
from SaigeToolkit.setuptools.cythonize_setup import cythonize_setup
cythonize_setup(target="deploy_demo.py", name="Saige", version="1.0")
Source code in SaigeToolkit/setuptools/cythonize_setup.py
def cythonize_setup(target: str, name: str, **setup_args: Dict[str, Any]) -> None:
    """cythonize and setup target python files

    Args:
        target (str): base setup target file, which is input to parsing function
        name (str): library name
        setup_args (Dict[str, Any]): other library setup keywords

    Example:
        ``` python
        from SaigeToolkit.setuptools.cythonize_setup import cythonize_setup
        cythonize_setup(target="deploy_demo.py", name="Saige", version="1.0")
        ```
    """
    compile_list = sorted(parse_compile_list(target))

    if platform.system() == "Windows":
        """To compile __init__.py on Windows

        Reference: https://stackoverflow.com/a/58826688
        """

        def get_export_symbols_fixed(self, ext):
            return []

        build_ext.get_export_symbols = get_export_symbols_fixed

    extensions = []
    for file in compile_list:
        if os.path.isfile(file.replace(".", os.path.sep) + ".py"):
            extensions.append(make_extension(file))
            print(f"[SETUP] {file}")
    print(f"[SETUP] {len(extensions)} Files")

    setup(
        name=name,
        ext_modules=cythonize(
            extensions,
            compiler_directives={"language_level": "3"},
        ),
        **setup_args,
    )

import_parser

parse_compile_list

parse_compile_list(module: str) -> List[str]

returns python files to be built by recursively parsing import phrases.

Reference

https://github.com/andrewp-as-is/list-imports.py/blob/master/list_imports/init.py

Example
from SaigeToolkit.setuptools.import_parser import parse_compile_list
compile_list = parse_compile_list("deploy_demo.py")
Note

file/dir의 존재 유무를 isfile/isdir 함수에서 glob의 string comapre로 변경한 이유.

이슈 window에서는 filename의 대소문자를 구분하지 않음, "A.py", "a.Py", "a.PY"가 모두 같은 file. os.path.isfile/isdir 함수 또한 대소문자를 구분하지 않음, "A"가 존재할 때 isfile("a")을 호출시 True return. 따라서 폴더 내부가 다음과 같이 되어있고, import A를 했다면 ├── A │ └── init.py └── a.py linux의 경우는 A.py가 없기 때문에, A directory를 잘 찾지만, window의 경우에는 isfile(A.py)가 True이기 때문에 logic에 문제가 발생.

해결 방법 glob로 모든 file name, directory name을 가져온 뒤, string compare 진행.

unresolved issue glob가 모든 파일 리스트를 읽어오기 때문에, 폴더 내에 파일이 많은 경우 속도가 느려지는 이슈.

Source code in SaigeToolkit/setuptools/import_parser.py
def parse_compile_list(module: str) -> List[str]:
    """returns python files to be built by recursively parsing `import` phrases.

    Reference:
        https://github.com/andrewp-as-is/list-imports.py/blob/master/list_imports/__init__.py

    Example:
        ``` python
        from SaigeToolkit.setuptools.import_parser import parse_compile_list
        compile_list = parse_compile_list("deploy_demo.py")
        ```

    Note:
        file/dir의 존재 유무를 isfile/isdir 함수에서 glob의 string comapre로 변경한 이유.

        이슈
            window에서는 filename의 대소문자를 구분하지 않음,
                "A.py", "a.Py", "a.PY"가 모두 같은 file.
            os.path.isfile/isdir 함수 또한 대소문자를 구분하지 않음,
                "A"가 존재할 때 isfile("a")을 호출시 True return.
            따라서 폴더 내부가 다음과 같이 되어있고, import A를 했다면
            ├── A
            │   └── __init__.py
            └── a.py
            linux의 경우는 A.py가 없기 때문에, A directory를 잘 찾지만,
            window의 경우에는 isfile(A.py)가 True이기 때문에 logic에 문제가 발생.

        해결 방법
            glob로 모든 file name, directory name을 가져온 뒤, string compare 진행.

        unresolved issue
            glob가 모든 파일 리스트를 읽어오기 때문에, 폴더 내에 파일이 많은 경우 속도가 느려지는 이슈.
    """
    compile_list = []
    all_existing_files = glob("**", recursive=True)
    all_existing_directories = glob("**" + os.sep, recursive=True)

    def _parse_compile_list(module: str) -> None:
        import_list = get_import_module_list(module)

        # recursively update compile list
        for imp in import_list:
            import_path = imp.replace(".", os.path.sep)

            if import_path + ".py" in all_existing_files and imp not in compile_list:
                compile_list.append(imp)
                _parse_compile_list(imp)

            elif (
                import_path + os.sep in all_existing_directories
                and imp + ".__init__" not in compile_list
            ):
                compile_list.append(imp + ".__init__")
                _parse_compile_list(imp + ".__init__")

    _parse_compile_list(module)

    return sorted(list(set(compile_list)))

get_import_module_list

get_import_module_list(module: str) -> List[str]

Get importing module list from single python file

Source code in SaigeToolkit/setuptools/import_parser.py
def get_import_module_list(module: str) -> List[str]:
    """Get importing module list from single python file"""
    path = module.replace(".", os.path.sep)
    path = path[:-3] + ".py" if path.endswith(os.path.sep + "py") else path + ".py"
    assert os.path.isfile(path), f"should be file path: {path}"

    directory = os.path.dirname(path)
    with open(path, encoding="utf-8") as f:
        code = f.read()
    tree = ast.parse(code)

    # Get importing module list
    import_list = []
    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            # style of [import A]
            for subnode in node.names:
                import_list.append(subnode.name)
        if isinstance(node, ast.ImportFrom):
            if node.module is None:
                # style of [from .. import A]
                import_list.extend(["." * node.level + n.name for n in node.names])
            else:
                # style of [from A import B] or [from ..A import B]
                # add "A" or "..A"
                import_list.append("." * node.level + node.module)
                # add "A.B" or "..A.B"
                import_list.extend(["." * node.level + node.module + "." + n.name for n in node.names])

    # Interpret dot-representation
    for idx, imp in enumerate(import_list):
        if imp[0] != ".":
            continue
        dot_directory = directory
        while imp[1] == ".":
            dot_directory = os.path.dirname(dot_directory)
            imp = imp[1:]
        import_list[idx] = dot_directory.replace(os.path.sep, ".") + imp

    return import_list