Esky 的 Python 项目结构

Posted

技术标签:

【中文标题】Esky 的 Python 项目结构【英文标题】:Python Project Structure for Esky 【发布时间】:2013-12-09 16:44:01 【问题描述】:

我的问题本质上是,“我应该如何构建我冻结的、已部署的基于 Python 的 Windows 应用程序的文件和文件夹。”为了了解我的情况,这里有一些背景:

我正在为我的工作场所使用 Python 2.7 构建桌面应用程序。它是基于 PyQt 构建的基于 GUI 的应用程序。我正在使用Esky 构建应用程序,这是一个跨平台的冻结和更新框架。 Esky 基本上包装/调用 py2exe、py2app、bb_freeze 或您安装的适合当前平台的任何工具。 Esky 创建了一个如下所示的压缩包:

prog.exe                     - esky bootstrapping executable
appdata/                     - container for all the esky magic
  appname-X.Y.platform/      - specific version of the application
    prog.exe                 - executable(s) as produced by freezer module
    library.zip              - pure-python frozen modules
    pythonXY.dll             - python DLL
    esky-files/              - esky control files
      bootstrap/             - files not yet moved into bootstrapping env
      bootstrap-manifest.txt - list of files expected in bootstrap env
      lockfile.txt           - lockfile to block removal of in-use versions
      ...other deps...
  updates/                   - work area for fetching/unpacking updates

然后可以将这些压缩包放置在 Esky 查找更新的文件服务器上。提供了许多方法来管理更新,包括一个非常简单的 auto_update()。当发生更新时,appname-X.Y.platform 文件夹本质上会被替换为下一个版本的文件夹......因此 myApp.0.1.win32 文件夹被替换为 myApp.0.2.win32 文件夹。

您应该知道的另一个背景方面是我正在将应用程序分发给我的同事,他们没有安装 Python。我不是在分发 Python 包或库,而是在部署一个桌面应用程序(我的同事并不特别关心它是用什么写的,只是它可以工作)。我已经构建了一个 Inno 安装程序,它可以安装应用程序、提供卸载程序和各种快捷方式。因为团队中的每个人基本上都拥有相同的 Windows 7 64 位环境,所以我只为那个平台构建是相当安全的。

所以,回到结构问题。我已经阅读了为项目框架推荐某种格式的指南,例如Learn Python the Hard Way, Exercise 46 或Hitchhiker's Guide to Packaging。但是,这些指南面向 Python 包开发人员,而不是编译应用程序开发人员。

我也遇到了 Esky 的 appname-X.Y.platform 文件夹的问题,因为每次更新程序时它都会更改名称(以反映版本号)。因为我希望开始菜单中的一些快捷方式始终引用文档、更改日志等,所以我让安装程序将其中一些文件放在 appdata 文件夹下。当程序更新时,我有一些代码来检查那些我希望在外部“可见”的文件的较新版本,并将较新版本从 appname-X.Y.platform 文件夹中复制出来,并覆盖 appdata 文件夹中的副本。然后我还需要一种存储持久用户设置的方法,因此程序会生成并使用 appdata\settings 文件夹(否则每次更新都会擦除设置)。

我是否应该在更新后继续让应用程序将新文件推送到 appdata 文件夹?我是否应该构建自己的文档、示例、设置等结构,并让程序在必要时用更新的文件填充这些文件夹?我是否应该尝试改变或更好地利用 Esky 的行为以更好地适应我的使用?也许我应该重新设计我的应用程序,使其既可以作为 Python 包也可以作为最终用户应用程序分发?

这个问题与this one about static files with Esky、this one about Python deployed application structure 和许多关于 Python 项目结构的一般性问题有关,这些问题没有使用 Esky 专门解决。 here 和 here 也提供一些讨论 Esky 的视频。

我正在寻求有关应对这些挑战的“最佳实践”方法的建议。如果这不符合 *** 问题格式,我很乐意尝试改写或缩小问题的重点。

【问题讨论】:

【参考方案1】:

因此,尽管事实上 Esky 的自动更新会在每次更新时更改我的应用程序文件夹的名称,但对于提到的关于在静态位置使文件可用于快捷方式的问题,这是我的解决方案。下面的函数在 QMainWindow 的类定义中。

如果您的应用程序不使用日志记录模块,则可以将日志记录语句替换为打印语句,但我强烈建议使用日志记录,尤其是在部署这样的独立应用程序时。

import os
import shutil
import logging

def push_updated_files(self):
    """
    Manually push auto-updated files from the application folder up to the appdata folder
    This enables shortcuts and other features on the computer to point to these files since the application
      directory name changes with each update.
    """
    logger = logging.getLogger(__name__)

    #Verify whether running script or frozen/deployed application
    if getattr(sys, 'frozen', False):
        logger.info("Verifying Application Integrity...")

        #Files which should by copied to appdata directory to be easily referenced by shortcuts, etc.
        data_files = ['main.ico',
                      'uninstall.ico',
                      'ReadMe.txt',
                      'changelog.txt',
                      'WhatsNew.txt',
                      'copyright.txt',
                      'Documentation.pdf']

        logger.debug("  App Path: 0".format(self._app_path))

        #Get application top directory
        logger.debug("  AppData Directory: 0".format(self._appdata_path))

        #Get application internal file path
        for f in data_files:
            a_file = f
            int_path = os.path.join(self._app_path, a_file)
            logger.debug("  Internal File Path: 0".format(int_path))

            #Get file's creation time
            mtime_int = os.stat(int_path).st_mtime
            logger.debug("  Internal File Modified Time: 0".format(time.ctime(mtime_int)))

            #Get external file path
            ext_path = os.path.join(self._appdata_path, a_file)
            if os.path.exists(ext_path):
                mtime_ext = os.stat(ext_path).st_mtime
                logger.debug("  External File Modified Time: 0".format(time.ctime(mtime_ext)))

                if mtime_int > mtime_ext:
                    logger.debug("  Replacing external file with new file...")
                    try:
                        os.remove(ext_path)
                        shutil.copy(int_path, ext_path)
                    except Exception, e:
                        logger.error("  Failed to replace the external file...", exc_info=True)
                else:
                    logger.debug("  External file is newer than internal file - all is well.")
            else:
                logger.debug("  Copying file to appdata to be externally accessible")
                shutil.copy(int_path, ext_path)

也与此相关,在处理用户设置时(当前只是一个用于填充最近文件列表的 history.txt 文件)我在 appdata 下但在应用程序文件夹之外有一个设置文件夹,这样设置就不会丢失每次更新。我可能会为文档和图标制作类似的文件夹。

【讨论】:

以上是关于Esky 的 Python 项目结构的主要内容,如果未能解决你的问题,请参考以下文章

基于esky实现python应用的自动升级

python的项目结构

使用 Esky 冻结/打包 Cocoa PyObjC Python 应用程序

Python 项目结构

python10 项目目录结构

python 项目目录结构