单个存储库中的多个Python包

问题描述 投票:0回答:2

嗨,我是一个名为 GridCal 的开源程序的创建者。它由基于 Qt 的 GUI 和计算引擎组成。从一开始,出于实际原因,我就将 GUI 和引擎放在同一个包下。下面的结构运行良好,并且 pypi 的包已正确创建。这是因为

setup.py
文件与包
GirdCal

处于同一级别
repository_folder
|
|_ src
    |_ GridCal
    |     |_ GUI
    |     |_ Core
    |     |_ IO
    |     |_ Simulations
    |     |_ __init__.py
    |     
    |_ setup.py 
    |_ upload_to_pypi.py

最近,我将代码拆分为 2 个包(GridCal 和 GridCalEngine),看看是否可以获得适用于开发和部署到 pypi 的结构。

结构如下:

repository_folder | |_ src |_ GridCal | |_ GUI | |_ __init__.py | |_ setup.py | |_ GridCalEngine | |_ Core | |_ IO | |_ Simulations | |_ __init__.py | |_ setup.py | |_ upload_to_pypi.py
这种拆分结构非常适合开发,因为 GUI 可以正确引用引擎,生成的包会被检查并上传到 pypi,但是在安装这些包时,安装失败,因为 

setup.py

 文件不在包之外就像前面的例子一样。

我在SO中看到了像

this和其他相关问题这样的资源,但它们没有解决生成可安装包的问题。

如何解决setup.py

文件位置问题?

python-3.x setuptools pypi
2个回答
0
投票
如果您无法将

setup.py

 移出包裹,请移动包裹
在子目录内。这是推荐的扁平结构
布局:

repository_folder | |_ GridCal | |_ GridCal | | | |_ GUI | | | |_ __init__.py | |_ setup.py | |_ GridCalEngine | |_ GridCalEngine | | | |_ Core | | | |_ IO | | | |_ Simulations | | | |_ __init__.py | |_ setup.py | |_ upload_to_pypi.py
 或 src 布局:

repository_folder | |_ GridCal | _ src | |_ GridCal | | |_ GUI | | |_ __init__.py | |_ setup.py | |_ GridCalEngine | _ src | | |_ GridCalEngine | | |_ Core | | |_ IO | | |_ Simulations | | |_ __init__.py | |_ setup.py | |_ upload_to_pypi.py
    

0
投票
我最终编写了自己的打包代码:

