How do I get the current 'package' name? (setup.py)

后端 未结 2 1814
故里飘歌
故里飘歌 2020-12-19 08:35

How do I get the current topmost package, i.e., the name defined in setup.py?

Here is my tree:

.
|-- README.md
|-- the_project_name_for_         


        
2条回答
  •  轮回少年
    2020-12-19 09:40

    A solution I've been working on:

    from os import listdir, path
    from contextlib import suppress
    import ast
    
    
    
    def get_first_setup_py(cur_dir):
        if 'setup.py' in listdir(cur_dir):
            return path.join(cur_dir, 'setup.py')
        prev_dir = cur_dir
        cur_dir = path.realpath(path.dirname(cur_dir))
        if prev_dir == cur_dir:
            raise StopIteration()
        return get_first_setup_py(cur_dir)
    
    
    setup_py_file_name = get_first_setup_py(path.dirname(__file__))
    

    First pass:

    def get_from_setup_py(setup_file): # mostly https://stackoverflow.com/a/47463422
        import importlib.util
    
    
        spec = importlib.util.spec_from_file_location('setup', setup_file)
        setup = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(setup)
        # And now access it
        print(setup)
    

    That option did work. So I returned to the ast solution I referenced in the question, and got this second pass to work:

    def parse_package_name_from_setup_py(setup_py_file_name):
        with open(setup_py_file_name, 'rt') as f:
            parsed_setup_py = ast.parse(f.read(), 'setup.py')
    
        # Assumes you have an `if __name__ == '__main__'` block:
        main_body = next(sym for sym in parsed_setup_py.body[::-1]
                         if isinstance(sym, ast.If)).body
    
        setup_call = next(sym.value
                          for sym in main_body[::-1]
                          if isinstance(sym, ast.Expr)
                          and isinstance(sym.value, ast.Call)
                          and sym.value.func.id in frozenset(('setup',
                                                              'distutils.core.setup',
                                                              'setuptools.setup')))
    
        package_name = next(keyword
                            for keyword in setup_call.keywords
                            if keyword.arg == 'name'
                            and isinstance(keyword.value, ast.Name))
    
        # Return the raw string if it is one
        if isinstance(package_name.value, ast.Str):
            return package_name.value.s
    
        # Otherwise it's a variable defined in the `if __name__ == '__main__'` block:
        elif isinstance(package_name.value, ast.Name):
            return next(sym.value.s
                        for sym in main_body
                        if isinstance(sym, ast.Assign)
                        and isinstance(sym.value, ast.Str)
                        and any(target.id == package_name.value.id
                                for target in sym.targets)
                        )
    
        else:
            raise NotImplemented('Package name extraction only built for raw strings & '
                                 'assigment in the same scope that setup() is called')
    

    Third pass (works for both installed and development versions):

    # Originally from https://stackoverflow.com/a/56032725;
    # but made more concise and added support whence source
    class App(object):
        def get_app_name(self) -> str:
            # Iterate through all installed packages and try to find one
            # that has the app's file in it
            app_def_path = inspect.getfile(self.__class__)
            with suppress(FileNotFoundError):
                return next(
                    (dist.project_name
                     for dist in pkg_resources.working_set
                     if any(app_def_path == path.normpath(path.join(dist.location, r[0]))
                            for r in csv.reader(dist.get_metadata_lines('RECORD')))),
                    None) or parse_package_name_from_setup_py(
                    get_first_setup_py(path.dirname(__file__)))
    

提交回复
热议问题