这是目前我的一个尝试。随着uliweb的项目多起来(为了便于管理和隔离,我们会考虑将不同的功能拆分为不同的项目),需要有时复用其它项目的模块,比如:用户管理等。uliweb项目是可以将一个满足条件的python包(使用 uliweb makeapp appname
)作为外部的app在INSTALLED_APPS中配置的。但是对于使用 uliweb makeproject projectname
生成的项目,却缺省不是一个可以处理的python包,因此直接是无法在另一个项目中导入的,为了实现这一点,我对uliweb进行了优化。
设计安装方法和结构
为了让一个目录是可以导入的,我们一般会使用setup.py来编写一个安装脚本,在其中写上对应要安装的包和目录。一个uliweb的project是什么结构呢?
project/ apps/ settings.ini app1/ app2/ wsgi_handler.py
我们所有的app都是放在apps目录下,所以它是一个重要的目录。比如我们的项目目录是project,因此对于app1这个app来说,我希望可以在安装后使用 import project.app1
来导入。那么我们会发现,希望导入的包名和实际的目录是不对应的。那么如何处理呢?
在写setup.py时,有两个变量很重要:packages和package_dir,它一个是用来表示安装后将出现的包名,可以是多个,另一个是一个配置,用来表示安装包与实际目录的关系。因此,我们可以这样写:
setup(packages=['project'], package_dir={'project':'apps'})
这样就告诉安装程序,在安装时创建包为project,但是它实际的文件是来自当前目录的apps下的内容。
考虑好这一点,我就开始改造uliweb。
改造uliweb
修改makeproject和项目模板
首先是考虑在执行makeproject时自动拷贝一个setup.py文件到新建的项目目录下。它的内容是:
import uliwebfrom uliweb.utils.setup import setupimport apps__doc__ = """doc"""setup(name='{ {=project_name}}', version=apps.__version__, description="Description of your project", package_dir = {'{ {=project_name}}':'apps'}, packages = ['{ {=project_name}}'], include_package_data=True, zip_safe=False,)
可以看到,它里面有一个变量 { {=project_name}}
,表示这个值将用实际的项目路径名进行替換。因此,如果我们执行 uliweb makeproject project
会在当前目录下生成 project 目录,在其中会有一个setup.py文件,makeproject命令会自动将模板中的 { {=project_name}}
替換为 project
。
然后我们还需要在apps下添加一个 __init__.py
,它定义了 __version__
变量值,这个值会用在 setup.py中。用来表示项目的版本。
改造安装脚本
使用setup.py来安装模块时主要有两种方式:install和develop。注意develop是setuptools中特有的。因此需要在你的环境中安装setuptools,同时还建议把pip也安装。最好的方式是使用virtualenv,它会自动安装这两个包。
对于install,它会把对应的文件全部拷贝到python的site-packages下,对于develop,它只会在site-packages下的easy_install.pth中加一个路径,不会真正拷贝。
对于install,因为会拷贝文件,所以不会有太大的问题。只不过如果你的项目经常更新,需要不停地执行install来更新。
因此,很多时候我会使用develop来安装。但是在实际处理过程中发现develop是存在问题的。主要问题就是不会进行实际文件的拷贝。因为我们的包结构和实际的目录结构不同,而拷贝的方式会创建这种结构,但是develop方式无法实现这种包结构。所以有问题。
那么怎么解决呢?我在网上搜了半天,终于找到一种办法,那就是创建: 符号连接 。符号连接是在linux下常用的一种方法。其实这种方法在windows下也是可以用的。有这么几个方法可以使用:
- os.symlink(src, linkname) #创建src为linkname
- os.path.islink(path) #检查path是事是一个link
- os.unlink(path) #删除一个link或文件
但是,在windows平台下,这些方法除了unlink以外,都存在问题。看到网上说在python 3.2之后就可以了。但是我运行的环境主要是python 2.6和2.7。所以需要不升级就能解决的方法。
找来找去,有两个参考:
- 这个讨论(其它的地方也看到有相同的代码)中有一段使用ctypes写的symlink的函数实现,用它可以在windows下创建符号连接。
- 这个项目实现了好几种windows下连接的支持,其中有symlink的支持(具体有哪些请自行研究吧)。这里面实现了上面的几个函数。
那么我的实现是写在 uliweb/utils/setup.py
中的,对原来的setuptools中的develop命令的几个方法进行了特殊处理,如:
-
install_for_development 我在它之后添加了根据setup中的packages和package_dir来创建符号连接的处理。它支持包和子包。因此你可以使用象
setup(packages=['project', 'project.batch'], package_dir={'project':'apps', 'project.batch':'batch'})
这样的方式,将不同的目录处理到一个包结构之下。
- uninstall_link 我在它之后増加了删除symlink的处理。原来的处理只会删除原来创建的文件,而不会删除新创建的连接。
因此,如果你要使用uliweb的develop方式,并且在windows下,你需要安装 ntfslink 这个包。为什么呢?
因为我发现,直接执行unlink去删除一个symlink会报 [Error 5]
的错误。而 ntfslink.symlink 中的unlink方法可以正确删除。但是我又发现,ntfslink.symlink 中的create好象无法创建正确的symlink,所以我还是使用了stackoverflow中的创建symlink的方法。
使用
其实前面已经写了:
- 执行
uliweb makeproject <projectname>
会在生成的 projectname 目录下创建一个setup.py文件。你可以打开修改它的内容以满足你的需要 - 执行
python setup.py install
或python setup.py develop
来安装你的项目,这样就可以在其它的项目中使用了。使用方式是import project.app
,如果想反安装,如果是使用develop安装的话,可以这样python setup.py develop -u
。但是使用install的话,报歉,没戏了。所以这时可以使用pip来做这事:pip uninstall <projectname>
。这也是我建议你两个都安装的原因,这两个工具互补还是不错的。