# This file is part of GridCal # # GridCal 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 3 of the License, or # (at your option) any later version. # # GridCal 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 GridCal. If not, see <http://www.gnu.org/licenses/>. import os import sys import tarfile import zipfile from pathlib import Path from typing import List, Tuple from subprocess import call def build_setup_cfg() -> str: """ Generate the content of setup.cgf :return: """ val = '[egg_info]\n' val += 'tag_build = \n' val += 'tag_date = 0\n' return val def build_pkg_info(name: str, version: str, summary: str, home_page: str, author: str, email: str, license_: str, keywords: str, classifiers_list: List[str], requires_pyhon: str, description_content_type: str, provides_extra: str, long_description: str): """ Generate the content of PKG-INFO :param name: :param version: :param summary: :param home_page: :param author: :param email: :param license_: :param keywords: :param classifiers_list: :param requires_pyhon: :param description_content_type: :param provides_extra: :param long_description: :return: """ val = 'Metadata-Version: 2.1\n' val += "Name: " + name + '\n' val += "Version: " + version + '\n' val += "Summary: " + summary + '\n' val += "Home-page: " + home_page + '\n' val += "Author: " + author + '\n' val += "Author-email: " + email + '\n' val += "License: " + license_ + '\n' val += "Keywords: " + keywords + '\n' for classifier in classifiers_list: val += "Classifier: " + classifier + '\n' val += "Requires-Python: " + requires_pyhon + '\n' val += "Description-Content-Type: " + description_content_type + '\n' val += "Provides-Extra: " + provides_extra + '\n' val += '\n' + long_description + '\n' return val def check_ext(filename, ext_filter) -> bool: """ Check of the file complies with the list of extensions :param filename: filename :param ext_filter: list of extnsions :return: true/false """ for ext in ext_filter: if filename.endswith(ext): return True return False def find_pkg_files(path: str, ext_filter=['.py']) -> List[Tuple[str, str]]: """ Get list :param path: path to traverse :param ext_filter: extensions of files to include :return: list of [filename, complete path """ files_list = list() for (dirpath, dirnames, filenames) in os.walk(path): for fname in filenames: if check_ext(filename=fname, ext_filter=ext_filter): pth = os.path.join(dirpath, fname) files_list.append((fname, pth)) return files_list def build_tar_gz_pkg(pkg_name: str, setup_path: str, version: str, summary: str, home_page: str, author: str, email: str, license_: str, keywords: str, classifiers_list: List[str], requires_pyhon: str, description_content_type: str, provides_extra: str, long_description: str, folder_to_save='dist', ext_filter=['py']): """ :param pkg_name: :param setup_path: :param version: :param summary: :param home_page: :param author: :param email: :param license_: :param keywords: :param classifiers_list: :param requires_pyhon: :param description_content_type: :param provides_extra: :param long_description: :param folder_to_save: :param ext_filter: :return: """ pkg_name2 = pkg_name + '-' + version filename = pkg_name2 + '.tar.gz' output_filename = os.path.join(folder_to_save, filename) files = find_pkg_files(path=pkg_name, ext_filter=ext_filter) pkg_info = build_pkg_info(name=pkg_name, version=version, summary=summary, home_page=home_page, author=author, email=email, license_=license_, keywords=keywords, classifiers_list=classifiers_list, requires_pyhon=requires_pyhon, description_content_type=description_content_type, provides_extra=provides_extra, long_description=long_description) pkg_info_path = 'pkg_info' + pkg_name with open(pkg_info_path, 'w') as f: f.write(pkg_info) setup_cfg = build_setup_cfg() setup_cfg_path = 'setup_cfg' + pkg_name with open(setup_cfg_path, 'w') as f: f.write(setup_cfg) with tarfile.open(output_filename, "w:gz") as tar: for name, file_path in files: if not name.endswith('setup.py'): tar.add(file_path, arcname=os.path.join(pkg_name2, file_path)) # add the setup where it belongs tar.add(setup_path, arcname=os.path.join(pkg_name2, 'setup.py')) # add tar.add(pkg_info_path, arcname=os.path.join(pkg_name2, 'PKG-INFO')) tar.add(setup_cfg_path, arcname=os.path.join(pkg_name2, 'setup.cfg')) os.remove(pkg_info_path) os.remove(setup_cfg_path) return output_filename def build_wheel(pkg_name: str, setup_path: str, version: str, summary: str, home_page: str, author: str, email: str, license_: str, keywords: str, classifiers_list: List[str], requires_pyhon: str, description_content_type: str, provides_extra: str, long_description: str, folder_to_save='dist', ext_filter=['py']): """ :param pkg_name: :param setup_path: :param version: :param summary: :param home_page: :param author: :param email: :param license_: :param keywords: :param classifiers_list: :param requires_pyhon: :param description_content_type: :param provides_extra: :param long_description: :param folder_to_save: :param ext_filter: :return: """ pkg_name2 = pkg_name + '-' + version filename = pkg_name2 + '.whl' output_filename = os.path.join(folder_to_save, filename) files = find_pkg_files(path=pkg_name, ext_filter=ext_filter) pkg_info = build_pkg_info(name=pkg_name, version=version, summary=summary, home_page=home_page, author=author, email=email, license_=license_, keywords=keywords, classifiers_list=classifiers_list, requires_pyhon=requires_pyhon, description_content_type=description_content_type, provides_extra=provides_extra, long_description=long_description) pkg_info_path = 'pkg_info' + pkg_name with open(pkg_info_path, 'w') as f: f.write(pkg_info) setup_cfg = build_setup_cfg() setup_cfg_path = 'setup_cfg' + pkg_name with open(setup_cfg_path, 'w') as f: f.write(setup_cfg) with zipfile.ZipFile(output_filename, "w", zipfile.ZIP_DEFLATED)as tar: for name, file_path in files: if not name.endswith('setup.py'): tar.write(file_path, arcname=os.path.join(pkg_name2, file_path)) # add the setup where it belongs tar.write(setup_path, arcname=os.path.join(pkg_name2, 'setup.py')) # add tar.writestr(os.path.join(pkg_name2, 'PKG-INFO'), data=pkg_info) tar.writestr(os.path.join(pkg_name2, 'setup.cfg'), data=setup_cfg_path) os.remove(pkg_info_path) os.remove(setup_cfg_path) return output_filename def read_pypirc() -> Tuple[str, str]: """ Read the pypirc file located in home :return: user, password """ home = Path.home() path = os.path.join(home, 'pypirc') with open(path) as file: lines = [line.rstrip() for line in file] user = '' pwd = '' for line in lines: if '=' in line: key, val = line.split('=') if key.strip() == 'username': user = val.strip() elif key.strip() == 'password': pwd = val.strip() return user, pwd def publish(pkg_name: str, setup_path: str, version: str, summary: str, home_page: str, author: str, email: str, license_: str, keywords: str, classifiers_list: List[str], requires_pyhon: str, description_content_type: str, provides_extra: str, long_description: str): """ Publish package to Pypi using twine :param pkg_name: name of the package (i.e GridCal) :param setup_path: path to the package setup.py (i.e. GridCal/setup.py) :param version: verison of the package (i.e. 5.1.0) :param summary: :param home_page: :param author: :param email: :param license_: :param keywords: :param classifiers_list: :param requires_pyhon: :param description_content_type: :param provides_extra: :param long_description: """ # build the tar.gz file fpath = build_tar_gz_pkg(pkg_name=pkg_name, setup_path=setup_path, version=version, summary=summary, home_page=home_page, author=author, email=email, license_=license_, keywords=keywords, classifiers_list=classifiers_list, requires_pyhon=requires_pyhon, description_content_type=description_content_type, provides_extra=provides_extra, long_description=long_description, folder_to_save='dist', ext_filter=['.py', '.csv', '.txt']) # check the tar.gz file call([sys.executable, '-m', 'twine', 'check', fpath]) user, pwd = read_pypirc() # upload the tar.gz file call([sys.executable, '-m', 'twine', 'upload', '--repository', 'pypi', '--username', user, '--password', pwd, fpath])
这使我能够拥有完全符合我想要的结构,并仍然生成 pip 正确的包。

repository_folder | |_ src |_ GridCal | |_ GUI | |_ __init__.py | |_ setup.py | |_ GridCalEngine | |_ Core | |_ IO | |_ Simulations | |_ __init__.py | |_ setup.py | |_ upload_to_pypi.py
    
© www.soinside.com 2019 - 2024. All rights reserved.