├── .gitignore ├── Makefile ├── README.rst ├── about.rst ├── command-line-scripts.rst ├── conf.py ├── dependencies.rst ├── everything.rst ├── funniest ├── .gitignore ├── MANIFEST.in ├── README.rst.example ├── funniest │ ├── __init__.py │ ├── command_line.py │ └── tests │ │ ├── __init__.py │ │ ├── test_command_line.py │ │ └── test_joke.py └── setup.py ├── index.rst ├── metadata.rst ├── minimal.rst ├── non-code-files.rst └── testing.rst /.gitignore: -------------------------------------------------------------------------------- 1 | # Rendered documentation. 2 | _build/ 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-packaging.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-packaging.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/python-packaging" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-packaging" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | This tutorial is hosted at http://www.scotttorborg.com/python-packaging/ 2 | 3 | Feedback is welcome, please submit a pull request or email storborg@gmail.com. 4 | 5 | 中文翻译 openmartin `Github `_ 6 | 7 | 中文版文档地址 http://python-packaging-zh.readthedocs.org/zh_CN/latest/index.html -------------------------------------------------------------------------------- /about.rst: -------------------------------------------------------------------------------- 1 | 关于这篇教程 2 | ================================== 3 | 4 | Scott Torborg - `storborg@gmail.com `_. 5 | 6 | I wrote this tutorial in an attempt to improve the state of Python packages at large. Tools like `virtualenv `_ and `pip `_, as well as improvements to setuptools, have made the Python package ecosystem a delight to work in. 7 | 8 | However, I frequently run across packages I want to use that don't interoperate correctly with others, or find myself in situations where I'm not sure exactly how to structure things. This is an attempt to fix that. 9 | 10 | This documentation is written in reStructuredText and built with `Sphinx `_. The source is open and hosted at http://github.com/storborg/python-packaging. 11 | 12 | To build the HTML version, just do this in a clone of the repo:: 13 | 14 | $ make html 15 | 16 | Contributions and fixes are welcome, encouraged, and credited. Please submit a pull request on GitHub or email me. 17 | 18 | 中文翻译 openmartin `Github `_ 19 | 20 | 中文版文档地址 http://python-packaging-zh.readthedocs.org/zh_CN/latest/index.html -------------------------------------------------------------------------------- /command-line-scripts.rst: -------------------------------------------------------------------------------- 1 | 命令行脚本 2 | ==================== 3 | 4 | 很多Python包都有命令行工具. 借助setuptools/PyPI你可以非常方便地添加有用的命令行工具到你发布包当中, 或者你想单纯发布使用Python编写 5 | 的命令行工具. 6 | 7 | 举个例子, 我们添加一个 ``funniest-joke`` 的可执行命令. 8 | 9 | 在 ``setuptools.setup()`` 中有两种方法 ``scripts`` 参数或是 ``console_scripts`` 入口. 10 | 11 | ``scripts`` 参数 12 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 13 | 14 | 第一种方法是把你的命令写在一个单独的文件中:: 15 | 16 | funniest/ 17 | funniest/ 18 | __init__.py 19 | ... 20 | setup.py 21 | bin/ 22 | funniest-joke 23 | ... 24 | 25 | ``bin/funniest-joke`` 如下:: 26 | 27 | #!/usr/bin/env python 28 | 29 | import funniest 30 | print funniest.joke() 31 | 32 | 在``setup()`` 添加:: 33 | 34 | setup( 35 | ... 36 | scripts=['bin/funniest-joke'], 37 | ... 38 | ) 39 | 40 | 当我们安装这个包的时候, setuptools会把你的脚本复制到PATH路径下:: 41 | 42 | $ funniest-joke 43 | 44 | 使用这种方法的好处是可以使用非Python的语言的编写, ``funniset-joke`` 可以是一个shell脚本或者其他的都可以. 45 | 46 | 47 | ``console_scripts`` 入口 48 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 49 | 50 | 第二种方法是通过'entry point'. setuptools 允许模块注册'entry points', 这样可以使用其他包的功能. ``console_scripts`` 也是一个'entry points'. 51 | 52 | ``console_scripts`` 允许Python的 *functions* (不是文件) 直接被注册成一个命令行工具. 53 | 54 | 下面, 我们将添加一个新文件提供命令行工具:: 55 | 56 | funniest/ 57 | funniest/ 58 | __init__.py 59 | command_line.py 60 | ... 61 | setup.py 62 | ... 63 | 64 | ``command_line.py`` 仅仅只提供命令行工具(这样组织代码更方便):: 65 | 66 | import funniest 67 | 68 | def main(): 69 | print funniest.joke() 70 | 71 | 你可以测试一下, 就像这样:: 72 | 73 | $ python 74 | >>> import funniest.command_line 75 | >>> funniest.command_line.main() 76 | ... 77 | 78 | 在setup.py 中 注册 ``main()`` :: 79 | 80 | setup( 81 | ... 82 | entry_points = { 83 | 'console_scripts': ['funniest-joke=funniest.command_line:main'], 84 | } 85 | ... 86 | ) 87 | 88 | 我们安装了这个包之后, 我们就可以直接使用 ``funniest-joke`` 命令. 因为setuptools 会自动生成一个脚本, 包括导入模块, 然后在调用注册的函数. 89 | -------------------------------------------------------------------------------- /conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # python-packaging documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Nov 17 10:03:55 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.todo'] 29 | 30 | todo_include_todos = True 31 | 32 | # Add any paths that contain templates here, relative to this directory. 33 | templates_path = ['_templates'] 34 | 35 | # The suffix of source filenames. 36 | source_suffix = '.rst' 37 | 38 | # The encoding of source files. 39 | #source_encoding = 'utf-8-sig' 40 | 41 | # The master toctree document. 42 | master_doc = 'index' 43 | 44 | # General information about the project. 45 | project = u'python-packaging' 46 | copyright = u'2015 openmartin; 2012, Scott Torborg' 47 | 48 | # The version info for the project you're documenting, acts as replacement for 49 | # |version| and |release|, also used in various other places throughout the 50 | # built documents. 51 | # 52 | # The short X.Y version. 53 | version = '0.1' 54 | # The full version, including alpha/beta/rc tags. 55 | release = '0.1' 56 | 57 | # The language for content autogenerated by Sphinx. Refer to documentation 58 | # for a list of supported languages. 59 | #language = None 60 | 61 | # There are two options for replacing |today|: either, you set today to some 62 | # non-false value, then it is used: 63 | #today = '' 64 | # Else, today_fmt is used as the format for a strftime call. 65 | #today_fmt = '%B %d, %Y' 66 | 67 | # List of patterns, relative to source directory, that match files and 68 | # directories to ignore when looking for source files. 69 | exclude_patterns = ['_build'] 70 | 71 | # The reST default role (used for this markup: `text`) to use for all documents. 72 | #default_role = None 73 | 74 | # If true, '()' will be appended to :func: etc. cross-reference text. 75 | #add_function_parentheses = True 76 | 77 | # If true, the current module name will be prepended to all description 78 | # unit titles (such as .. function::). 79 | #add_module_names = True 80 | 81 | # If true, sectionauthor and moduleauthor directives will be shown in the 82 | # output. They are ignored by default. 83 | #show_authors = False 84 | 85 | # The name of the Pygments (syntax highlighting) style to use. 86 | pygments_style = 'sphinx' 87 | 88 | # A list of ignored prefixes for module index sorting. 89 | #modindex_common_prefix = [] 90 | 91 | 92 | # -- Options for HTML output --------------------------------------------------- 93 | 94 | # The theme to use for HTML and HTML Help pages. See the documentation for 95 | # a list of builtin themes. 96 | html_theme = 'default' 97 | 98 | # Theme options are theme-specific and customize the look and feel of a theme 99 | # further. For a list of options available for each theme, see the 100 | # documentation. 101 | #html_theme_options = {} 102 | 103 | # Add any paths that contain custom themes here, relative to this directory. 104 | #html_theme_path = [] 105 | 106 | # The name for this set of Sphinx documents. If None, it defaults to 107 | # " v documentation". 108 | html_title = 'Python Packaging Tutorial' 109 | 110 | # A shorter title for the navigation bar. Default is the same as html_title. 111 | html_short_title = 'How To Package Your Python Code' 112 | 113 | # The name of an image file (relative to this directory) to place at the top 114 | # of the sidebar. 115 | #html_logo = None 116 | 117 | # The name of an image file (within the static path) to use as favicon of the 118 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 119 | # pixels large. 120 | #html_favicon = None 121 | 122 | # Add any paths that contain custom static files (such as style sheets) here, 123 | # relative to this directory. They are copied after the builtin static files, 124 | # so a file named "default.css" will overwrite the builtin "default.css". 125 | html_static_path = ['_static'] 126 | 127 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 128 | # using the given strftime format. 129 | #html_last_updated_fmt = '%b %d, %Y' 130 | 131 | # If true, SmartyPants will be used to convert quotes and dashes to 132 | # typographically correct entities. 133 | #html_use_smartypants = True 134 | 135 | # Custom sidebar templates, maps document names to template names. 136 | #html_sidebars = {} 137 | 138 | # Additional templates that should be rendered to pages, maps page names to 139 | # template names. 140 | #html_additional_pages = {} 141 | 142 | # If false, no module index is generated. 143 | #html_domain_indices = True 144 | 145 | # If false, no index is generated. 146 | html_use_index = False 147 | 148 | # If true, the index is split into individual pages for each letter. 149 | #html_split_index = False 150 | 151 | # If true, links to the reST sources are added to the pages. 152 | #html_show_sourcelink = True 153 | 154 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 155 | #html_show_sphinx = True 156 | 157 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 158 | #html_show_copyright = True 159 | 160 | # If true, an OpenSearch description file will be output, and all pages will 161 | # contain a tag referring to it. The value of this option must be the 162 | # base URL from which the finished HTML is served. 163 | #html_use_opensearch = '' 164 | 165 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 166 | #html_file_suffix = None 167 | 168 | # Output file base name for HTML help builder. 169 | htmlhelp_basename = 'python-packagingdoc' 170 | 171 | 172 | # -- Options for LaTeX output -------------------------------------------------- 173 | 174 | latex_elements = { 175 | # The paper size ('letterpaper' or 'a4paper'). 176 | #'papersize': 'letterpaper', 177 | 178 | # The font size ('10pt', '11pt' or '12pt'). 179 | #'pointsize': '10pt', 180 | 181 | # Additional stuff for the LaTeX preamble. 182 | #'preamble': '', 183 | } 184 | 185 | # Grouping the document tree into LaTeX files. List of tuples 186 | # (source start file, target name, title, author, documentclass [howto/manual]). 187 | latex_documents = [ 188 | ('index', 'python-packaging.tex', u'python-packaging Documentation', 189 | u'Scott Torborg', 'manual'), 190 | ] 191 | 192 | # The name of an image file (relative to this directory) to place at the top of 193 | # the title page. 194 | #latex_logo = None 195 | 196 | # For "manual" documents, if this is true, then toplevel headings are parts, 197 | # not chapters. 198 | #latex_use_parts = False 199 | 200 | # If true, show page references after internal links. 201 | #latex_show_pagerefs = False 202 | 203 | # If true, show URL addresses after external links. 204 | #latex_show_urls = False 205 | 206 | # Documents to append as an appendix to all manuals. 207 | #latex_appendices = [] 208 | 209 | # If false, no module index is generated. 210 | #latex_domain_indices = True 211 | 212 | 213 | # -- Options for manual page output -------------------------------------------- 214 | 215 | # One entry per manual page. List of tuples 216 | # (source start file, name, description, authors, manual section). 217 | man_pages = [ 218 | ('index', 'python-packaging', u'python-packaging Documentation', 219 | [u'Scott Torborg'], 1) 220 | ] 221 | 222 | # If true, show URL addresses after external links. 223 | #man_show_urls = False 224 | 225 | 226 | # -- Options for Texinfo output ------------------------------------------------ 227 | 228 | # Grouping the document tree into Texinfo files. List of tuples 229 | # (source start file, target name, title, author, 230 | # dir menu entry, description, category) 231 | texinfo_documents = [ 232 | ('index', 'python-packaging', u'python-packaging Documentation', 233 | u'Scott Torborg', 'python-packaging', 'One line description of project.', 234 | 'Miscellaneous'), 235 | ] 236 | 237 | # Documents to append as an appendix to all manuals. 238 | #texinfo_appendices = [] 239 | 240 | # If false, no module index is generated. 241 | #texinfo_domain_indices = True 242 | 243 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 244 | #texinfo_show_urls = 'footnote' 245 | -------------------------------------------------------------------------------- /dependencies.rst: -------------------------------------------------------------------------------- 1 | 依赖关系 2 | ======================= 3 | 4 | 如果你使用Python, 很自然的你会使用其他人在PyPI或者其他地方公开发布的包 5 | 6 | setuptools给我们提供了很方便的工具来说明依赖关系, 而且在安装我们的包的时候会自动安装依赖包. 7 | 8 | 我们可以给 **funniest** joke 添加一些格式, 使用 `Markdown `_. 9 | 10 | ``__init__.py`` :: 11 | 12 | from markdown import markdown 13 | 14 | def joke(): 15 | return markdown(u'How do you tell HTML from HTML5?' 16 | u'Try it out in **Internet Explorer**.' 17 | u'Does it work?' 18 | u'No?' 19 | u'It\'s HTML5.') 20 | 21 | 22 | 现在我们的包依赖 ``markdown`` 这个包. 我们需要在 ``setup.py`` 中添加 ``install_requires`` 参数:: 23 | 24 | from setuptools import setup 25 | 26 | setup(name='funniest', 27 | version='0.1', 28 | description='The funniest joke in the world', 29 | url='http://github.com/storborg/funniest', 30 | author='Flying Circus', 31 | author_email='flyingcircus@example.com', 32 | license='MIT', 33 | packages=['funniest'], 34 | install_requires=[ 35 | 'markdown', 36 | ], 37 | zip_safe=False) 38 | 39 | 为了测试是否可行,我们可以试一试 ``python setup.py develop`` :: 40 | 41 | $ python setup.py develop 42 | running develop 43 | running egg_info 44 | writing requirements to funniest.egg-info/requires.txt 45 | writing funniest.egg-info/PKG-INFO 46 | writing top-level names to funniest.egg-info/top_level.txt 47 | writing dependency_links to funniest.egg-info/dependency_links.txt 48 | reading manifest file 'funniest.egg-info/SOURCES.txt' 49 | writing manifest file 'funniest.egg-info/SOURCES.txt' 50 | running build_ext 51 | Creating /.../site-packages/funniest.egg-link (link to .) 52 | funniest 0.1 is already the active version in easy-install.pth 53 | 54 | Installed /Users/scott/local/funniest 55 | Processing dependencies for funniest==0.1 56 | Searching for Markdown==2.1.1 57 | Best match: Markdown 2.1.1 58 | Adding Markdown 2.1.1 to easy-install.pth file 59 | 60 | Using /.../site-packages 61 | Finished processing dependencies for funniest==0.1 62 | 63 | 当我们安装funniest包的时候, ``pip install funniest`` 也会同时安装 ``markdown`` . 64 | 65 | 66 | 不在PyPI中的包 67 | ~~~~~~~~~~~~~~~~~~~~ 68 | 69 | 有时候, 你需要一些按照setuptools格式组织的安装包, 但是它们没有在PyPI发布. 在这种情况下, 你可以在 ``dependency_links`` 70 | 中填入下载的URL, 可能需要在URL中加一些其他信息, setuptools将根据URL找到和安装这些依赖包. 71 | 72 | 举个例子, Github上的包可以按照下面的格式填写URL:: 73 | 74 | setup( 75 | ... 76 | dependency_links=['http://github.com/user/repo/tarball/master#egg=package-1.0'] 77 | ... 78 | ) 79 | -------------------------------------------------------------------------------- /everything.rst: -------------------------------------------------------------------------------- 1 | 把所有的东西组合起来 2 | ======================= 3 | 4 | 最后整个Python包, 看起来像这样:: 5 | 6 | funniest/ 7 | funniest/ 8 | __init__.py 9 | command_line.py 10 | tests/ 11 | __init__.py 12 | test_joke.py 13 | test_command_line.py 14 | MANIFEST.in 15 | README.rst 16 | setup.py 17 | .gitignore 18 | 19 | 每个文件如下: 20 | 21 | **funniest/__init__.py** 22 | 23 | .. literalinclude:: funniest/funniest/__init__.py 24 | 25 | **funniest/command_line.py** 26 | 27 | .. literalinclude:: funniest/funniest/command_line.py 28 | 29 | **funniest/tests/__init__.py** 30 | 31 | (empty) 32 | 33 | **funniest/tests/test_joke.py** 34 | 35 | .. literalinclude:: funniest/funniest/tests/test_joke.py 36 | 37 | **funniest/tests/test_command_line.py** 38 | 39 | .. literalinclude:: funniest/funniest/tests/test_command_line.py 40 | 41 | **MANIFEST.in** 42 | 43 | .. literalinclude:: funniest/MANIFEST.in 44 | 45 | **README.rst** 46 | 47 | .. literalinclude:: funniest/README.rst.example 48 | 49 | **setup.py** 50 | 51 | .. literalinclude:: funniest/setup.py 52 | 53 | **.gitignore** 54 | 55 | .. literalinclude:: funniest/.gitignore 56 | -------------------------------------------------------------------------------- /funniest/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled python modules. 2 | *.pyc 3 | 4 | # Setuptools distribution folder. 5 | /dist/ 6 | 7 | # Python egg metadata, regenerated from source files by setuptools. 8 | /*.egg-info 9 | /*.egg 10 | -------------------------------------------------------------------------------- /funniest/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | -------------------------------------------------------------------------------- /funniest/README.rst.example: -------------------------------------------------------------------------------- 1 | Funniest 2 | -------- 3 | 4 | To use (with caution), simply do:: 5 | 6 | >>> import funniest 7 | >>> print funniest.joke() 8 | 9 | -------------------------------------------------------------------------------- /funniest/funniest/__init__.py: -------------------------------------------------------------------------------- 1 | from markdown import markdown 2 | 3 | def joke(): 4 | return markdown(u'How do you tell HTML from HTML5?' 5 | u'Try it out in **Internet Explorer**.' 6 | u'Does it work?' 7 | u'No?' 8 | u'It\'s HTML5.') 9 | -------------------------------------------------------------------------------- /funniest/funniest/command_line.py: -------------------------------------------------------------------------------- 1 | from . import joke 2 | 3 | 4 | def main(): 5 | print joke() 6 | -------------------------------------------------------------------------------- /funniest/funniest/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmartin/python-packaging/ad22e75b25acf76ad4181827f1d463f21b7fdd3e/funniest/funniest/tests/__init__.py -------------------------------------------------------------------------------- /funniest/funniest/tests/test_command_line.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from funniest.cmd import main 4 | 5 | 6 | class TestCmd(TestCase): 7 | def test_basic(self): 8 | main() 9 | -------------------------------------------------------------------------------- /funniest/funniest/tests/test_joke.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import funniest 4 | 5 | 6 | class TestJoke(TestCase): 7 | def test_is_string(self): 8 | s = funniest.joke() 9 | self.assertTrue(isinstance(s, basestring)) 10 | -------------------------------------------------------------------------------- /funniest/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | def readme(): 4 | with open('README.rst.example') as f: 5 | return f.read() 6 | 7 | setup(name='funniest', 8 | version='0.1', 9 | description='The funniest joke in the world', 10 | long_description=readme(), 11 | classifiers=[ 12 | 'Development Status :: 3 - Alpha', 13 | 'License :: OSI Approved :: MIT License', 14 | 'Programming Language :: Python :: 2.7', 15 | 'Topic :: Text Processing :: Linguistic', 16 | ], 17 | keywords='funniest joke comedy flying circus', 18 | url='http://github.com/storborg/funniest', 19 | author='Flying Circus', 20 | author_email='flyingcircus@example.com', 21 | license='MIT', 22 | packages=['funniest'], 23 | install_requires=[ 24 | 'markdown', 25 | ], 26 | test_suite='nose.collector', 27 | tests_require=['nose', 'nose-cover3'], 28 | entry_points={ 29 | 'console_scripts': ['funniest-joke=funniest.command_line:main'], 30 | }, 31 | include_package_data=True, 32 | zip_safe=False) 33 | -------------------------------------------------------------------------------- /index.rst: -------------------------------------------------------------------------------- 1 | 如何打包你的Python代码 2 | =============================== 3 | 4 | 这个教程目标是为了更好地描述打包的过程,让大家都能学会如何打包Python代码。 5 | 但是打包并非 *仅仅只有* 一种方式,这个教程仅仅只描述了一种可行的打包方式。 6 | 7 | 打包之后,你的代码有如下好处: 8 | 9 | * 可以使用 ``pip`` or ``easy_install`` 安装. 10 | * 可以做为其他包的依赖关系. 11 | * 其他用户更加方便地使用和测试你的代码. 12 | * 其他用户可以更方便的理解你的代码,因为你的代码是按照打包需要的格式来组织的. 13 | * 更加方便添加和分发文档. 14 | 15 | 我们一步一步地,制作一个简单的python包 **funniest** ,你就会发现我所说非虚。 16 | 17 | .. toctree:: 18 | :maxdepth: 1 19 | 20 | minimal 21 | dependencies 22 | metadata 23 | testing 24 | command-line-scripts 25 | non-code-files 26 | everything 27 | about 28 | 29 | .. note:: 30 | 31 | 目前,这份教程仅仅针对Python 2.x,可能在Python 3.x 上并不适用 32 | 33 | .. seealso:: 34 | 35 | `Setuptools Documentation `_ 36 | setuptools documentation. 37 | 38 | `Python Packaging User Guide `_ 39 | "Python Packaging User Guide" (PyPUG) 目标在于为Python包如何打包和安装,提供权威的指南. 40 | -------------------------------------------------------------------------------- /metadata.rst: -------------------------------------------------------------------------------- 1 | 组织更好地元数据 2 | ======================= 3 | 4 | ``setuptools.setup()`` 函数接受很多参数, 需要你填写关于你的包的元数据. 5 | 完整的填写这些参数可以让人们更加容易找到和判断你的包是干什么的.:: 6 | 7 | from setuptools import setup 8 | 9 | setup(name='funniest', 10 | version='0.1', 11 | description='The funniest joke in the world', 12 | long_description='Really, the funniest around.', 13 | classifiers=[ 14 | 'Development Status :: 3 - Alpha', 15 | 'License :: OSI Approved :: MIT License', 16 | 'Programming Language :: Python :: 2.7', 17 | 'Topic :: Text Processing :: Linguistic', 18 | ], 19 | keywords='funniest joke comedy flying circus', 20 | url='http://github.com/storborg/funniest', 21 | author='Flying Circus', 22 | author_email='flyingcircus@example.com', 23 | license='MIT', 24 | packages=['funniest'], 25 | install_requires=[ 26 | 'markdown', 27 | ], 28 | include_package_data=True, 29 | zip_safe=False) 30 | 31 | ``classifiers`` 参数, 完整的分类列表在这里 http://pypi.python.org/pypi?%3Aaction=list_classifiers. 32 | 33 | 34 | README / Long Description 35 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 36 | 37 | 你可能希望添加一个README说明文件到你的包中, 而且也可以满足PyPI ``long_description`` 的规范. 如果 38 | 这个文件使用reStructuredText语法, 将会有更丰富的格式. 39 | 40 | **funniest**, 添加两个文件:: 41 | 42 | funniest/ 43 | funniest/ 44 | __init__.py 45 | setup.py 46 | README.rst 47 | MANIFEST.in 48 | 49 | ``README.rst`` :: 50 | 51 | Funniest 52 | -------- 53 | 54 | To use (with caution), simply do:: 55 | 56 | >>> import funniest 57 | >>> print funniest.joke() 58 | 59 | ``MANIFEST.in`` :: 60 | 61 | include README.rst 62 | 63 | 这个文件是用来告诉setuptools打包的时候把README.rst添加进去, 否则的话只会打包包含Python代码的文件. 64 | 65 | 接下来修改 ``setup.py`` :: 66 | 67 | from setuptools import setup 68 | 69 | def readme(): 70 | with open('README.rst') as f: 71 | return f.read() 72 | 73 | setup(name='funniest', 74 | version='0.1', 75 | description='The funniest joke in the world', 76 | long_description=readme(), 77 | classifiers=[ 78 | 'Development Status :: 3 - Alpha', 79 | 'License :: OSI Approved :: MIT License', 80 | 'Programming Language :: Python :: 2.7', 81 | 'Topic :: Text Processing :: Linguistic', 82 | ], 83 | keywords='funniest joke comedy flying circus', 84 | url='http://github.com/storborg/funniest', 85 | author='Flying Circus', 86 | author_email='flyingcircus@example.com', 87 | license='MIT', 88 | packages=['funniest'], 89 | install_requires=[ 90 | 'markdown', 91 | ], 92 | include_package_data=True, 93 | zip_safe=False) 94 | 95 | 当你的代码存放在GitHub或者是BitBucket, README.rst 会自动成为项目的主页. -------------------------------------------------------------------------------- /minimal.rst: -------------------------------------------------------------------------------- 1 | 最小的结构 2 | ================= 3 | 4 | 让我们来看一小段代码:: 5 | 6 | def joke(): 7 | return (u'How do you tell HTML from HTML5?' 8 | u'Try it out in Internet Explorer.' 9 | u'Does it work?' 10 | u'No?' 11 | u'It\'s HTML5.') 12 | 13 | 这小段代码仅仅是为了展示如何打包和分发Python代码, 没有实际的用途。 14 | 15 | 16 | 选择一个包名 17 | ~~~~~~~~~~~~~~ 18 | 19 | Python 模块或者包名应该遵守以下的规则: 20 | 21 | * 全小写 22 | * 不要和pypi上已有的包名重复,即使你不想公开发布你的包,因为你的包可能作为其他包的依赖包 23 | * 使用下划线分隔单词或者什么都不用(不要使用连字符) 24 | 25 | 现在把我们的函数变成一个Python module **funniest** 26 | 27 | 28 | 开始工作 29 | ~~~~~~~~~~~~~~~~~~~~~~~~ 30 | 31 | 目录结构 **funniest** 如下:: 32 | 33 | funniest/ 34 | funniest/ 35 | __init__.py 36 | setup.py 37 | 38 | 最外层的目录是我们版本管理工具的根目录, 例如 ``funniest.git`` . 子目录也叫 ``funniest`` , 代表Python module. 39 | 40 | 为了更好理解, 我们把函数 ``joke()`` 放到 ``__init__.py`` 中:: 41 | 42 | def joke(): 43 | return (u'How do you tell HTML from HTML5?' 44 | u'Try it out in Internet Explorer.' 45 | u'Does it work?' 46 | u'No?' 47 | u'It\'s HTML5.') 48 | 49 | 最主要的setup配置文件是 ``setup.py`` , 应该包含一行代码调用 ``setuptools.setup()`` ,就像下面这样:: 50 | 51 | from setuptools import setup 52 | 53 | setup(name='funniest', 54 | version='0.1', 55 | description='The funniest joke in the world', 56 | url='http://github.com/storborg/funniest', 57 | author='Flying Circus', 58 | author_email='flyingcircus@example.com', 59 | license='MIT', 60 | packages=['funniest'], 61 | zip_safe=False) 62 | 63 | 现在我们可以在本地安装这个python包:: 64 | 65 | $ python setup.py install 66 | 67 | 我们也可以使用开发模式安装这个包, 每次修改代码之后不用重新安装, 立即可用最新的代码.:: 68 | 69 | $ python setup.py develop 70 | 71 | 不管用哪种方式,安装之后就可以在python中使用这个包:: 72 | 73 | >>> import funniest 74 | >>> print funniest.joke() 75 | 76 | 77 | 在PyPI上发布 78 | ~~~~~~~~~~~~~~~~~~ 79 | 80 | 脚本 ``setup.py`` 也是在PyPI注册和上传源码包的入口. 81 | 82 | 第一步注册这个包(包括注册包名,上传元数据,创建pypi.python.org的页面):: 83 | 84 | $ python setup.py register 85 | 86 | 如果从未在PyPI上发布过东西, 你需要创建一个账号, 命令行向导会一步一步告诉你怎么做. 87 | 88 | 注册之后你可以在PyPI看到这个包的页面 **funniest** : 89 | 90 | http://pypi.python.org/pypi/funniest/0.1 91 | 92 | 尽管用户可以根据URL链接找到你的git仓库, 但是为了使用方便我们需要上传一个源码包. 用户不需要clone你的git仓库,而且可以使用安装工具 93 | 自动化安装和搜索依赖关系. 94 | 95 | 第二步创建一个源码包:: 96 | 97 | $ python setup.py sdist 98 | 99 | 这一步会在你的顶层目录下创建 ``dist/funniest-0.1.tar.gz`` . 如果你有时间, 可以把这个文件拷贝到另一台主机上, 解压然后安装, 100 | 测试一下安装包. 101 | 102 | 第三步上传到PyPI:: 103 | 104 | $ python setup.py sdist upload 105 | 106 | 你可以把这几步结合起来, 更新元数据, 发布新版本, 一步就完成:: 107 | 108 | $ python setup.py register sdist upload 109 | 110 | 想要查看setup.py更多的功能可以看看帮助:: 111 | 112 | $ python setup.py --help-commands 113 | 114 | 115 | 安装这个包 116 | ~~~~~~~~~~~~~~~~~~~~~~ 117 | 118 | 上面的步骤完成之后, 其他用户可以直接用 ``easy_install`` 安装:: 119 | 120 | easy_install funniest 121 | 122 | 或者使用 ``pip`` :: 123 | 124 | $ pip install funniest 125 | 126 | 如果这包作为其他包的依赖包, 它将被自动安装(我们在后面会提到如何配置) 127 | 128 | 129 | 添加其他文件 130 | ~~~~~~~~~~~~~~~~~~~~~~~ 131 | 132 | 大部分时间我们的代码分散在多个文件当中, 133 | 134 | 举个例子, 我们把函数移动到一个新的文件中 ``text`` , 现在我们的目录结构是这样子的:: 135 | 136 | funniest/ 137 | funniest/ 138 | __init__.py 139 | text.py 140 | setup.py 141 | 142 | ``__init__.py`` :: 143 | 144 | from .text import joke 145 | 146 | ``text.py`` :: 147 | 148 | def joke(): 149 | return (u'How do you tell HTML from HTML5?' 150 | u'Try it out in Internet Explorer.' 151 | u'Does it work?' 152 | u'No?' 153 | u'It\'s HTML5.') 154 | 155 | 所有的代码应该都在 ``funniest/funniest/`` 目录下. 156 | 157 | 158 | 忽略的文件 (.gitignore, etc) 159 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 160 | 161 | 我们可能需要一个 ``.gitignore`` 或者是其他代码管理工具类似的文件, 因为创建包的过程中会产生一下中间文件, 我们并不想提交到代码仓库当中. 162 | 163 | 下面是一个 ``.gitignore`` 的例子:: 164 | 165 | # Compiled python modules. 166 | *.pyc 167 | 168 | # Setuptools distribution folder. 169 | /dist/ 170 | 171 | # Python egg metadata, regenerated from source files by setuptools. 172 | /*.egg-info 173 | 174 | 175 | 大功告成 176 | ~~~~~~~~~~~~~~~~~~~ 177 | 178 | 上面讲的结构已经包含了创建一个包的所有步骤. 如果所有的Python工具和库都遵循同样的规则来打包, 世界会更加美好. 179 | 180 | **客官别急** 下面还有更多内容, 因为大部分的包还需要命令行脚本, 文档, 测试,分析工具等等, 请看下一篇. 181 | -------------------------------------------------------------------------------- /non-code-files.rst: -------------------------------------------------------------------------------- 1 | 添加非代码的其他文件 2 | ===================== 3 | 4 | 通常我们的包都需要一下不是python代码的文件, 例如图片, 数据, 文档等等. 为了让setuptools正确处理这些文件, 我们需要特别定义一下这些文件. 5 | 6 | 我们需要在 ``MANIFEST.in`` 中指定这些文件, ``MANIFEST.in`` 提供了一个文件清单, 使用相对路径或是绝对路径指出打包时需要包含的特殊文件.:: 7 | 8 | include README.rst 9 | include docs/*.txt 10 | include funniest/data.json 11 | 12 | 为了让在安装的时候这些特殊文件能被复制到 ``site-packages`` 下的文件夹中, 需要在 ``setup()`` 添加参数 ``include_package_data=True`` . 13 | 14 | .. note:: 15 | 16 | 添加在安装包中的文件(例如计算需要的数据文件)应该放在Python模块的文件夹里面(例如 ``funniest/funniest/data.json`` ). 17 | 在加载这些文件的时候, 使用相对路径再加上 ``__file__`` 变量. -------------------------------------------------------------------------------- /testing.rst: -------------------------------------------------------------------------------- 1 | 添加测试代码 2 | ================== 3 | 4 | **funniest** 需要一些测试工作. 这些代码都应该放在 ``funniest.`` 子模块的目录下. 5 | 这样的结构,这些测试代码既可以导入, 又不会污染全局的命名空间.:: 6 | 7 | funniest/ 8 | funniest/ 9 | __init__.py 10 | tests/ 11 | __init__.py 12 | test_joke.py 13 | setup.py 14 | ... 15 | 16 | ``test_joke.py`` 是我们第一个测试文件. 17 | 虽然现在有一些小题大做, 但是这是为了演示代码是如何组织的, 所以我们创建了 ``unittest.TestCase`` 的一个子类:: 18 | 19 | from unittest import TestCase 20 | 21 | import funniest 22 | 23 | class TestJoke(TestCase): 24 | def test_is_string(self): 25 | s = funniest.joke() 26 | self.assertTrue(isinstance(s, basestring)) 27 | 28 | 运行这些测试用例最好的方式是使用 `Nose `_ (特别是你不知道用什么的时候) 29 | 30 | $ pip install nose 31 | $ nosetests 32 | 33 | 为了把测试工作集成到 ``setup.py`` 中, 我们需要添加一些参数, 这些参数会确保运行测试用例的时候Nose会被安装.:: 34 | 35 | setup( 36 | ... 37 | test_suite='nose.collector', 38 | tests_require=['nose'], 39 | ) 40 | 41 | 然后, 我们就可以这样运行测试:: 42 | 43 | $ python setup.py test 44 | 45 | setuptools 将会安装nose和运行测试用例. --------------------------------------------------------------------------------