├── MANIFEST.in
├── makefile
├── .gitignore
├── setup.py
├── README.md
├── README.rst
├── doc
├── index.rst
├── Makefile
└── conf.py
└── distribute_setup.py
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include README.rst
3 | include distribute_setup.py
4 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | README.rst: README.md
2 | pandoc -f markdown -t rst README.md > README.rst
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 |
3 | # C extensions
4 | *.so
5 |
6 | # Packages
7 | *.egg
8 | *.egg-info
9 | dist
10 | build
11 | eggs
12 | parts
13 | bin
14 | var
15 | sdist
16 | develop-eggs
17 | .installed.cfg
18 | lib
19 | lib64
20 |
21 | # Installer logs
22 | pip-log.txt
23 |
24 | # Unit test / coverage reports
25 | .coverage
26 | .tox
27 | nosetests.xml
28 |
29 | #Translations
30 | *.mo
31 |
32 | # Sphinx documentation build
33 | doc/.build
34 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from __future__ import with_statement
2 |
3 | from distribute_setup import use_setuptools
4 | use_setuptools()
5 |
6 | from setuptools import setup, find_packages
7 |
8 |
9 | with open('README.rst') as readme:
10 | long_description = readme.read()
11 |
12 |
13 | setup(
14 | name='Flask-Resource',
15 | version='0.0.1',
16 | author='Zachary Voase',
17 | author_email='z@zacharyvoase.com',
18 | url='https://github.com/zacharyvoase/flask-resource',
19 | description="Build resource-oriented Web apps with Flask.",
20 | long_description=long_description,
21 | packages=find_packages(exclude=('tests',)),
22 | )
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flask-Resource
2 |
3 | Flask-Resource is a [Flask][] extension for writing resource-oriented Web
4 | applications.
5 |
6 | [flask]: http://flask.pocoo.org/
7 |
8 |
9 | ## Installation
10 |
11 | pip install flask-resource
12 |
13 |
14 | ## License
15 |
16 |
17 | Copyright (C) 2012 Zachary Voase
18 |
19 | Permission is hereby granted, free of charge, to any person obtaining a copy of
20 | this software and associated documentation files (the "Software"), to deal in
21 | the Software without restriction, including without limitation the rights to
22 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
23 | of the Software, and to permit persons to whom the Software is furnished to do
24 | so, subject to the following conditions:
25 |
26 | The above copyright notice and this permission notice shall be included in all
27 | copies or substantial portions of the Software.
28 |
29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35 | SOFTWARE.
36 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | flask-resource
2 | ==============
3 |
4 | flask-resource is a `Flask `_ extension for
5 | writing resource-oriented Web applications.
6 |
7 | Installation
8 | ------------
9 |
10 | ::
11 |
12 | pip install flask-resource
13 |
14 | License
15 | -------
16 |
17 | Copyright (C) 2012 Zachary Voase
18 |
19 | Permission is hereby granted, free of charge, to any person obtaining a
20 | copy of this software and associated documentation files (the
21 | "Software"), to deal in the Software without restriction, including
22 | without limitation the rights to use, copy, modify, merge, publish,
23 | distribute, sublicense, and/or sell copies of the Software, and to
24 | permit persons to whom the Software is furnished to do so, subject to
25 | the following conditions:
26 |
27 | The above copyright notice and this permission notice shall be included
28 | in all copies or substantial portions of the Software.
29 |
30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
31 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
33 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
34 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
35 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
36 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 |
--------------------------------------------------------------------------------
/doc/index.rst:
--------------------------------------------------------------------------------
1 | Flask-Resource
2 | ==============
3 |
4 | Flask-Resource is a Flask extension which makes it easy to create
5 | standards-compliant, resource-oriented Web applications. It adopts a strong
6 | **convention-over-configuration** approach, which means you get to write less
7 | code overall.
8 |
9 | These are a few features:
10 |
11 |
12 | Conventions
13 | -----------
14 |
15 | `Cool URIs don't change `_. Some
16 | conventions have arisen on the Web for creating long-lasting, meaningful URLs.
17 | When you use a framework which knows about these conventions, you don't have to
18 | spend time crafting well-formed URIs—it's done for you.
19 |
20 | Likewise, when you model your Web application around resources, some decisions
21 | become trivial—how to name URLs (for Flask's built-in URL building), what
22 | template to render for an action on a URL, et cetera. By using Flask-Resource,
23 | you get these things automatically.
24 |
25 |
26 | Content Negotiation
27 | -------------------
28 |
29 | If you structure your application well, you don't need a separate ``/api/`` URL
30 | to talk JSON or XML—you can do it at the same URLs as the rest of your
31 | application. With Flask-Resource, you split up your functions into an action
32 | part and a rendering part. This means you can process a form, then respond in
33 | HTML for browsers and JSON for JavaScript and API clients, without any extra
34 | work or ugly nested ``if`` statements.
35 |
36 |
37 | Quickstart
38 | ----------
39 |
40 | Jump into the :doc:`quickstart ` now, to see how easily you can
41 | build a resource-oriented application with Flask.
42 |
43 |
44 | Contents:
45 |
46 | .. toctree::
47 | :maxdepth: 2
48 |
49 | quickstart
50 |
51 |
52 |
53 | Indices and tables
54 | ==================
55 |
56 | * :ref:`genindex`
57 | * :ref:`modindex`
58 | * :ref:`search`
59 |
60 |
--------------------------------------------------------------------------------
/doc/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/flask-resource.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/flask-resource.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/flask-resource"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/flask-resource"
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 |
--------------------------------------------------------------------------------
/doc/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Flask-Resource documentation build configuration file, created by
4 | # sphinx-quickstart on Sat Jun 16 22:48:07 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 = []
29 |
30 | # Add any paths that contain templates here, relative to this directory.
31 | templates_path = ['.templates']
32 |
33 | # The suffix of source filenames.
34 | source_suffix = '.rst'
35 |
36 | # The encoding of source files.
37 | #source_encoding = 'utf-8-sig'
38 |
39 | # The master toctree document.
40 | master_doc = 'index'
41 |
42 | # General information about the project.
43 | project = u'Flask-Resource'
44 | copyright = u'2012, Zachary Voase'
45 |
46 | # The version info for the project you're documenting, acts as replacement for
47 | # |version| and |release|, also used in various other places throughout the
48 | # built documents.
49 | #
50 | # The short X.Y version.
51 | version = '0.0.1'
52 | # The full version, including alpha/beta/rc tags.
53 | release = '0.0.1'
54 |
55 | # The language for content autogenerated by Sphinx. Refer to documentation
56 | # for a list of supported languages.
57 | #language = None
58 |
59 | # There are two options for replacing |today|: either, you set today to some
60 | # non-false value, then it is used:
61 | #today = ''
62 | # Else, today_fmt is used as the format for a strftime call.
63 | #today_fmt = '%B %d, %Y'
64 |
65 | # List of patterns, relative to source directory, that match files and
66 | # directories to ignore when looking for source files.
67 | exclude_patterns = ['.build']
68 |
69 | # The reST default role (used for this markup: `text`) to use for all documents.
70 | #default_role = None
71 |
72 | # If true, '()' will be appended to :func: etc. cross-reference text.
73 | #add_function_parentheses = True
74 |
75 | # If true, the current module name will be prepended to all description
76 | # unit titles (such as .. function::).
77 | #add_module_names = True
78 |
79 | # If true, sectionauthor and moduleauthor directives will be shown in the
80 | # output. They are ignored by default.
81 | #show_authors = False
82 |
83 | # The name of the Pygments (syntax highlighting) style to use.
84 | pygments_style = 'sphinx'
85 |
86 | # A list of ignored prefixes for module index sorting.
87 | #modindex_common_prefix = []
88 |
89 |
90 | # -- Options for HTML output ---------------------------------------------------
91 |
92 | # The theme to use for HTML and HTML Help pages. See the documentation for
93 | # a list of builtin themes.
94 | html_theme = 'default'
95 |
96 | # Theme options are theme-specific and customize the look and feel of a theme
97 | # further. For a list of options available for each theme, see the
98 | # documentation.
99 | #html_theme_options = {}
100 |
101 | # Add any paths that contain custom themes here, relative to this directory.
102 | #html_theme_path = []
103 |
104 | # The name for this set of Sphinx documents. If None, it defaults to
105 | # " v documentation".
106 | #html_title = None
107 |
108 | # A shorter title for the navigation bar. Default is the same as html_title.
109 | #html_short_title = None
110 |
111 | # The name of an image file (relative to this directory) to place at the top
112 | # of the sidebar.
113 | #html_logo = None
114 |
115 | # The name of an image file (within the static path) to use as favicon of the
116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
117 | # pixels large.
118 | #html_favicon = None
119 |
120 | # Add any paths that contain custom static files (such as style sheets) here,
121 | # relative to this directory. They are copied after the builtin static files,
122 | # so a file named "default.css" will overwrite the builtin "default.css".
123 | html_static_path = ['.static']
124 |
125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
126 | # using the given strftime format.
127 | #html_last_updated_fmt = '%b %d, %Y'
128 |
129 | # If true, SmartyPants will be used to convert quotes and dashes to
130 | # typographically correct entities.
131 | #html_use_smartypants = True
132 |
133 | # Custom sidebar templates, maps document names to template names.
134 | #html_sidebars = {}
135 |
136 | # Additional templates that should be rendered to pages, maps page names to
137 | # template names.
138 | #html_additional_pages = {}
139 |
140 | # If false, no module index is generated.
141 | #html_domain_indices = True
142 |
143 | # If false, no index is generated.
144 | #html_use_index = True
145 |
146 | # If true, the index is split into individual pages for each letter.
147 | #html_split_index = False
148 |
149 | # If true, links to the reST sources are added to the pages.
150 | #html_show_sourcelink = True
151 |
152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
153 | #html_show_sphinx = True
154 |
155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
156 | #html_show_copyright = True
157 |
158 | # If true, an OpenSearch description file will be output, and all pages will
159 | # contain a tag referring to it. The value of this option must be the
160 | # base URL from which the finished HTML is served.
161 | #html_use_opensearch = ''
162 |
163 | # This is the file name suffix for HTML files (e.g. ".xhtml").
164 | #html_file_suffix = None
165 |
166 | # Output file base name for HTML help builder.
167 | htmlhelp_basename = 'flask-resourcedoc'
168 |
169 |
170 | # -- Options for LaTeX output --------------------------------------------------
171 |
172 | latex_elements = {
173 | # The paper size ('letterpaper' or 'a4paper').
174 | #'papersize': 'letterpaper',
175 |
176 | # The font size ('10pt', '11pt' or '12pt').
177 | #'pointsize': '10pt',
178 |
179 | # Additional stuff for the LaTeX preamble.
180 | #'preamble': '',
181 | }
182 |
183 | # Grouping the document tree into LaTeX files. List of tuples
184 | # (source start file, target name, title, author, documentclass [howto/manual]).
185 | latex_documents = [
186 | ('index', 'Flask-Resource.tex', u'Flask-Resource Documentation',
187 | u'Zachary Voase', 'manual'),
188 | ]
189 |
190 | # The name of an image file (relative to this directory) to place at the top of
191 | # the title page.
192 | #latex_logo = None
193 |
194 | # For "manual" documents, if this is true, then toplevel headings are parts,
195 | # not chapters.
196 | #latex_use_parts = False
197 |
198 | # If true, show page references after internal links.
199 | #latex_show_pagerefs = False
200 |
201 | # If true, show URL addresses after external links.
202 | #latex_show_urls = False
203 |
204 | # Documents to append as an appendix to all manuals.
205 | #latex_appendices = []
206 |
207 | # If false, no module index is generated.
208 | #latex_domain_indices = True
209 |
210 |
211 | # -- Options for manual page output --------------------------------------------
212 |
213 | # One entry per manual page. List of tuples
214 | # (source start file, name, description, authors, manual section).
215 | man_pages = [
216 | ('index', 'Flask-Resource', u'Flask-Resource Documentation',
217 | [u'Zachary Voase'], 1)
218 | ]
219 |
220 | # If true, show URL addresses after external links.
221 | #man_show_urls = False
222 |
223 |
224 | # -- Options for Texinfo output ------------------------------------------------
225 |
226 | # Grouping the document tree into Texinfo files. List of tuples
227 | # (source start file, target name, title, author,
228 | # dir menu entry, description, category)
229 | texinfo_documents = [
230 | ('index', 'Flask-Resource', u'Flask-Resource Documentation',
231 | u'Zachary Voase', 'Flask-Resource', 'One line description of project.',
232 | 'Miscellaneous'),
233 | ]
234 |
235 | # Documents to append as an appendix to all manuals.
236 | #texinfo_appendices = []
237 |
238 | # If false, no module index is generated.
239 | #texinfo_domain_indices = True
240 |
241 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
242 | #texinfo_show_urls = 'footnote'
243 |
--------------------------------------------------------------------------------
/distribute_setup.py:
--------------------------------------------------------------------------------
1 | #!python
2 | """Bootstrap distribute installation
3 |
4 | If you want to use setuptools in your package's setup.py, just include this
5 | file in the same directory with it, and add this to the top of your setup.py::
6 |
7 | from distribute_setup import use_setuptools
8 | use_setuptools()
9 |
10 | If you want to require a specific version of setuptools, set a download
11 | mirror, or use an alternate download directory, you can do so by supplying
12 | the appropriate options to ``use_setuptools()``.
13 |
14 | This file can also be run as a script to install or upgrade setuptools.
15 | """
16 | import os
17 | import sys
18 | import time
19 | import fnmatch
20 | import tempfile
21 | import tarfile
22 | from distutils import log
23 |
24 | try:
25 | from site import USER_SITE
26 | except ImportError:
27 | USER_SITE = None
28 |
29 | try:
30 | import subprocess
31 |
32 | def _python_cmd(*args):
33 | args = (sys.executable,) + args
34 | return subprocess.call(args) == 0
35 |
36 | except ImportError:
37 | # will be used for python 2.3
38 | def _python_cmd(*args):
39 | args = (sys.executable,) + args
40 | # quoting arguments if windows
41 | if sys.platform == 'win32':
42 | def quote(arg):
43 | if ' ' in arg:
44 | return '"%s"' % arg
45 | return arg
46 | args = [quote(arg) for arg in args]
47 | return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
48 |
49 | DEFAULT_VERSION = "0.6.27"
50 | DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
51 | SETUPTOOLS_FAKED_VERSION = "0.6c11"
52 |
53 | SETUPTOOLS_PKG_INFO = """\
54 | Metadata-Version: 1.0
55 | Name: setuptools
56 | Version: %s
57 | Summary: xxxx
58 | Home-page: xxx
59 | Author: xxx
60 | Author-email: xxx
61 | License: xxx
62 | Description: xxx
63 | """ % SETUPTOOLS_FAKED_VERSION
64 |
65 |
66 | def _install(tarball, install_args=()):
67 | # extracting the tarball
68 | tmpdir = tempfile.mkdtemp()
69 | log.warn('Extracting in %s', tmpdir)
70 | old_wd = os.getcwd()
71 | try:
72 | os.chdir(tmpdir)
73 | tar = tarfile.open(tarball)
74 | _extractall(tar)
75 | tar.close()
76 |
77 | # going in the directory
78 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
79 | os.chdir(subdir)
80 | log.warn('Now working in %s', subdir)
81 |
82 | # installing
83 | log.warn('Installing Distribute')
84 | if not _python_cmd('setup.py', 'install', *install_args):
85 | log.warn('Something went wrong during the installation.')
86 | log.warn('See the error message above.')
87 | finally:
88 | os.chdir(old_wd)
89 |
90 |
91 | def _build_egg(egg, tarball, to_dir):
92 | # extracting the tarball
93 | tmpdir = tempfile.mkdtemp()
94 | log.warn('Extracting in %s', tmpdir)
95 | old_wd = os.getcwd()
96 | try:
97 | os.chdir(tmpdir)
98 | tar = tarfile.open(tarball)
99 | _extractall(tar)
100 | tar.close()
101 |
102 | # going in the directory
103 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
104 | os.chdir(subdir)
105 | log.warn('Now working in %s', subdir)
106 |
107 | # building an egg
108 | log.warn('Building a Distribute egg in %s', to_dir)
109 | _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
110 |
111 | finally:
112 | os.chdir(old_wd)
113 | # returning the result
114 | log.warn(egg)
115 | if not os.path.exists(egg):
116 | raise IOError('Could not build the egg.')
117 |
118 |
119 | def _do_download(version, download_base, to_dir, download_delay):
120 | egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
121 | % (version, sys.version_info[0], sys.version_info[1]))
122 | if not os.path.exists(egg):
123 | tarball = download_setuptools(version, download_base,
124 | to_dir, download_delay)
125 | _build_egg(egg, tarball, to_dir)
126 | sys.path.insert(0, egg)
127 | import setuptools
128 | setuptools.bootstrap_install_from = egg
129 |
130 |
131 | def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
132 | to_dir=os.curdir, download_delay=15, no_fake=True):
133 | # making sure we use the absolute path
134 | to_dir = os.path.abspath(to_dir)
135 | was_imported = 'pkg_resources' in sys.modules or \
136 | 'setuptools' in sys.modules
137 | try:
138 | try:
139 | import pkg_resources
140 | if not hasattr(pkg_resources, '_distribute'):
141 | if not no_fake:
142 | _fake_setuptools()
143 | raise ImportError
144 | except ImportError:
145 | return _do_download(version, download_base, to_dir, download_delay)
146 | try:
147 | pkg_resources.require("distribute>="+version)
148 | return
149 | except pkg_resources.VersionConflict:
150 | e = sys.exc_info()[1]
151 | if was_imported:
152 | sys.stderr.write(
153 | "The required version of distribute (>=%s) is not available,\n"
154 | "and can't be installed while this script is running. Please\n"
155 | "install a more recent version first, using\n"
156 | "'easy_install -U distribute'."
157 | "\n\n(Currently using %r)\n" % (version, e.args[0]))
158 | sys.exit(2)
159 | else:
160 | del pkg_resources, sys.modules['pkg_resources'] # reload ok
161 | return _do_download(version, download_base, to_dir,
162 | download_delay)
163 | except pkg_resources.DistributionNotFound:
164 | return _do_download(version, download_base, to_dir,
165 | download_delay)
166 | finally:
167 | if not no_fake:
168 | _create_fake_setuptools_pkg_info(to_dir)
169 |
170 | def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
171 | to_dir=os.curdir, delay=15):
172 | """Download distribute from a specified location and return its filename
173 |
174 | `version` should be a valid distribute version number that is available
175 | as an egg for download under the `download_base` URL (which should end
176 | with a '/'). `to_dir` is the directory where the egg will be downloaded.
177 | `delay` is the number of seconds to pause before an actual download
178 | attempt.
179 | """
180 | # making sure we use the absolute path
181 | to_dir = os.path.abspath(to_dir)
182 | try:
183 | from urllib.request import urlopen
184 | except ImportError:
185 | from urllib2 import urlopen
186 | tgz_name = "distribute-%s.tar.gz" % version
187 | url = download_base + tgz_name
188 | saveto = os.path.join(to_dir, tgz_name)
189 | src = dst = None
190 | if not os.path.exists(saveto): # Avoid repeated downloads
191 | try:
192 | log.warn("Downloading %s", url)
193 | src = urlopen(url)
194 | # Read/write all in one block, so we don't create a corrupt file
195 | # if the download is interrupted.
196 | data = src.read()
197 | dst = open(saveto, "wb")
198 | dst.write(data)
199 | finally:
200 | if src:
201 | src.close()
202 | if dst:
203 | dst.close()
204 | return os.path.realpath(saveto)
205 |
206 | def _no_sandbox(function):
207 | def __no_sandbox(*args, **kw):
208 | try:
209 | from setuptools.sandbox import DirectorySandbox
210 | if not hasattr(DirectorySandbox, '_old'):
211 | def violation(*args):
212 | pass
213 | DirectorySandbox._old = DirectorySandbox._violation
214 | DirectorySandbox._violation = violation
215 | patched = True
216 | else:
217 | patched = False
218 | except ImportError:
219 | patched = False
220 |
221 | try:
222 | return function(*args, **kw)
223 | finally:
224 | if patched:
225 | DirectorySandbox._violation = DirectorySandbox._old
226 | del DirectorySandbox._old
227 |
228 | return __no_sandbox
229 |
230 | def _patch_file(path, content):
231 | """Will backup the file then patch it"""
232 | existing_content = open(path).read()
233 | if existing_content == content:
234 | # already patched
235 | log.warn('Already patched.')
236 | return False
237 | log.warn('Patching...')
238 | _rename_path(path)
239 | f = open(path, 'w')
240 | try:
241 | f.write(content)
242 | finally:
243 | f.close()
244 | return True
245 |
246 | _patch_file = _no_sandbox(_patch_file)
247 |
248 | def _same_content(path, content):
249 | return open(path).read() == content
250 |
251 | def _rename_path(path):
252 | new_name = path + '.OLD.%s' % time.time()
253 | log.warn('Renaming %s into %s', path, new_name)
254 | os.rename(path, new_name)
255 | return new_name
256 |
257 | def _remove_flat_installation(placeholder):
258 | if not os.path.isdir(placeholder):
259 | log.warn('Unkown installation at %s', placeholder)
260 | return False
261 | found = False
262 | for file in os.listdir(placeholder):
263 | if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
264 | found = True
265 | break
266 | if not found:
267 | log.warn('Could not locate setuptools*.egg-info')
268 | return
269 |
270 | log.warn('Removing elements out of the way...')
271 | pkg_info = os.path.join(placeholder, file)
272 | if os.path.isdir(pkg_info):
273 | patched = _patch_egg_dir(pkg_info)
274 | else:
275 | patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
276 |
277 | if not patched:
278 | log.warn('%s already patched.', pkg_info)
279 | return False
280 | # now let's move the files out of the way
281 | for element in ('setuptools', 'pkg_resources.py', 'site.py'):
282 | element = os.path.join(placeholder, element)
283 | if os.path.exists(element):
284 | _rename_path(element)
285 | else:
286 | log.warn('Could not find the %s element of the '
287 | 'Setuptools distribution', element)
288 | return True
289 |
290 | _remove_flat_installation = _no_sandbox(_remove_flat_installation)
291 |
292 | def _after_install(dist):
293 | log.warn('After install bootstrap.')
294 | placeholder = dist.get_command_obj('install').install_purelib
295 | _create_fake_setuptools_pkg_info(placeholder)
296 |
297 | def _create_fake_setuptools_pkg_info(placeholder):
298 | if not placeholder or not os.path.exists(placeholder):
299 | log.warn('Could not find the install location')
300 | return
301 | pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
302 | setuptools_file = 'setuptools-%s-py%s.egg-info' % \
303 | (SETUPTOOLS_FAKED_VERSION, pyver)
304 | pkg_info = os.path.join(placeholder, setuptools_file)
305 | if os.path.exists(pkg_info):
306 | log.warn('%s already exists', pkg_info)
307 | return
308 |
309 | if not os.access(pkg_info, os.W_OK):
310 | log.warn("Don't have permissions to write %s, skipping", pkg_info)
311 |
312 | log.warn('Creating %s', pkg_info)
313 | f = open(pkg_info, 'w')
314 | try:
315 | f.write(SETUPTOOLS_PKG_INFO)
316 | finally:
317 | f.close()
318 |
319 | pth_file = os.path.join(placeholder, 'setuptools.pth')
320 | log.warn('Creating %s', pth_file)
321 | f = open(pth_file, 'w')
322 | try:
323 | f.write(os.path.join(os.curdir, setuptools_file))
324 | finally:
325 | f.close()
326 |
327 | _create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info)
328 |
329 | def _patch_egg_dir(path):
330 | # let's check if it's already patched
331 | pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
332 | if os.path.exists(pkg_info):
333 | if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
334 | log.warn('%s already patched.', pkg_info)
335 | return False
336 | _rename_path(path)
337 | os.mkdir(path)
338 | os.mkdir(os.path.join(path, 'EGG-INFO'))
339 | pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
340 | f = open(pkg_info, 'w')
341 | try:
342 | f.write(SETUPTOOLS_PKG_INFO)
343 | finally:
344 | f.close()
345 | return True
346 |
347 | _patch_egg_dir = _no_sandbox(_patch_egg_dir)
348 |
349 | def _before_install():
350 | log.warn('Before install bootstrap.')
351 | _fake_setuptools()
352 |
353 |
354 | def _under_prefix(location):
355 | if 'install' not in sys.argv:
356 | return True
357 | args = sys.argv[sys.argv.index('install')+1:]
358 | for index, arg in enumerate(args):
359 | for option in ('--root', '--prefix'):
360 | if arg.startswith('%s=' % option):
361 | top_dir = arg.split('root=')[-1]
362 | return location.startswith(top_dir)
363 | elif arg == option:
364 | if len(args) > index:
365 | top_dir = args[index+1]
366 | return location.startswith(top_dir)
367 | if arg == '--user' and USER_SITE is not None:
368 | return location.startswith(USER_SITE)
369 | return True
370 |
371 |
372 | def _fake_setuptools():
373 | log.warn('Scanning installed packages')
374 | try:
375 | import pkg_resources
376 | except ImportError:
377 | # we're cool
378 | log.warn('Setuptools or Distribute does not seem to be installed.')
379 | return
380 | ws = pkg_resources.working_set
381 | try:
382 | setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools',
383 | replacement=False))
384 | except TypeError:
385 | # old distribute API
386 | setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
387 |
388 | if setuptools_dist is None:
389 | log.warn('No setuptools distribution found')
390 | return
391 | # detecting if it was already faked
392 | setuptools_location = setuptools_dist.location
393 | log.warn('Setuptools installation detected at %s', setuptools_location)
394 |
395 | # if --root or --preix was provided, and if
396 | # setuptools is not located in them, we don't patch it
397 | if not _under_prefix(setuptools_location):
398 | log.warn('Not patching, --root or --prefix is installing Distribute'
399 | ' in another location')
400 | return
401 |
402 | # let's see if its an egg
403 | if not setuptools_location.endswith('.egg'):
404 | log.warn('Non-egg installation')
405 | res = _remove_flat_installation(setuptools_location)
406 | if not res:
407 | return
408 | else:
409 | log.warn('Egg installation')
410 | pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
411 | if (os.path.exists(pkg_info) and
412 | _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
413 | log.warn('Already patched.')
414 | return
415 | log.warn('Patching...')
416 | # let's create a fake egg replacing setuptools one
417 | res = _patch_egg_dir(setuptools_location)
418 | if not res:
419 | return
420 | log.warn('Patched done.')
421 | _relaunch()
422 |
423 |
424 | def _relaunch():
425 | log.warn('Relaunching...')
426 | # we have to relaunch the process
427 | # pip marker to avoid a relaunch bug
428 | if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']:
429 | sys.argv[0] = 'setup.py'
430 | args = [sys.executable] + sys.argv
431 | sys.exit(subprocess.call(args))
432 |
433 |
434 | def _extractall(self, path=".", members=None):
435 | """Extract all members from the archive to the current working
436 | directory and set owner, modification time and permissions on
437 | directories afterwards. `path' specifies a different directory
438 | to extract to. `members' is optional and must be a subset of the
439 | list returned by getmembers().
440 | """
441 | import copy
442 | import operator
443 | from tarfile import ExtractError
444 | directories = []
445 |
446 | if members is None:
447 | members = self
448 |
449 | for tarinfo in members:
450 | if tarinfo.isdir():
451 | # Extract directories with a safe mode.
452 | directories.append(tarinfo)
453 | tarinfo = copy.copy(tarinfo)
454 | tarinfo.mode = 448 # decimal for oct 0700
455 | self.extract(tarinfo, path)
456 |
457 | # Reverse sort directories.
458 | if sys.version_info < (2, 4):
459 | def sorter(dir1, dir2):
460 | return cmp(dir1.name, dir2.name)
461 | directories.sort(sorter)
462 | directories.reverse()
463 | else:
464 | directories.sort(key=operator.attrgetter('name'), reverse=True)
465 |
466 | # Set correct owner, mtime and filemode on directories.
467 | for tarinfo in directories:
468 | dirpath = os.path.join(path, tarinfo.name)
469 | try:
470 | self.chown(tarinfo, dirpath)
471 | self.utime(tarinfo, dirpath)
472 | self.chmod(tarinfo, dirpath)
473 | except ExtractError:
474 | e = sys.exc_info()[1]
475 | if self.errorlevel > 1:
476 | raise
477 | else:
478 | self._dbg(1, "tarfile: %s" % e)
479 |
480 | def _build_install_args(argv):
481 | install_args = []
482 | user_install = '--user' in argv
483 | if user_install and sys.version_info < (2,6):
484 | log.warn("--user requires Python 2.6 or later")
485 | raise SystemExit(1)
486 | if user_install:
487 | install_args.append('--user')
488 | return install_args
489 |
490 | def main(argv, version=DEFAULT_VERSION):
491 | """Install or upgrade setuptools and EasyInstall"""
492 | tarball = download_setuptools()
493 | _install(tarball, _build_install_args(argv))
494 |
495 |
496 | if __name__ == '__main__':
497 | main(sys.argv[1:])
498 |
--------------------------------------------------------------------------------