├── .github └── workflows │ └── tests.yaml ├── .gitignore ├── .readthedocs.yaml ├── AUTHORS ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── conf.py ├── developer.txt ├── example_app.ini ├── example_cgi_app.ini ├── index.txt ├── license.txt ├── modules │ ├── checkperms.txt │ ├── cherrypy_server.txt │ ├── command.txt │ ├── copydir.txt │ ├── filemaker.txt │ ├── templates.txt │ ├── testapp.txt │ └── util.secret.txt └── news.txt ├── paste ├── __init__.py └── script │ ├── __init__.py │ ├── appinstall.py │ ├── bool_optparse.py │ ├── cgi_server.py │ ├── checkperms.py │ ├── cherrypy_server.py │ ├── command.py │ ├── copydir.py │ ├── create_distro.py │ ├── default_sysconfig.py │ ├── entrypoints.py │ ├── epdesc.py │ ├── exe.py │ ├── filemaker.py │ ├── flup_server.py │ ├── grep.py │ ├── help.py │ ├── interfaces.py │ ├── paster-templates │ └── basic_package │ │ ├── +package+ │ │ └── __init__.py │ │ ├── docs │ │ └── license.txt_tmpl │ │ ├── setup.cfg │ │ └── setup.py_tmpl │ ├── pluginlib.py │ ├── request.py │ ├── serve.py │ ├── templates.py │ ├── testapp.py │ ├── twisted_web2_server.py │ ├── util │ ├── __init__.py │ ├── logging_config.py │ └── secret.py │ └── wsgiutils_server.py ├── regen-docs ├── scripts └── paster ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── appsetup │ ├── __init__.py │ ├── test_make_project.py │ └── testfiles │ │ ├── admin_index.py │ │ ├── conftest.py │ │ ├── iscape.txt │ │ └── test_forbidden.py ├── fake_packages │ └── FakePlugin.egg │ │ ├── FakePlugin.egg-info │ │ ├── PKG-INFO │ │ ├── entry_points.txt │ │ ├── paster_plugins.txt │ │ └── top_level.txt │ │ ├── fakeplugin │ │ └── __init__.py │ │ └── setup.py ├── sample_templates │ ├── test1.txt │ ├── test2.py_tmpl │ ├── test3.ini_tmpl │ └── test4.html_tmpl ├── test_command.py ├── test_egg_finder.py ├── test_logging_config.py ├── test_plugin_adder.py ├── test_template_introspect.py └── test_util.py └── tox.ini /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: 3 | - push 4 | - pull_request 5 | - workflow_dispatch 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | include: 12 | - python: 3.8 13 | toxenv: py38 14 | - python: 3.9 15 | toxenv: py39 16 | - python: "3.10" 17 | toxenv: py310 18 | - python: "3.11" 19 | toxenv: py311 20 | - python: "3.12" 21 | toxenv: py312 22 | - python: "3.13" 23 | toxenv: py313 24 | name: ${{ matrix.toxenv }} on Python ${{ matrix.python }} 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: actions/setup-python@v5 28 | with: 29 | python-version: ${{ matrix.python }} 30 | - run: pip install tox 31 | - run: tox 32 | env: 33 | TOXENV: ${{ matrix.toxenv }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .tox 3 | .coverage 4 | *.egg-info 5 | docs/_build/ 6 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-lts-latest 11 | tools: 12 | python: "3.11" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/conf.py 17 | 18 | # We recommend specifying your dependencies to enable reproducible builds: 19 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 20 | # python: 21 | # install: 22 | # - requirements: docs/requirements.txt 23 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | PasteScript authors 2 | =================== 3 | 4 | * Atsushi Odagiri aka aodag 5 | * bbangert 6 | * Chris McDonough 7 | * Clay Gerrard 8 | * FGtatsuro 9 | * flynsqrl 10 | * Gary van der Merwe 11 | * Ian Bicking 12 | * Jaap Roes 13 | * kevin 14 | * Marc Abramowitz 15 | * Mike Gilbert 16 | * pjenvey 17 | * rflosi 18 | * Richard Mitchell 19 | * thecrypto 20 | * thejimmyg 21 | * Victor Stinner 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include docs *.txt 2 | include docs/conf.py 3 | include docs/*.ini 4 | include regen-docs 5 | include scripts/paster 6 | include tests/sample_templates/*tmpl 7 | include tox.ini 8 | recursive-exclude docs/_build/_sources * 9 | recursive-include docs/_build *.html 10 | recursive-include tests *.txt *.py PKG-INFO 11 | recursive-include paste *.js *.jpg 12 | recursive-include paste/script/paster-templates/basic_package setup.* *.py *_tmpl 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # simple Makefile for some common tasks 2 | .PHONY: clean test dist release pypi tagv 3 | 4 | pastescript-version := $(shell python setup.py --version) 5 | 6 | clean: 7 | find . -name "*.pyc" |xargs rm || true 8 | rm -r dist || true 9 | rm -r build || true 10 | rm -rf .tox || true 11 | rm -r cover .coverage || true 12 | rm -r .eggs || true 13 | rm -r pastescript.egg-info || true 14 | 15 | tagv: 16 | git tag -s -m ${pastescript-version} ${pastescript-version} 17 | git push origin master --tags 18 | 19 | cleanagain: 20 | find . -name "*.pyc" |xargs rm || true 21 | rm -r dist || true 22 | rm -r build || true 23 | rm -r .tox || true 24 | rm -r cover .coverage || true 25 | rm -r .eggs || true 26 | rm -r pastescript.egg-info || true 27 | 28 | test: 29 | tox --skip-missing-interpreters 30 | 31 | dist: test 32 | python3 setup.py sdist bdist_wheel 33 | 34 | release: clean test cleanagain tagv pypi gh 35 | 36 | pypi: 37 | python3 setup.py sdist bdist_wheel 38 | twine upload dist/* 39 | 40 | gh: 41 | gh release create ${pastescript-version} --generate-notes dist/* 42 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | PasteScript is a pluggable command-line tool. 2 | 3 | **Note**: Paste Script is being maintained on life support. That 4 | means that critical bugs will be fixed, and support for new versions 5 | of Python will be handled, but other than that new features are not 6 | being considered. 7 | 8 | **With version 3.6.0 pastescript development moves to the pasteorg GitHub 9 | organization and will be going deeper into maintenance mode unless 10 | more active maintainers step forward to take over. "Deeper" in this 11 | case means that releases will be much less frequent and patches 12 | will only be accepted for security issues or major problems. Current 13 | consumers of pastescript should prepare to migrate away to more modern 14 | solutions.** 15 | 16 | It includes some built-in features; 17 | 18 | * Create file layouts for packages. For instance, ``paster create 19 | --template=basic_package MyPackage`` will create a `setuptools 20 | `_-ready 21 | file layout. 22 | 23 | * Serving up web applications, with configuration based on 24 | `paste.deploy `_. 25 | 26 | The latest version is available in a `GitHub repository 27 | `_. 28 | 29 | For the latest changes see the `news file 30 | `_. 31 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Paste documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Apr 22 22:08:49 2008. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # The contents of this file are pickled, so don't put values in the namespace 9 | # that aren't pickleable (module imports are okay, they're removed automatically). 10 | # 11 | # All configuration values have a default value; values that are commented out 12 | # serve to show the default value. 13 | 14 | 15 | # If your extensions are in another directory, add it here. 16 | #import sys 17 | #sys.path.append('some/directory') 18 | 19 | # General configuration 20 | # --------------------- 21 | 22 | # Add any Sphinx extension module names here, as strings. They can be extensions 23 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 24 | extensions = ['sphinx.ext.autodoc'] 25 | 26 | # Add any paths that contain templates here, relative to this directory. 27 | templates_path = ['_templates'] 28 | 29 | # The suffix of source filenames. 30 | source_suffix = '.txt' 31 | 32 | # The master toctree document. 33 | master_doc = 'index' 34 | 35 | # General substitutions. 36 | project = 'Paste Script' 37 | copyright = '2011, Ian Bicking' 38 | 39 | # The default replacements for |version| and |release|, also used in various 40 | # other places throughout the built documents. 41 | # 42 | # The short X.Y version. 43 | version = '' 44 | # The full version, including alpha/beta/rc tags. 45 | release = '' 46 | 47 | # There are two options for replacing |today|: either, you set today to some 48 | # non-false value, then it is used: 49 | #today = '' 50 | # Else, today_fmt is used as the format for a strftime call. 51 | today_fmt = '%B %d, %Y' 52 | 53 | # List of documents that shouldn't be included in the build. 54 | #unused_docs = ['include/contact.txt', 'include/reference_header.txt'] 55 | 56 | # If true, '()' will be appended to :func: etc. cross-reference text. 57 | #add_function_parentheses = True 58 | 59 | # If true, the current module name will be prepended to all description 60 | # unit titles (such as .. function::). 61 | #add_module_names = True 62 | 63 | # If true, sectionauthor and moduleauthor directives will be shown in the 64 | # output. They are ignored by default. 65 | #show_authors = False 66 | 67 | # The name of the Pygments (syntax highlighting) style to use. 68 | pygments_style = 'sphinx' 69 | 70 | 71 | # Options for HTML output 72 | # ----------------------- 73 | 74 | html_theme = 'nature' 75 | 76 | # Add any paths that contain custom static files (such as style sheets) here, 77 | # relative to this directory. They are copied after the builtin static files, 78 | # so a file named "default.css" will overwrite the builtin "default.css". 79 | html_static_path = ['_static'] 80 | 81 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 82 | # using the given strftime format. 83 | html_last_updated_fmt = '%b %d, %Y' 84 | 85 | # If true, SmartyPants will be used to convert quotes and dashes to 86 | # typographically correct entities. 87 | #html_use_smartypants = True 88 | 89 | # Content template for the index page. 90 | #html_index = '' 91 | 92 | # Custom sidebar templates, maps document names to template names. 93 | #html_sidebars = {} 94 | 95 | # Additional templates that should be rendered to pages, maps page names to 96 | # template names. 97 | #html_additional_pages = {} 98 | 99 | # If false, no module index is generated. 100 | #html_use_modindex = True 101 | 102 | # If true, the reST sources are included in the HTML build as _sources/. 103 | #html_copy_source = True 104 | 105 | # Output file base name for HTML help builder. 106 | htmlhelp_basename = 'PasteScriptdoc' 107 | 108 | 109 | # Options for LaTeX output 110 | # ------------------------ 111 | 112 | # The paper size ('letter' or 'a4'). 113 | #latex_paper_size = 'letter' 114 | 115 | # The font size ('10pt', '11pt' or '12pt'). 116 | #latex_font_size = '10pt' 117 | 118 | # Grouping the document tree into LaTeX files. List of tuples 119 | # (source start file, target name, title, author, document class [howto/manual]). 120 | #latex_documents = [] 121 | 122 | # Additional stuff for the LaTeX preamble. 123 | #latex_preamble = '' 124 | 125 | # Documents to append as an appendix to all manuals. 126 | #latex_appendices = [] 127 | 128 | # If false, no module index is generated. 129 | #latex_use_modindex = True 130 | -------------------------------------------------------------------------------- /docs/developer.txt: -------------------------------------------------------------------------------- 1 | Paste Script: Development 2 | ========================= 3 | 4 | :author: Ian Bicking 5 | :revision: $Rev$ 6 | :date: $LastChangedDate$ 7 | 8 | .. contents:: 9 | 10 | Introduction 11 | ------------ 12 | 13 | This document is an introduction to how you can extend ``paster`` and 14 | Paste Script for your system -- be it a framework, server setup, or 15 | whatever else you want to do. 16 | 17 | What Paste Script Can Do 18 | ------------------------ 19 | 20 | ``paster`` is a two-level command, where the second level (e.g., 21 | ``paster help``, ``paster create``, etc) is pluggable. 22 | 23 | Commands are attached to `Python Eggs 24 | `_, i.e., to the 25 | package you distribute and someone installs. The commands are 26 | identified using `entry points 27 | `_. 28 | 29 | To make your command available do something like this in your 30 | ``setup.py`` file:: 31 | 32 | from setuptools import setup 33 | setup(... 34 | entry_points=""" 35 | [paste.paster_command] 36 | mycommand = mypackage.mycommand:MyCommand 37 | 38 | [paste.global_paster_command] 39 | myglobal = mypackage.myglobal:MyGlobalCommand 40 | """) 41 | 42 | This means that ``paster mycommand`` will run the ``MyCommand`` 43 | command located in the ``mypackage.mycommand`` module. Similarly with 44 | ``paster myglobal``. The distinction between these two entry points 45 | is that the first will only be usable when ``paster`` is run inside a 46 | project that is identified as using your project, while the second 47 | will be globally available as a command as soon as your package is 48 | installed. 49 | 50 | How's the Local Thing Work? 51 | --------------------------- 52 | 53 | So if you have a local command, how does it get enabled? If the 54 | person is running ``paster`` inside their project directory, 55 | ``paster`` will look in ``Project_Name.egg-info/paster_plugins.txt`` 56 | which is a list of project names (the name of your package) whose 57 | commands should be made available. 58 | 59 | This is for frameworks, so frameworks can add commands to ``paster`` 60 | that only apply to projects that use that framework. 61 | 62 | What Do Commands Look Like? 63 | --------------------------- 64 | 65 | The command objects (like ``MyCommand``) are subclasses of 66 | ``paste.script.command.Command``. You can look at that class to get 67 | an idea, but a basic outline looks like this:: 68 | 69 | from paste.script import command 70 | 71 | class MyCommand(command.Command): 72 | 73 | max_args = 1 74 | min_args = 1 75 | 76 | usage = "NAME" 77 | summary = "Say hello!" 78 | group_name = "My Package Name" 79 | 80 | parser = command.Command.standard_parser(verbose=True) 81 | parser.add_option('--goodbye', 82 | action='store_true', 83 | dest='goodbye', 84 | help="Say 'Goodbye' instead") 85 | 86 | def command(self): 87 | name = self.args[0] 88 | if self.verbose: 89 | print "Got name: %r" % name 90 | if self.options.goodbye: 91 | print "Goodbye", name 92 | else: 93 | print "Hello", name 94 | 95 | ``max_args`` and ``min_args`` are used to give error messages. You 96 | can also raise ``command.BadCommand(msg)`` if the arguments are 97 | incorrect in some way. (Use ``None`` here to give no restriction) 98 | 99 | The ``usage`` variable means ``paster mycommand -h`` will give a usage 100 | of ``paster mycommand [options] NAME``. ``summary`` is used with 101 | ``paster help`` (describing your command in a short form). 102 | ``group_name`` is used to group commands together for ``paste help`` 103 | under that title. 104 | 105 | The ``parser`` object is an `optparse 106 | ` 107 | ``OptionParser`` object. ``Command.standard_parser`` is a class 108 | method that creates normal options, and enables options based on these 109 | keyword (boolean) arguments: ``verbose``, ``interactive``, 110 | ``no_interactive`` (if interactive is the default), ``simulate``, 111 | ``quiet`` (undoes verbose), and ``overwrite``. You can create the 112 | parser however you want, but using ``standard_parser()`` encourages a 113 | consistent set of shared options across commands. 114 | 115 | When your command is run, ``.command()`` is called. As you can see, 116 | the options are in ``self.options`` and the positional arguments are 117 | in ``self.args``. Some options are turned into instance variables -- 118 | especially ``self.verbose`` and ``self.simulate`` (even if you haven't 119 | chosen to use those options, many methods expect to find some value 120 | there, which is why they are turned into instance variables). 121 | 122 | There are quite a few useful methods you can use in your command. See 123 | the `Command class `_ for a 124 | complete list. Some particulars: 125 | 126 | ``run_command(cmd, arg1, arg2, ..., cwd=os.getcwd(), capture_stderr=False)``: 127 | 128 | Runs the command, respecting verbosity and simulation. Will raise 129 | an error if the command doesn't exit with a 0 code. 130 | 131 | ``insert_into_file(filename, marker_name, text, indent=False)``: 132 | 133 | Inserts a line of text into the file, looking for a marker like 134 | ``-*- marker_name -*-`` (and inserting just after it). If 135 | ``indent=True``, then the line will be indented at the same level 136 | as the marker line. 137 | 138 | ``ensure_dir(dir, svn_add=True)``: 139 | 140 | Ensures that the directory exists. If ``svn_add`` is true and the 141 | parent directory has an ``.svn`` directory, add the new directory 142 | to Subversion. 143 | 144 | ``ensure_file(filename, content, svn_add=True)``: 145 | 146 | Ensure the file exists with the given content. Will ask the user 147 | before overwriting a file if ``--interactive`` has been given. 148 | 149 | Templates 150 | --------- 151 | 152 | The other pluggable part is "templates". These are used to create new 153 | projects. Paste Script includes one template itself: 154 | ``basic_package`` which creates a new setuptools package. 155 | 156 | To enable, add to ``setup.py``:: 157 | 158 | setup(... 159 | entry_points=""" 160 | [paste.paster_create_template] 161 | framework = framework.templates:FrameworkTemplate 162 | """) 163 | 164 | ``FrameworkTemplate`` should be a subclass of 165 | ``paste.script.templates.Template``. An easy way to do this is simply 166 | with:: 167 | 168 | from paste.script import templates 169 | 170 | class FrameworkTemplate(templates.Template): 171 | 172 | egg_plugins = ['Framework'] 173 | summary = 'Template for creating a basic Framework package' 174 | required_templates = ['basic_package'] 175 | _template_dir = 'template' 176 | use_cheetah = True 177 | 178 | ``egg_plugins`` will add ``Framework`` to ``paste_plugins.txt`` in the 179 | package. ``required_template`` means those template will be run 180 | before this one (so in this case you'll have a complete package ready, 181 | and you can just write your framework files to it). ``_template_dir`` 182 | is a module-relative directory to find your source files. 183 | 184 | The source files are just a directory of files that will be copied 185 | into place, potentially with variable substitutions. Three variables 186 | are expected: ``project`` is the project name (e.g., ``Project-Name``), 187 | ``package`` is the Python package in that project (e.g., 188 | ``projectname``) and ``egg`` is the project's egg name as generated by 189 | setuptools (e.g., ``Project_Name``). Users can add other variables by 190 | adding ``foo=bar`` arguments to ``paster create``. 191 | 192 | Filenames are substituted with ``+var_name+``, e.g., ``+package+`` is 193 | the package directory. 194 | 195 | If a file in the template directory ends in ``_tmpl`` then it will be 196 | substituted. If ``use_cheetah`` is true, then it's treated as a 197 | `Cheetah `_ template. Otherwise 198 | `string.Template 199 | `_ is 200 | used, though full expressions are allowed in ``${expr}`` instead of 201 | just variables. 202 | 203 | See the `templates module `_ for 204 | more. 205 | -------------------------------------------------------------------------------- /docs/example_app.ini: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env paster exe serve 2 | # This file serves the roles of being an rc script, you 3 | # can run it with "./example_wsgiutils_app.ini start" 4 | # plus start and restart. 5 | [exe] 6 | # Here we could set options for how this server should 7 | # start. These all correspond to command-line options: 8 | 9 | # user = username 10 | # group = groupname 11 | # daemon = true 12 | # pid_file = /path/to/pid 13 | # log_file = /path/to/log 14 | # reload = true 15 | # reload_interval = 10 16 | 17 | # For this example we'll daemonize: 18 | 19 | daemon = true 20 | 21 | [server:main] 22 | use = egg:Paste#http 23 | port = 8080 24 | 25 | [app:main] 26 | use = egg:PasteScript#test 27 | -------------------------------------------------------------------------------- /docs/example_cgi_app.ini: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env paster exe serve 2 | [server:main] 3 | use = egg:PasteScript#cgi 4 | 5 | [app:main] 6 | use = egg:PasteScript#test 7 | -------------------------------------------------------------------------------- /docs/index.txt: -------------------------------------------------------------------------------- 1 | Paste Script 2 | ============ 3 | 4 | :author: Ian Bicking 5 | :revision: $Rev$ 6 | :date: $LastChangedDate$ 7 | 8 | Contents: 9 | 10 | .. toctree:: 11 | :maxdepth: 1 12 | 13 | news 14 | developer 15 | license 16 | 17 | Warning 18 | ------- 19 | 20 | Paste Script is being maintained on life support. That means that 21 | critical bugs will be fixed, and support for new versions of Python 22 | will be handled, but other than that new features are not being 23 | considered. Development has moved to `GitHub 24 | `_. 25 | 26 | Introduction 27 | ------------ 28 | 29 | If you are developer, see the `Developer Documentation 30 | `_; this will tell you how to add commands and 31 | templates to ``paster``. For a list of updates see the `news file 32 | `_. 33 | 34 | Paste Script is released under the `MIT license 35 | `_. 36 | 37 | Status 38 | ------ 39 | 40 | Paste Script has passed version 1.0. Paste Script is in maintenance 41 | mode. Bugs will be fixed, new features are not being considered. 42 | 43 | ``paster create`` 44 | ----------------- 45 | 46 | This creates the skeleton for new projects. Many different kinds of 47 | projects have created skeletons for their projects (Pylons, 48 | TurboGears, ZopeSkel, and others). 49 | 50 | For a tutorial for making new skeletons, see `this tutorial from Lucas 51 | Szybalski `_. It also 52 | discusses creating new subcommands for paster. 53 | 54 | ``paster serve`` 55 | ---------------- 56 | 57 | The one useful command you may want to know about for ``paster`` is 58 | ``paster serve``. This serves an application described in a `Paste 59 | Deploy `_ configuration file. 60 | 61 | Configuration 62 | ------------- 63 | 64 | A quickstart (and example), if not complete explanation:: 65 | 66 | [app:main] 67 | use = egg:PasteEnabledPackage 68 | option1 = foo 69 | option2 = bar 70 | 71 | [server:main] 72 | use = egg:PasteScript#wsgiutils 73 | host = 127.0.0.1 74 | port = 80 75 | 76 | ``egg:PasteEnabledPackage`` refers to some package that has been 77 | prepared for use with paste.deploy, and options given to that 78 | package. If you are starting out with some framework, you'll have to 79 | reference some documentation for that framework to paste-deploy-ify 80 | your application (or read the paste.deploy documentation). 81 | 82 | In the same file is a server description. 83 | ``egg:PasteScript#wsgiutils`` is a server (named ``wsgiutils``) 84 | provided by this package, based on `WSGIUtils 85 | `_. And we pass various 86 | options particular to that server. 87 | 88 | Other packages can provide servers, but currently Paste Script 89 | includes glue for these: 90 | 91 | ``wsgiutils``: 92 | 93 | A `SimpleHTTPServer 94 | `_ 95 | based threaded HTTP server, using `WSGIUtils 96 | `_. 97 | 98 | ``flup_(scgi|fcgi|ajp)_(thread|fork)``: 99 | 100 | This set of servers supports `SCGI 101 | `_, `FastCGI 102 | `_ and `AJP 103 | `_ 104 | protocols, for connection an external web server (like Apache) to 105 | your application. Both threaded and forking versions are 106 | available. This is based on `flup 107 | `_. 108 | 109 | There is the start of support for `twisted.web2 110 | `_ in 111 | ``paste.script.twisted_web2_server``; patches welcome. 112 | 113 | Running the Server 114 | ------------------ 115 | 116 | ``paster serve --help`` gives useful output:: 117 | 118 | usage: /usr/local/bin/paster serve [options] CONFIG_FILE [start|stop|restart|status] 119 | Serve the described application 120 | 121 | If start/stop/restart is given, then it will start (normal 122 | operation), stop (--stop-daemon), or do both. You probably want 123 | ``--daemon`` as well for stopping. 124 | 125 | Options: 126 | -h, --help show this help message and exit 127 | -v, --verbose 128 | -q, --quiet 129 | -nNAME, --app-name=NAME 130 | Load the named application (default main) 131 | -sSERVER_TYPE, --server=SERVER_TYPE 132 | Use the named server. 133 | --server-name=SECTION_NAME 134 | Use the named server as defined in the configuration 135 | file (default: main) 136 | --daemon Run in daemon (background) mode 137 | --pid-file=FILENAME Save PID to file (default to paster.pid if running in 138 | daemon mode) 139 | --log-file=LOG_FILE Save output to the given log file (redirects stdout) 140 | --reload Use auto-restart file monitor 141 | --reload-interval=RELOAD_INTERVAL 142 | Seconds between checking files (low number can cause 143 | significant CPU usage) 144 | --status Show the status of the (presumably daemonized) server 145 | --user=USERNAME Set the user (usually only possible when run as root) 146 | --group=GROUP Set the group (usually only possible when run as root) 147 | --stop-daemon Stop a daemonized server (given a PID file, or default 148 | paster.pid file) 149 | 150 | Basically you give it a configuration file. If you don't do anything 151 | else, it'll serve the ``[app:main]`` application with the 152 | ``[server:main]`` server. You can pass in ``--server-name=foo`` to 153 | serve the ``[server:foo]`` section (or even 154 | ``--server-name=config:foo.ini`` to use a separate configuration 155 | file). 156 | 157 | Similarly you can use ``--app-name=foo`` to serve ``[app:foo]``. 158 | 159 | ``--daemon`` will run the server in the backgroup, ``--user`` and 160 | ``--group`` will set the user, as you might want to do from a start 161 | script (run as root). If you don't give a ``--pid-file`` it will 162 | write the pid to ``paster.pid`` (in the current directory). 163 | 164 | ``--stop-daemon`` will stop the daemon in ``paster.pid`` or whatever 165 | ``--pid-file`` you give. ``--log-file`` will redirect stdout and 166 | stderr to that file. 167 | 168 | ``--reload`` will start the reload monitor, and restart the server 169 | whenever a file is edited. This can be a little expensive, but is 170 | very useful during development. 171 | 172 | #! Scripts 173 | ---------- 174 | 175 | On Posix (Linux, Unix, etc) systems you can turn your configuration 176 | files into executable scripts. 177 | 178 | First make the file executable (``chmod +x config_file.ini``). The 179 | you should add a line like this to the top of the file:: 180 | 181 | #!/usr/bin/env paster 182 | 183 | You can include a command and command-line options in an ``[exe]`` 184 | section, like:: 185 | 186 | [exe] 187 | command = serve 188 | daemon = true 189 | user = nobody 190 | group = nobody 191 | 192 | (use ``true`` and ``false`` for options that don't take an argument). 193 | 194 | If you use ``daemon = true`` then you'll be able to use the script as 195 | an rc script, so you can do:: 196 | 197 | $ sudo ./config_file.ini start 198 | $ sudo ./config_file.ini restart 199 | 200 | and so forth. 201 | 202 | Note that this is a little wonky still on some platforms and shells 203 | (notably it doesn't work under `csh 204 | `_). If you get 205 | an error about "Command config_file.ini not known" then this probably 206 | won't work for you. In the future an additional script to ``paster`` 207 | will be added just for this purpose. 208 | -------------------------------------------------------------------------------- /docs/license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2007 Ian Bicking and Contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /docs/modules/checkperms.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.script.checkperms` -- functions to check and diff file permissions 2 | ============================================================================== 3 | 4 | .. automodule:: paste.script.checkperms 5 | 6 | .. contents:: 7 | 8 | Module Contents 9 | --------------- 10 | 11 | Permissions 12 | ~~~~~~~~~~~ 13 | 14 | .. autofunction:: read_perm_spec 15 | .. autofunction:: mode_diff 16 | .. autofunction:: calc_mode_diff 17 | .. autofunction:: calc_set_mode 18 | .. autofunction:: set_mode 19 | 20 | Ownership 21 | ~~~~~~~~~ 22 | 23 | .. autofunction:: calc_ownership_spec 24 | .. autofunction:: ownership_diff 25 | .. autofunction:: set_ownership 26 | 27 | Models 28 | ~~~~~~ 29 | 30 | .. autoclass:: PermissionSpec 31 | -------------------------------------------------------------------------------- /docs/modules/cherrypy_server.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.script.cherrypy_server` -- CherryPy WSGI server 2 | =========================================================== 3 | 4 | This is a slightly repackaged version of the WSGI HTTP server in 5 | `CherryPy `_. 6 | 7 | .. automodule:: paste.script.wsgiserver 8 | 9 | Module Contents 10 | --------------- 11 | 12 | .. autofunction:: cpwsgi_server 13 | 14 | -------------------------------------------------------------------------------- /docs/modules/command.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.script.command` -- framework for paster commands 2 | ============================================================ 3 | 4 | .. automodule:: paste.script.command 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoexception:: BadCommand 10 | .. autoclass:: Command 11 | 12 | -------------------------------------------------------------------------------- /docs/modules/copydir.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.script.copydir` -- recursive, interactive copy 2 | ========================================================== 3 | 4 | .. automodule:: paste.script.copydir 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autofunction:: copy_dir 10 | .. autoexception:: SkipTemplate 11 | .. autofunction:: query_interactive 12 | .. autoclass:: LaxTemplate 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/modules/filemaker.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.script.filemaker` -- interactive file writing helpers 2 | ================================================================= 3 | 4 | .. automodule:: paste.script.filemaker 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: FileOp 10 | -------------------------------------------------------------------------------- /docs/modules/templates.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.script.templates` -- framework for paster create templates 2 | ====================================================================== 3 | 4 | .. automodule:: paste.script.templates 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: Template 10 | .. autoclass:: var 11 | .. autoclass:: BasicPackage 12 | 13 | -------------------------------------------------------------------------------- /docs/modules/testapp.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.script.testapp` -- simple test WSGI application 2 | =========================================================== 3 | 4 | .. automodule:: paste.script.testapp 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: TestApplication 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/modules/util.secret.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.script.util.secret` -- create random secrets 2 | ======================================================== 3 | 4 | .. automodule:: paste.script.util.secret 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autofunction:: secret_string 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/news.txt: -------------------------------------------------------------------------------- 1 | News: Paste Script 2 | ================== 3 | 4 | .. contents:: 5 | 6 | 3.7.0 (2025-01-22) 7 | ------------------ 8 | 9 | * Open, read and write files in binary mode in copydir.py. 10 | * Fix locking for modern Python. 11 | 12 | 3.6.0 (2024-04-27) 13 | ------------------ 14 | 15 | * Pastescript is moved to https://github.com/pasteorg/pastescript 16 | 17 | With version 3.6.0 pastescript development moves to the pasteorg GitHub 18 | organization and will be going deeper into maintenance mode unless 19 | more active maintainers step forward to take over. "Deeper" in this 20 | case means that releases will be much less frequent and patches 21 | will only be accepted for security issues or major problems. Current 22 | consumers of pastescript should prepare to migrate away to more modern 23 | solutions. 24 | 25 | 3.5.1 (2024-03-15) 26 | ------------------ 27 | 28 | * Be explicit about Python 3 being required. 29 | 30 | 3.5.0 (2024-03-14) 31 | ------------------ 32 | 33 | * Remove remains of Python 2 in code. Thanks to Alexandre Detiste (a-detiste) 34 | 35 | 3.4.0 (2024-01-22) 36 | ------------------ 37 | 38 | * Correct working with PasteDeploy >=3. Thanks brondsem. 39 | 40 | 3.3.0 (2023-01-03) 41 | ------------------ 42 | 43 | * Remove support for Python 2 in tests. It may still work outside tests. 44 | * Fix homepage link. Thanks to Guillaume Gauvrit (mardiros). 45 | * Stop using nose to run tests. 46 | * Run tests in GitHub actions instead of travis CI. 47 | 48 | 3.2.1 (2021-04-27) 49 | ------------------ 50 | 51 | * Require ``setuptools`` in ``install_requires``. Thanks to Tomáš Hrnčiar 52 | (hrnciar) 53 | * Fix tests to run again. 54 | 55 | 3.2.0 (2019-09-24) 56 | ------------------ 57 | 58 | * Use wsgiserver.WSGIServer instead of wsgiutils.wsgiServer.WSGIServer 59 | for Python 3 compatibility. 60 | 61 | 3.1.0 (2019-03-04) 62 | ----------------- 63 | 64 | * Remove dependency on ``unittest2``. 65 | 66 | 3.0.0 (2018-11-26) 67 | ------------------ 68 | 69 | * Moved to `GitHub `_. 70 | * Put into maintenance mode, meaning: critical bugs will be fixed, 71 | and support for new versions of Python will be handled, but new 72 | features are not being considered. 73 | 74 | 2.0.2 (2015-05-27) 75 | ------------------ 76 | 77 | * Issue #20: Fix "paster points --list" on Python 3. 78 | 79 | * Fix cgi_server on Python 3. 80 | 81 | * Fix usage of the sort() method on Python 3. 82 | 83 | * Fix grep on Python 3. 84 | 85 | 2.0.1 (2015-05-26) 86 | ------------------ 87 | 88 | * Fix --help command on Python 3. Patch written by Atsushi Odagiri (aodag). 89 | 90 | * Fix "paster create --template=basic_package test" command on Python 3. 91 | Patch written by Atsushi Odagiri (aodag). 92 | 93 | * Fix error when 'paster create --list-template' on Python 3. Patch written by 94 | FGtatsuro. 95 | 96 | * Create universal wheel package. 97 | 98 | 2.0 (2015-05-26) 99 | ---------------- 100 | 101 | * Experimental Python 3 support. 102 | 103 | * six module is now required. 104 | 105 | * Drop support of Python 2.5 and older 106 | 107 | 1.7.5 108 | ----- 109 | 110 | * Import CherryPy directly instead of including its server inline in the 111 | ``paste.script`` package. You must install CherryPy before using 112 | ``egg:PasteScript#cherrypy`` 113 | 114 | 1.7.4.2 115 | ------- 116 | 117 | * 1.7.4 had package release problems, was reverted; 1.7.4.1 also had 118 | package problems. 119 | 120 | * Include special ``here`` and ``__file__`` default vars for logging 121 | config files, similar to PasteDeploy config loading. 122 | 123 | * Allow Jython to import various bits from ``paste.script.command`` and 124 | ``paste.script.copydir`` without throwing an import error (subprocess 125 | module cannot be imported on Jython). This allows PasteScript to work 126 | minimally on Jython, although execution will fail for 127 | ``command.run_command`` and ``copydir.copydir``. 128 | 129 | 1.7.3 130 | ----- 131 | 132 | * CherryPy wsgiserver updated to 3.1.1, fixes a regression in Python 133 | 2.5 plus a couple other small fixes. 134 | 135 | 1.7.2 136 | ----- 137 | 138 | * Fix a packaging issue that could cause problems when installing 139 | PasteScript. 140 | 141 | 1.7.1 142 | ----- 143 | 144 | * filemaker.py's FileOp can now handle a tuple as a source_dir argument that 145 | should function the same as the _template_dir option for pkg_resources. 146 | 147 | * CherryPy wsgiserver updated to trunk@2063 for Python 2.6 148 | compatibility. 149 | 150 | 1.7 151 | --- 152 | 153 | * _template_dir now takes a tuple argument, which should be the 154 | package name, and the relative location to the package of its template 155 | directory. pkg_resources will then be used to load make the templates 156 | rather than raw file access making it zip-safe. 157 | 158 | * CherryPy wsgiserver updated to the 3.1.0 release's. 159 | 160 | * Support Python 2.6. 161 | 162 | * Added experimental support for a quicker paster serve --reload for 163 | Jython. 164 | 165 | * Non-Python files in ``paste/script/templates/`` causes an error on 166 | 2.6; renamed directory to avoid this. 167 | 168 | 1.6.3 169 | ----- 170 | 171 | * Fixes for ``paste.script.filemaker`` 172 | 173 | * A setuptools ``egg_info.writers`` entry point is now provided that's 174 | responsible for writing paster_plugins.txt for projects that define 175 | a new ``paster_plugins`` setup() keyword. paster_plugins.txt will 176 | still be created for new projects that need it and lack a 177 | ``paster_plugins`` setup() keyword, but this is deprecated. Projects 178 | defining ``paster_plugins`` in setup() should also define a 179 | ``setup_requires`` setup() keyword including PasteScript. 180 | 181 | * An ``egg_plugins`` variable (a list of strings based off the 182 | Templates classes' ``egg_plugins`` variables) is now available to 183 | paster create templates for the new ``paster_plugins`` setup() 184 | keyword. 185 | 186 | * PasteScript is no longer included in 187 | ``egg_plugins``/paster_plugins.txt by default. 188 | 189 | 1.6.2 190 | ----- 191 | 192 | * Fix SkipTemplate (could raise ``TypeError: argument 1 must be string 193 | or read-only buffer, not None`` before) 194 | 195 | 1.6.1 (and 1.6.1.1) 196 | ------------------- 197 | 198 | * Fix paster serve under Windows. 199 | 200 | 1.6 201 | --- 202 | 203 | * Added commands ``paster request config.ini URL`` and ``paster post 204 | config.ini URL < post-body``, that allow you to do artificial 205 | requests to applications. 206 | 207 | * Check the writability of the pid and log files earlier. This caused 208 | particular problems if you started it in daemon mode, and the files 209 | weren't writable. From Chris Atlee. 210 | 211 | * Start the monitor (when using ``--monitor``) after daemonizing, so 212 | that ``paster serve --monitor --daemon`` works (before it would 213 | constantly restart). 214 | 215 | * In Paste Script templates, you can give ``should_echo=False`` in 216 | variable definitions, and if the user is queried for the variable 217 | then the input will not be echoed (as for a password). From Dirceu 218 | Pereira Tiegs. 219 | 220 | * Added a method 221 | ``paste.script.appinstall.Installer.template_renderer``, which can 222 | be used to override template substitution with ``paster 223 | make-config``. The function is similar to the same function used 224 | with ``paster create`` templates. 225 | 226 | * Remove ``--daemon`` option from Windows, as it depends on 227 | ``os.fork`` 228 | 229 | * When using ``paster create`` and inserting text with a ``-*-`` 230 | marker, multi-line text will no longer be reinserted. 231 | 232 | * Improved output when skipping templates with ``paster create``. 233 | 234 | * When starting a server with ``paster serve --daemon`` and the pid 235 | file exists and describes a running process, do not start another 236 | process. 237 | 238 | * Added ``umask`` option to 239 | ``egg:PasteScript#flup_fcgi_thread/fork``. 240 | 241 | * Deprecate the flup entry points, as flup now has the necessary entry 242 | points in its own package. 243 | 244 | 1.3.6 245 | ----- 246 | 247 | * CherryPy wsgiserver updated to the 3.0.2 release's 248 | 249 | * paster no longer hides ``pkg_resources.DistributionNotFound`` error 250 | messages describing the target project's requirements. Aids the 251 | somewhat confusing "did you run setup.py develop?" message when it had 252 | already been ran, but since then had a requirement added that wasn't 253 | installed. 254 | 255 | * Logging configuration is now read from the config file during ``paster 256 | setup-app``. 257 | 258 | * Custom Formatters and Handlers (Handlers outside of the logging module) 259 | are now supported in logging configuration files. 260 | 261 | 1.3.5 262 | ----- 263 | 264 | * Initialize logging earlier in the serve command for components that want 265 | to utilize it. Patch from Christopher Lenz. 266 | 267 | * Fixed Python 2.3 incompatibility (no ``string.Template``) in 268 | ``paste.script.appinstall``. 269 | 270 | 1.3.4 271 | ----- 272 | 273 | * Make sure that when using ``--monitor`` or ``--reload``, if the 274 | parent monitoring process dies, also kill the subprocess. 275 | * When using ``paster serve --log-file``, append to the log file (was 276 | truncating any previous contents). 277 | 278 | * Read logging information from the server file, using the logging module's 279 | `standard configuration format 280 | `_ 281 | 282 | * When adding files don't fail because an svn command fails. Helpful 283 | particularly on Windows, when the svn command-line isn't installed 284 | (e.g., using TortoiseSVN). 285 | 286 | 1.3.3 287 | ----- 288 | 289 | * Fixed problem with ``paster serve`` on Windows. Also on Windows, 290 | fixed issue with executables with spaces in their names (this case 291 | requires the ``win32all`` module). 292 | 293 | * You can use ``+dot+`` in your project template filenames, 294 | specifically so that you can use leading dots in the filename. 295 | Usually leading dots cause the file to be ignored. So if you want 296 | to have new projects contain a ``.cvsignore`` file, you can put a 297 | ``+dot+cvsignore`` file in your template. 298 | 299 | * Relatedly, ``+plus+`` has been added so you can include pluses. 300 | 301 | 1.3.2 302 | ----- 303 | 304 | * ``paster`` was largely broken under Windows; fixed. 305 | 306 | 1.3.1 307 | ----- 308 | 309 | * Fix related to Python 2.5 (when there are errors creating files, you 310 | could get infinite recursion under Python 2.5). 311 | 312 | * Use ``subprocess`` module in ``paster serve`` command. Added 313 | ``--monitor`` option which will restart the server if it exits. 314 | 315 | * The ``exe`` command now does % substitution in keys (e.g., 316 | ``pid_file=%(here)s/paste.pid``). 317 | 318 | * Some import problems with Cheetah should be improved. 319 | 320 | 1.3 321 | --- 322 | 323 | * Fixed an exception being raised when shutting down flup servers using 324 | sockets. 325 | 326 | * Fixed the CherryPy 3 WSGI server entry point's handling of SIGHUP 327 | and SIGTERM. 328 | 329 | * The CherryPy wsgiserver is now available at 330 | ``paste.script.wsgiserver`` (no longer requiring CherryPy to be 331 | installed). 332 | 333 | * Added entry point for twisted server. 334 | 335 | * Made ``paste.script.pluginlib:egg_info_dir`` work with packages that 336 | put the ``Package.egg-info/`` directory in a subdirectory (typically 337 | ``src/``). 338 | 339 | * Remove Cheetah requirement. Packages using Cheetah templates should 340 | require Cheetah themselves. If you are using ``paster make-config`` 341 | and you *don't* want to use Cheetah, you must add ``use_cheetah = 342 | False`` to your ``Installer`` subclass (it defaults to true for 343 | backward compatibility). 344 | 345 | * Make scripts work when there is no ``setup.py`` (if you aren't 346 | making a Python/setuptools package). 347 | 348 | * When using ``paste.script.copydir.copy_dir`` (as with most ``paster 349 | create`` templates), you can raise ``SkipTemplate`` (or call the 350 | ``skip_template()`` function) which will cause the template to be 351 | skipped. You can use this to conditionally include files. 352 | 353 | * When using ``paster serve c:/...``, it should no longer confuse 354 | ``c:`` with a scheme (such as ``config:`` or ``egg:``). 355 | 356 | * More careful about catching import errors in ``websetup``, so if you 357 | have a bug in your ``app.websetup`` module it won't swallow it. 358 | 359 | 1.1 360 | --- 361 | 362 | * Added a ``warn_returncode`` option to ``Command.run_command``, and 363 | make ``ensure_file`` use this for its svn command. 364 | 365 | * If the svn command-line program isn't working for you, commands that 366 | use ``ensure_file`` will continue to work but just with a warning. 367 | 368 | * Removed copyright notice that was accidentally included in new 369 | packages created by ``paster create``. 370 | 371 | * Allow variable assignments at the end of ``paster serve``, like 372 | ``paster serve http_port=80``; then you can use ``%(http_port)s`` in 373 | your config files (requires up-to-date Paste Deploy). 374 | 375 | * Allow a ``package_dir`` variable so that you can put your package 376 | into subdirectories of the base directory (e.g., ``src/``). 377 | 378 | * Changes to the ``twisted.web2`` wrapper (from pythy). 379 | 380 | * Warn when you run ``paster setup-app`` and no modules are listed in 381 | ``top_level.txt`` (which can happen with a newly checked out 382 | project). 383 | 384 | 1.0 385 | --- 386 | 387 | * Added entry point for CherryPy 3's WSGI server. 388 | 389 | * Fixed ``paster serve`` to hide KeyboardInterrupt (CTRL-C) tracebacks 390 | in ``--reload`` mode. 391 | 392 | * Added ``template_renderer`` argument to 393 | ``paste.script.copydir.copydir``. This allows you to use arbitrary 394 | template languages, instead of just ``string.Template`` and Cheetah. 395 | 396 | 0.9.9 397 | ----- 398 | 399 | * egg:PasteScript#test (the paste.script.testapp) now accepts ``lint`` 400 | and ``text`` boolean configuration. ``lint`` will turn on 401 | ``paste.lint`` validation. ``text`` will cause it to return a 402 | simple text/plain response with the environ, instead of an HTML 403 | table. 404 | 405 | * Improvements all around to ``paster points``, plus documentation for 406 | existing entry point groups. 407 | 408 | 0.9.8 409 | ----- 410 | 411 | * New projects will now ignore 412 | ``Package.egg-info/dependency_links.txt``, just like all the other 413 | derivative files in the ``egg-info`` directory 414 | 415 | * ``paster serve --reload`` was broken on Windows when the Python 416 | executable was in a directory with spaces in it. This is probably a 417 | bug in the ``subprocess`` module. 418 | 419 | 0.9.7 420 | ----- 421 | 422 | * Update to filemaker commands to take optional argument so that when 423 | new directories are for a Python package, they will have a __init__.py 424 | created as well. 425 | 426 | 0.9.6 427 | ----- 428 | 429 | * Do all variable assignment during package creation up-front, before 430 | actually creating the files. 431 | 432 | * Added the ``egg`` template variable: provides projects with a safe 433 | egg name as generated by setuptools. This should be used for 434 | egg-info directories in templates (e.g. ``+egg+.egg-info`` instead 435 | of ``+project+.egg-info``), and anywhere else the egg name is 436 | expected, to prevent errors with project names containing hyphens. 437 | 438 | 0.9 439 | --- 440 | 441 | * Installer calls ``websetup.setup_app(command, conf, vars)``; 442 | ``setup_config()`` will be deprecated in the future 443 | 444 | * Added copyright information 445 | 446 | * ``paster serve config.ini#section`` works now 447 | 448 | * ``paster make-config/setup-app`` will read ``$PASTE_SYSCONFIG`` to 449 | find extra ``sysconfig.py`` files. 450 | 451 | * ``paster create`` will now query interactively for variables if they 452 | aren't explicitly provided. 453 | 454 | 0.5 455 | --- 456 | 457 | * If the output directory doesn't exist when running ``paster 458 | create``, do not default to having interactive (confirmation) on. 459 | 460 | 0.4.2 461 | ----- 462 | 463 | * Fixed the Flup FastCGI interface. (There seem to still be problems 464 | with forking FastCGI.) 465 | 466 | * The ``prepare-app`` command has been renamed ``make-config`` 467 | 468 | * Changed the way ``make-config`` and ``setup-app`` use ``sysconfig`` 469 | -- these are now modules that can define various functions 470 | 471 | * Allow for default config file names 472 | 473 | * Consider config generation that may produce a directory (this case 474 | is now generally workable) 475 | 476 | * Allow for multiple config files (specifically with --edit) 477 | 478 | * Give config file generation the variables ``app_install_uuid`` and 479 | ``app_install_secret`` that they can use for their config files 480 | 481 | * Include Ka-Ping Yee's uuid module in ``paste.script.util.uuid`` 482 | 483 | * ``paster help`` doesn't bail when commands can't be loaded 484 | 485 | * Be a little safer when ``--edit`` fails and ``--setup`` is provided 486 | (don't automatically set up if the edit seems to have failed) 487 | 488 | 0.4.1 489 | ----- 490 | 491 | * Two small bugfixes, one related to the ``basic_package`` template 492 | (it included a reference to ``finddata``, which it should not have), 493 | and a fix to how the ``.egg-info`` directory is determined. 494 | 495 | 0.4 496 | --- 497 | 498 | * Added ``points`` command, for entry-point related queries. 499 | 500 | * paste.deploy config files that start with ``#!/usr/bin/env paster`` 501 | can make a script into an executable. 502 | 503 | * Improvements to ``paster serve`` command: 504 | 505 | - Handle bad PID files better 506 | 507 | - Daemonization is more reliable 508 | 509 | - Allow ``start``, ``stop``, ``restart`` instead of just options 510 | 511 | * Improvements to ``paster create`` command: 512 | 513 | - Invoked interactively by default (so that you are warned before 514 | overwriting files) 515 | 516 | * Added new commands: 517 | 518 | - ``points`` for viewing Egg entry point information 519 | 520 | - ``prepare-app`` and ``setup-app`` for installing web applications 521 | 522 | * Fixed bug in how Egg distributions are loaded. 523 | 524 | 0.3.1 525 | ----- 526 | 527 | * Fixed small bug with running ``paster serve`` on Windows. (Small to 528 | fix, kept script from running on Windows entirely). 529 | 530 | 0.3 531 | --- 532 | 533 | Initial release. 534 | -------------------------------------------------------------------------------- /paste/__init__.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | try: 4 | import pkg_resources 5 | pkg_resources.declare_namespace(__name__) 6 | except ImportError: 7 | # don't prevent use of paste if pkg_resources isn't installed 8 | from pkgutil import extend_path 9 | __path__ = extend_path(__path__, __name__) 10 | 11 | try: 12 | import modulefinder 13 | except ImportError: 14 | pass 15 | else: 16 | for p in __path__: 17 | modulefinder.AddPackagePath(__name__, p) 18 | 19 | -------------------------------------------------------------------------------- /paste/script/__init__.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | # 4 | -------------------------------------------------------------------------------- /paste/script/bool_optparse.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | """ 4 | A subclass of ``optparse.OptionParser`` that allows boolean long 5 | options (like ``--verbose``) to also take arguments (like 6 | ``--verbose=true``). Arguments *must* use ``=``. 7 | """ 8 | 9 | import optparse 10 | try: 11 | _ = optparse._ 12 | except AttributeError: 13 | from gettext import gettext as _ 14 | 15 | class BoolOptionParser(optparse.OptionParser): 16 | 17 | def _process_long_opt(self, rargs, values): 18 | arg = rargs.pop(0) 19 | 20 | # Value explicitly attached to arg? Pretend it's the next 21 | # argument. 22 | if "=" in arg: 23 | (opt, next_arg) = arg.split("=", 1) 24 | rargs.insert(0, next_arg) 25 | had_explicit_value = True 26 | else: 27 | opt = arg 28 | had_explicit_value = False 29 | 30 | opt = self._match_long_opt(opt) 31 | option = self._long_opt[opt] 32 | if option.takes_value(): 33 | nargs = option.nargs 34 | if len(rargs) < nargs: 35 | if nargs == 1: 36 | self.error(_("%s option requires an argument") % opt) 37 | else: 38 | self.error(_("%s option requires %d arguments") 39 | % (opt, nargs)) 40 | elif nargs == 1: 41 | value = rargs.pop(0) 42 | else: 43 | value = tuple(rargs[0:nargs]) 44 | del rargs[0:nargs] 45 | 46 | elif had_explicit_value: 47 | value = rargs[0].lower().strip() 48 | del rargs[0:1] 49 | if value in ('true', 'yes', 'on', '1', 'y', 't'): 50 | value = None 51 | elif value in ('false', 'no', 'off', '0', 'n', 'f'): 52 | # Don't process 53 | return 54 | else: 55 | self.error(_('%s option takes a boolean value only (true/false)') % opt) 56 | 57 | else: 58 | value = None 59 | 60 | option.process(opt, value, values, self) 61 | -------------------------------------------------------------------------------- /paste/script/cgi_server.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | import os 4 | import sys 5 | 6 | ## FIXME: this should be deprecated in favor of wsgiref 7 | 8 | def paste_run_cgi(wsgi_app, global_conf): 9 | run_with_cgi(wsgi_app) 10 | 11 | stdout = sys.__stdout__ 12 | stdout = stdout.buffer 13 | 14 | # Taken from the WSGI spec: 15 | 16 | def run_with_cgi(application): 17 | 18 | environ = dict(os.environ.items()) 19 | environ['wsgi.input'] = sys.stdin 20 | environ['wsgi.errors'] = sys.stderr 21 | environ['wsgi.version'] = (1,0) 22 | environ['wsgi.multithread'] = False 23 | environ['wsgi.multiprocess'] = True 24 | environ['wsgi.run_once'] = True 25 | 26 | if environ.get('HTTPS','off') in ('on','1'): 27 | environ['wsgi.url_scheme'] = 'https' 28 | else: 29 | environ['wsgi.url_scheme'] = 'http' 30 | 31 | headers_set = [] 32 | headers_sent = [] 33 | 34 | def write(data): 35 | if not headers_set: 36 | raise AssertionError("write() before start_response()") 37 | 38 | elif not headers_sent: 39 | # Before the first output, send the stored headers 40 | status, response_headers = headers_sent[:] = headers_set 41 | line = 'Status: %s\r\n' % status 42 | line = line.encode('utf-8') 43 | stdout.write(line) 44 | for header in response_headers: 45 | line = '%s: %s\r\n' % header 46 | line = line.encode('utf-8') 47 | stdout.write(line) 48 | stdout.write(b'\r\n') 49 | 50 | stdout.write(data) 51 | stdout.flush() 52 | 53 | def start_response(status,response_headers,exc_info=None): 54 | if exc_info: 55 | try: 56 | if headers_sent: 57 | # Re-raise original exception if headers sent 58 | raise exc_info 59 | finally: 60 | exc_info = None # avoid dangling circular ref 61 | elif headers_set: 62 | raise AssertionError("Headers already set!") 63 | 64 | headers_set[:] = [status,response_headers] 65 | return write 66 | 67 | result = application(environ, start_response) 68 | try: 69 | for data in result: 70 | if data: # don't send headers until body appears 71 | write(data) 72 | if not headers_sent: 73 | write('') # send headers now if body was empty 74 | finally: 75 | if hasattr(result,'close'): 76 | result.close() 77 | -------------------------------------------------------------------------------- /paste/script/checkperms.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | """ 4 | This is a module to check the filesystem for the presence and 5 | permissions of certain files. It can also be used to correct the 6 | permissions (but not existance) of those files. 7 | 8 | Currently only supports Posix systems (with Posixy permissions). 9 | Permission stuff can probably be stubbed out later. 10 | """ 11 | import os 12 | 13 | def read_perm_spec(spec): 14 | """ 15 | Reads a spec like 'rw-r--r--' into a octal number suitable for 16 | chmod. That is characters in groups of three -- first group is 17 | user, second for group, third for other (all other people). The 18 | characters are r (read), w (write), and x (executable), though the 19 | executable can also be s (sticky). Files in sticky directories 20 | get the directories permission setting. 21 | 22 | Examples:: 23 | 24 | >>> print oct(read_perm_spec('rw-r--r--')) 25 | 0o644 26 | >>> print oct(read_perm_spec('rw-rwsr--')) 27 | 0o2664 28 | >>> print oct(read_perm_spec('r-xr--r--')) 29 | 0o544 30 | >>> print oct(read_perm_spec('r--------')) 31 | 0o400 32 | """ 33 | total_mask = 0 34 | # suid/sgid modes give this mask in user, group, other mode: 35 | set_bits = (0o4000, 0o2000, 0) 36 | pieces = (spec[0:3], spec[3:6], spec[6:9]) 37 | for i, (mode, set_bit) in enumerate(zip(pieces, set_bits)): 38 | mask = 0 39 | read, write, exe = list(mode) 40 | if read == 'r': 41 | mask = mask | 4 42 | elif read != '-': 43 | raise ValueError( 44 | "Character %r unexpected (should be '-' or 'r')" 45 | % read) 46 | if write == 'w': 47 | mask = mask | 2 48 | elif write != '-': 49 | raise ValueError( 50 | "Character %r unexpected (should be '-' or 'w')" 51 | % write) 52 | if exe == 'x': 53 | mask = mask | 1 54 | elif exe not in ('s', '-'): 55 | raise ValueError( 56 | "Character %r unexpected (should be '-', 'x', or 's')" 57 | % exe) 58 | if exe == 's' and i == 2: 59 | raise ValueError(( 60 | "The 'other' executable setting cannot be suid/sgid ('s')")) 61 | mask = mask << ((2-i)*3) 62 | if exe == 's': 63 | mask = mask | set_bit 64 | total_mask = total_mask | mask 65 | return total_mask 66 | 67 | modes = [ 68 | (0o4000, 'setuid bit', 69 | 'setuid bit: make contents owned by directory owner'), 70 | (0o2000, 'setgid bit', 71 | 'setgid bit: make contents inherit permissions from directory'), 72 | (0o1000, 'sticky bit', 73 | 'sticky bit: append-only directory'), 74 | (0o0400, 'read by owner', 'read by owner'), 75 | (0o0200, 'write by owner', 'write by owner'), 76 | (0o0100, 'execute by owner', 'owner can search directory'), 77 | (0o0040, 'allow read by group members', 78 | 'allow read by group members',), 79 | (0o0020, 'allow write by group members', 80 | 'allow write by group members'), 81 | (0o0010, 'execute by group members', 82 | 'group members can search directory'), 83 | (0o0004, 'read by others', 'read by others'), 84 | (0o0002, 'write by others', 'write by others'), 85 | (0o0001, 'execution by others', 'others can search directory'), 86 | ] 87 | 88 | exe_bits = [0o100, 0o010, 0o001] 89 | exe_mask = 0o111 90 | full_mask = 0o7777 91 | 92 | def mode_diff(filename, mode, **kw): 93 | """ 94 | Returns the differences calculated using ``calc_mode_diff`` 95 | """ 96 | cur_mode = os.stat(filename).st_mode 97 | return calc_mode_diff(cur_mode, mode, **kw) 98 | 99 | def calc_mode_diff(cur_mode, mode, keep_exe=True, 100 | not_set='not set: ', 101 | set='set: '): 102 | """ 103 | Gives the difference between the actual mode of the file and the 104 | given mode. If ``keep_exe`` is true, then if the mode doesn't 105 | include any executable information the executable information will 106 | simply be ignored. High bits are also always ignored (except 107 | suid/sgid and sticky bit). 108 | 109 | Returns a list of differences (empty list if no differences) 110 | """ 111 | for exe_bit in exe_bits: 112 | if mode & exe_bit: 113 | keep_exe = False 114 | diffs = [] 115 | isdir = os.path.isdir(filename) 116 | for bit, file_desc, dir_desc in modes: 117 | if keep_exe and bit in exe_bits: 118 | continue 119 | if isdir: 120 | desc = dir_desc 121 | else: 122 | desc = file_desc 123 | if (mode & bit) and not (cur_mode & bit): 124 | diffs.append(not_set + desc) 125 | if not (mode & bit) and (cur_mode & bit): 126 | diffs.append(set + desc) 127 | return diffs 128 | 129 | def calc_set_mode(cur_mode, mode, keep_exe=True): 130 | """ 131 | Calculates the new mode given the current node ``cur_mode`` and 132 | the mode spec ``mode`` and if ``keep_exe`` is true then also keep 133 | the executable bits in ``cur_mode`` if ``mode`` has no executable 134 | bits in it. Return the new mode. 135 | 136 | Examples:: 137 | 138 | >>> print oct(calc_set_mode(0o775, 0o644)) 139 | 0o755 140 | >>> print oct(calc_set_mode(0o775, 0o744)) 141 | 0o744 142 | >>> print oct(calc_set_mode(0o10600, 0o644)) 143 | 0o10644 144 | >>> print oct(calc_set_mode(0o775, 0o644, False)) 145 | 0o644 146 | """ 147 | for exe_bit in exe_bits: 148 | if mode & exe_bit: 149 | keep_exe = False 150 | # This zeros-out full_mask parts of the current mode: 151 | keep_parts = (cur_mode | full_mask) ^ full_mask 152 | if keep_exe: 153 | keep_parts = keep_parts | (cur_mode & exe_mask) 154 | new_mode = keep_parts | mode 155 | return new_mode 156 | 157 | def set_mode(filename, mode, **kw): 158 | """ 159 | Sets the mode on ``filename`` using ``calc_set_mode`` 160 | """ 161 | cur_mode = os.stat(filename).st_mode 162 | new_mode = calc_set_mode(cur_mode, mode, **kw) 163 | os.chmod(filename, new_mode) 164 | 165 | def calc_ownership_spec(spec): 166 | """ 167 | Calculates what a string spec means, returning (uid, username, 168 | gid, groupname), where there can be None values meaning no 169 | preference. 170 | 171 | The spec is a string like ``owner:group``. It may use numbers 172 | instead of user/group names. It may leave out ``:group``. It may 173 | use '-' to mean any-user/any-group. 174 | 175 | """ 176 | import grp 177 | import pwd 178 | user = group = None 179 | uid = gid = None 180 | if ':' in spec: 181 | user_spec, group_spec = spec.split(':', 1) 182 | else: 183 | user_spec, group_spec = spec, '-' 184 | if user_spec == '-': 185 | user_spec = '0' 186 | if group_spec == '-': 187 | group_spec = '0' 188 | try: 189 | uid = int(user_spec) 190 | except ValueError: 191 | uid = pwd.getpwnam(user_spec) 192 | user = user_spec 193 | else: 194 | if not uid: 195 | uid = user = None 196 | else: 197 | user = pwd.getpwuid(uid).pw_name 198 | try: 199 | gid = int(group_spec) 200 | except ValueError: 201 | gid = grp.getgrnam(group_spec) 202 | group = group_spec 203 | else: 204 | if not gid: 205 | gid = group = None 206 | else: 207 | group = grp.getgrgid(gid).gr_name 208 | return (uid, user, gid, group) 209 | 210 | def ownership_diff(filename, spec): 211 | """ 212 | Return a list of differences between the ownership of ``filename`` 213 | and the spec given. 214 | """ 215 | import grp 216 | import pwd 217 | diffs = [] 218 | uid, user, gid, group = calc_ownership_spec(spec) 219 | st = os.stat(filename) 220 | if uid and uid != st.st_uid: 221 | diffs.append('owned by %s (should be %s)' % 222 | (pwd.getpwuid(st.st_uid).pw_name, user)) 223 | if gid and gid != st.st_gid: 224 | diffs.append('group %s (should be %s)' % 225 | (grp.getgrgid(st.st_gid).gr_name, group)) 226 | return diffs 227 | 228 | def set_ownership(filename, spec): 229 | """ 230 | Set the ownership of ``filename`` given the spec. 231 | """ 232 | uid, user, gid, group = calc_ownership_spec(spec) 233 | st = os.stat(filename) 234 | if not uid: 235 | uid = st.st_uid 236 | if not gid: 237 | gid = st.st_gid 238 | os.chmod(filename, uid, gid) 239 | 240 | class PermissionSpec(object): 241 | """ 242 | Represents a set of specifications for permissions. 243 | 244 | Typically reads from a file that looks like this:: 245 | 246 | rwxrwxrwx user:group filename 247 | 248 | If the filename ends in /, then it expected to be a directory, and 249 | the directory is made executable automatically, and the contents 250 | of the directory are given the same permission (recursively). By 251 | default the executable bit on files is left as-is, unless the 252 | permissions specifically say it should be on in some way. 253 | 254 | You can use 'nomodify filename' for permissions to say that any 255 | permission is okay, and permissions should not be changed. 256 | 257 | Use 'noexist filename' to say that a specific file should not 258 | exist. 259 | 260 | Use 'symlink filename symlinked_to' to assert a symlink destination 261 | 262 | The entire file is read, and most specific rules are used for each 263 | file (i.e., a rule for a subdirectory overrides the rule for a 264 | superdirectory). Order does not matter. 265 | """ 266 | 267 | def __init__(self): 268 | self.paths = {} 269 | 270 | def parsefile(self, filename): 271 | f = open(filename) 272 | lines = f.readlines() 273 | f.close() 274 | self.parselines(lines, filename=filename) 275 | 276 | commands = {} 277 | 278 | def parselines(self, lines, filename=None): 279 | for lineindex, line in enumerate(lines): 280 | line = line.strip() 281 | if not line or line.startswith('#'): 282 | continue 283 | parts = line.split() 284 | command = parts[0] 285 | if command in self.commands: 286 | cmd = self.commands[command](*parts[1:]) 287 | else: 288 | cmd = self.commands['*'](*parts) 289 | self.paths[cmd.path] = cmd 290 | 291 | def check(self): 292 | action = _Check(self) 293 | self.traverse(action) 294 | 295 | def fix(self): 296 | action = _Fixer(self) 297 | self.traverse(action) 298 | 299 | def traverse(self, action): 300 | paths = self.paths_sorted() 301 | checked = {} 302 | for path, checker in list(paths)[::-1]: 303 | self.check_tree(action, path, paths, checked) 304 | for path, checker in paths: 305 | if path not in checked: 306 | action.noexists(path, checker) 307 | 308 | def traverse_tree(self, action, path, paths, checked): 309 | if path in checked: 310 | return 311 | self.traverse_path(action, path, paths, checked) 312 | if os.path.isdir(path): 313 | for fn in os.listdir(path): 314 | fn = os.path.join(path, fn) 315 | self.traverse_tree(action, fn, paths, checked) 316 | 317 | def traverse_path(self, action, path, paths, checked): 318 | checked[path] = None 319 | for check_path, checker in paths: 320 | if path.startswith(check_path): 321 | action.check(check_path, checker) 322 | if not checker.inherit: 323 | break 324 | 325 | def paths_sorted(self): 326 | paths = sorted(self.paths.items(), 327 | key=lambda key_value: len(key_value[0]), 328 | reversed=True) 329 | 330 | class _Rule(object): 331 | class __metaclass__(type): 332 | def __new__(meta, class_name, bases, d): 333 | cls = type.__new__(meta, class_name, bases, d) 334 | PermissionSpec.commands[cls.__name__] = cls 335 | return cls 336 | 337 | inherit = False 338 | def noexists(self): 339 | return ['Path %s does not exist' % path] 340 | 341 | class _NoModify(_Rule): 342 | 343 | name = 'nomodify' 344 | 345 | def __init__(self, path): 346 | self.path = path 347 | 348 | def fix(self, path): 349 | pass 350 | 351 | class _NoExist(_Rule): 352 | 353 | name = 'noexist' 354 | 355 | def __init__(self, path): 356 | self.path = path 357 | 358 | def check(self, path): 359 | return ['Path %s should not exist' % path] 360 | 361 | def noexists(self, path): 362 | return [] 363 | 364 | def fix(self, path): 365 | # @@: Should delete? 366 | pass 367 | 368 | class _SymLink(_Rule): 369 | 370 | name = 'symlink' 371 | inherit = True 372 | 373 | def __init__(self, path, dest): 374 | self.path = path 375 | self.dest = dest 376 | 377 | def check(self, path): 378 | assert path == self.path, ( 379 | "_Symlink should only be passed specific path %s (not %s)" 380 | % (self.path, path)) 381 | try: 382 | link = os.path.readlink(path) 383 | except OSError as e: 384 | if e.errno != 22: 385 | raise 386 | return ['Path %s is not a symlink (should point to %s)' 387 | % (path, self.dest)] 388 | if link != self.dest: 389 | return ['Path %s should symlink to %s, not %s' 390 | % (path, self.dest, link)] 391 | return [] 392 | 393 | def fix(self, path): 394 | assert path == self.path, ( 395 | "_Symlink should only be passed specific path %s (not %s)" 396 | % (self.path, path)) 397 | if not os.path.exists(path): 398 | os.symlink(path, self.dest) 399 | else: 400 | # @@: This should correct the symlink or something: 401 | print('Not symlinking %s' % path) 402 | 403 | class _Permission(_Rule): 404 | 405 | name = '*' 406 | 407 | def __init__(self, perm, owner, dir): 408 | self.perm_spec = read_perm_spec(perm) 409 | self.owner = owner 410 | self.dir = dir 411 | 412 | def check(self, path): 413 | return mode_diff(path, self.perm_spec) 414 | 415 | def fix(self, path): 416 | set_mode(path, self.perm_spec) 417 | 418 | class _Strategy(object): 419 | 420 | def __init__(self, spec): 421 | self.spec = spec 422 | 423 | class _Check(_Strategy): 424 | 425 | def noexists(self, path, checker): 426 | checker.noexists(path) 427 | 428 | def check(self, path, checker): 429 | checker.check(path) 430 | 431 | class _Fixer(_Strategy): 432 | 433 | def noexists(self, path, checker): 434 | pass 435 | 436 | def check(self, path, checker): 437 | checker.fix(path) 438 | 439 | if __name__ == '__main__': 440 | import doctest 441 | doctest.testmod() 442 | 443 | -------------------------------------------------------------------------------- /paste/script/cherrypy_server.py: -------------------------------------------------------------------------------- 1 | """ 2 | Entry point for CherryPy's WSGI server 3 | """ 4 | try: 5 | from cherrypy import wsgiserver 6 | except ImportError: 7 | print('=' * 60) 8 | print('== You must install CherryPy (pip install cherrypy) to use the egg:PasteScript#cherrypy server') 9 | print('=' * 60) 10 | raise 11 | 12 | try: 13 | import ssl 14 | from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter 15 | except ImportError: 16 | builtin = False 17 | else: 18 | builtin = True 19 | 20 | def cpwsgi_server(app, global_conf=None, host='127.0.0.1', port=None, 21 | ssl_pem=None, protocol_version=None, numthreads=None, 22 | server_name=None, max=None, request_queue_size=None, 23 | timeout=None): 24 | """ 25 | Serves the specified WSGI app via CherryPyWSGIServer. 26 | 27 | ``app`` 28 | 29 | The WSGI 'application callable'; multiple WSGI applications 30 | may be passed as (script_name, callable) pairs. 31 | 32 | ``host`` 33 | 34 | This is the ipaddress to bind to (or a hostname if your 35 | nameserver is properly configured). This defaults to 36 | 127.0.0.1, which is not a public interface. 37 | 38 | ``port`` 39 | 40 | The port to run on, defaults to 8080 for HTTP, or 4443 for 41 | HTTPS. This can be a string or an integer value. 42 | 43 | ``ssl_pem`` 44 | 45 | This an optional SSL certificate file (via OpenSSL) You can 46 | generate a self-signed test PEM certificate file as follows: 47 | 48 | $ openssl genrsa 1024 > host.key 49 | $ chmod 400 host.key 50 | $ openssl req -new -x509 -nodes -sha1 -days 365 \\ 51 | -key host.key > host.cert 52 | $ cat host.cert host.key > host.pem 53 | $ chmod 400 host.pem 54 | 55 | ``protocol_version`` 56 | 57 | The protocol used by the server, by default ``HTTP/1.1``. 58 | 59 | ``numthreads`` 60 | 61 | The number of worker threads to create. 62 | 63 | ``server_name`` 64 | 65 | The string to set for WSGI's SERVER_NAME environ entry. 66 | 67 | ``max`` 68 | 69 | The maximum number of queued requests. (defaults to -1 = no 70 | limit). 71 | 72 | ``request_queue_size`` 73 | 74 | The 'backlog' argument to socket.listen(); specifies the 75 | maximum number of queued connections. 76 | 77 | ``timeout`` 78 | 79 | The timeout in seconds for accepted connections. 80 | """ 81 | is_ssl = False 82 | if ssl_pem: 83 | port = port or 4443 84 | is_ssl = True 85 | 86 | if not port: 87 | if ':' in host: 88 | host, port = host.split(':', 1) 89 | else: 90 | port = 8080 91 | bind_addr = (host, int(port)) 92 | 93 | kwargs = {} 94 | for var_name in ('numthreads', 'max', 'request_queue_size', 'timeout'): 95 | var = locals()[var_name] 96 | if var is not None: 97 | kwargs[var_name] = int(var) 98 | 99 | server = wsgiserver.CherryPyWSGIServer(bind_addr, app, 100 | server_name=server_name, **kwargs) 101 | if is_ssl: 102 | if builtin: 103 | server.ssl_module = 'builtin' 104 | server.ssl_adapter = BuiltinSSLAdapter(ssl_pem, ssl_pem) 105 | else: 106 | server.ssl_certificate = server.ssl_private_key = ssl_pem 107 | 108 | if protocol_version: 109 | server.protocol = protocol_version 110 | 111 | try: 112 | protocol = is_ssl and 'https' or 'http' 113 | if host == '0.0.0.0': 114 | print('serving on 0.0.0.0:%s view at %s://127.0.0.1:%s' % \ 115 | (port, protocol, port)) 116 | else: 117 | print("serving on %s://%s:%s" % (protocol, host, port)) 118 | server.start() 119 | except (KeyboardInterrupt, SystemExit): 120 | server.stop() 121 | return server 122 | -------------------------------------------------------------------------------- /paste/script/copydir.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | import os 4 | import pkg_resources 5 | from urllib.parse import quote 6 | import string 7 | try: 8 | import html 9 | except ImportError: 10 | import cgi as html 11 | 12 | Cheetah = None 13 | try: 14 | import subprocess 15 | except ImportError: 16 | subprocess = None # jython 17 | import inspect 18 | 19 | class SkipTemplate(Exception): 20 | """ 21 | Raised to indicate that the template should not be copied over. 22 | Raise this exception during the substitution of your template 23 | """ 24 | 25 | def copy_dir(source, dest, vars, verbosity, simulate, indent=0, 26 | use_cheetah=False, sub_vars=True, interactive=False, 27 | svn_add=True, overwrite=True, template_renderer=None): 28 | """ 29 | Copies the ``source`` directory to the ``dest`` directory. 30 | 31 | ``vars``: A dictionary of variables to use in any substitutions. 32 | 33 | ``verbosity``: Higher numbers will show more about what is happening. 34 | 35 | ``simulate``: If true, then don't actually *do* anything. 36 | 37 | ``indent``: Indent any messages by this amount. 38 | 39 | ``sub_vars``: If true, variables in ``_tmpl`` files and ``+var+`` 40 | in filenames will be substituted. 41 | 42 | ``use_cheetah``: If true, then any templates encountered will be 43 | substituted with Cheetah. Otherwise ``template_renderer`` or 44 | ``string.Template`` will be used for templates. 45 | 46 | ``svn_add``: If true, any files written out in directories that are part of 47 | a svn working copy will be added (via ``svn add``). 48 | 49 | ``overwrite``: If false, then don't every overwrite anything. 50 | 51 | ``interactive``: If you are overwriting a file and interactive is 52 | true, then ask before overwriting. 53 | 54 | ``template_renderer``: This is a function for rendering templates 55 | (if you don't want to use Cheetah or string.Template). It should 56 | have the signature ``template_renderer(content_as_string, 57 | vars_as_dict, filename=filename)``. 58 | """ 59 | # This allows you to use a leading +dot+ in filenames which would 60 | # otherwise be skipped because leading dots make the file hidden: 61 | vars.setdefault('dot', '.') 62 | vars.setdefault('plus', '+') 63 | use_pkg_resources = isinstance(source, tuple) 64 | if use_pkg_resources: 65 | names = pkg_resources.resource_listdir(source[0], source[1]) 66 | else: 67 | names = os.listdir(source) 68 | names.sort() 69 | pad = ' '*(indent*2) 70 | if not os.path.exists(dest): 71 | if verbosity >= 1: 72 | print('%sCreating %s/' % (pad, dest)) 73 | if not simulate: 74 | svn_makedirs(dest, svn_add=svn_add, verbosity=verbosity, 75 | pad=pad) 76 | elif verbosity >= 2: 77 | print('%sDirectory %s exists' % (pad, dest)) 78 | for name in names: 79 | if use_pkg_resources: 80 | full = '/'.join([source[1], name]) 81 | else: 82 | full = os.path.join(source, name) 83 | reason = should_skip_file(name) 84 | if reason: 85 | if verbosity >= 2: 86 | reason = pad + reason % {'filename': full} 87 | print(reason) 88 | continue 89 | if sub_vars: 90 | dest_full = os.path.join(dest, substitute_filename(name, vars)) 91 | sub_file = False 92 | if dest_full.endswith('_tmpl'): 93 | dest_full = dest_full[:-5] 94 | sub_file = sub_vars 95 | if use_pkg_resources and pkg_resources.resource_isdir(source[0], full): 96 | if verbosity: 97 | print('%sRecursing into %s' % (pad, os.path.basename(full))) 98 | copy_dir((source[0], full), dest_full, vars, verbosity, simulate, 99 | indent=indent+1, use_cheetah=use_cheetah, 100 | sub_vars=sub_vars, interactive=interactive, 101 | svn_add=svn_add, template_renderer=template_renderer) 102 | continue 103 | elif not use_pkg_resources and os.path.isdir(full): 104 | if verbosity: 105 | print('%sRecursing into %s' % (pad, os.path.basename(full))) 106 | copy_dir(full, dest_full, vars, verbosity, simulate, 107 | indent=indent+1, use_cheetah=use_cheetah, 108 | sub_vars=sub_vars, interactive=interactive, 109 | svn_add=svn_add, template_renderer=template_renderer) 110 | continue 111 | elif use_pkg_resources: 112 | content = pkg_resources.resource_string(source[0], full) 113 | content = content.encode() 114 | else: 115 | with open(full, 'rb') as f: 116 | content = f.read() 117 | if sub_file: 118 | try: 119 | content = content.decode() 120 | content = substitute_content(content, vars, filename=full, 121 | use_cheetah=use_cheetah, 122 | template_renderer=template_renderer) 123 | content = content.encode() 124 | except SkipTemplate: 125 | continue 126 | if content is None: 127 | continue 128 | already_exists = os.path.exists(dest_full) 129 | if already_exists: 130 | with open(dest_full, 'rb') as f: 131 | old_content = f.read() 132 | if old_content == content: 133 | if verbosity: 134 | print('%s%s already exists (same content)' % (pad, dest_full)) 135 | continue 136 | if interactive: 137 | if not query_interactive( 138 | full, dest_full, content.decode(), old_content.decode(), 139 | simulate=simulate): 140 | continue 141 | elif not overwrite: 142 | continue 143 | if verbosity and use_pkg_resources: 144 | print('%sCopying %s to %s' % (pad, full, dest_full)) 145 | elif verbosity: 146 | print('%sCopying %s to %s' % (pad, os.path.basename(full), dest_full)) 147 | if not simulate: 148 | with open(dest_full, 'wb') as f: 149 | f.write(content) 150 | if svn_add and not already_exists: 151 | if os.system('svn info %r >/dev/null 2>&1' % os.path.dirname(os.path.abspath(dest_full))) > 0: 152 | if verbosity > 1: 153 | print('%sNot part of a svn working copy; cannot add file' % pad) 154 | else: 155 | cmd = ['svn', 'add', dest_full] 156 | if verbosity > 1: 157 | print('%sRunning: %s' % (pad, ' '.join(cmd))) 158 | if not simulate: 159 | # @@: Should 160 | if subprocess is None: 161 | raise RuntimeError('copydir failed, environment ' 162 | 'does not support subprocess ' 163 | 'module') 164 | proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) 165 | stdout, stderr = proc.communicate() 166 | if verbosity > 1 and stdout: 167 | print('Script output:') 168 | print(stdout) 169 | elif svn_add and already_exists and verbosity > 1: 170 | print('%sFile already exists (not doing svn add)' % pad) 171 | 172 | def should_skip_file(name): 173 | """ 174 | Checks if a file should be skipped based on its name. 175 | 176 | If it should be skipped, returns the reason, otherwise returns 177 | None. 178 | """ 179 | if name.startswith('.'): 180 | return 'Skipping hidden file %(filename)s' 181 | if name.endswith('~') or name.endswith('.bak'): 182 | return 'Skipping backup file %(filename)s' 183 | if name.endswith('.pyc') or name.endswith('.pyo'): 184 | return 'Skipping %s file %%(filename)s' % os.path.splitext(name)[1] 185 | if name.endswith('$py.class'): 186 | return 'Skipping $py.class file %(filename)s' 187 | if name in ('CVS', '_darcs'): 188 | return 'Skipping version control directory %(filename)s' 189 | return None 190 | 191 | # Overridden on user's request: 192 | all_answer = None 193 | 194 | def query_interactive(src_fn, dest_fn, src_content, dest_content, 195 | simulate): 196 | global all_answer 197 | from difflib import unified_diff, context_diff 198 | u_diff = list(unified_diff( 199 | dest_content.splitlines(), 200 | src_content.splitlines(), 201 | dest_fn, src_fn)) 202 | c_diff = list(context_diff( 203 | dest_content.splitlines(), 204 | src_content.splitlines(), 205 | dest_fn, src_fn)) 206 | added = len([l for l in u_diff if l.startswith('+') 207 | and not l.startswith('+++')]) 208 | removed = len([l for l in u_diff if l.startswith('-') 209 | and not l.startswith('---')]) 210 | if added > removed: 211 | msg = '; %i lines added' % (added-removed) 212 | elif removed > added: 213 | msg = '; %i lines removed' % (removed-added) 214 | else: 215 | msg = '' 216 | print('Replace %i bytes with %i bytes (%i/%i lines changed%s)' % ( 217 | len(dest_content), len(src_content), 218 | removed, len(dest_content.splitlines()), msg)) 219 | prompt = 'Overwrite %s [y/n/d/B/?] ' % dest_fn 220 | while 1: 221 | if all_answer is None: 222 | response = input(prompt).strip().lower() 223 | else: 224 | response = all_answer 225 | if not response or response[0] == 'b': 226 | import shutil 227 | new_dest_fn = dest_fn + '.bak' 228 | n = 0 229 | while os.path.exists(new_dest_fn): 230 | n += 1 231 | new_dest_fn = dest_fn + '.bak' + str(n) 232 | print('Backing up %s to %s' % (dest_fn, new_dest_fn)) 233 | if not simulate: 234 | shutil.copyfile(dest_fn, new_dest_fn) 235 | return True 236 | elif response.startswith('all '): 237 | rest = response[4:].strip() 238 | if not rest or rest[0] not in ('y', 'n', 'b'): 239 | print(query_usage) 240 | continue 241 | response = all_answer = rest[0] 242 | if response[0] == 'y': 243 | return True 244 | elif response[0] == 'n': 245 | return False 246 | elif response == 'dc': 247 | print('\n'.join(c_diff)) 248 | elif response[0] == 'd': 249 | print('\n'.join(u_diff)) 250 | else: 251 | print(query_usage) 252 | 253 | query_usage = """\ 254 | Responses: 255 | Y(es): Overwrite the file with the new content. 256 | N(o): Do not overwrite the file. 257 | D(iff): Show a unified diff of the proposed changes (dc=context diff) 258 | B(ackup): Save the current file contents to a .bak file 259 | (and overwrite) 260 | Type "all Y/N/B" to use Y/N/B for answer to all future questions 261 | """ 262 | 263 | def svn_makedirs(dir, svn_add, verbosity, pad): 264 | parent = os.path.dirname(os.path.abspath(dir)) 265 | if not os.path.exists(parent): 266 | svn_makedirs(parent, svn_add, verbosity, pad) 267 | os.mkdir(dir) 268 | if not svn_add: 269 | return 270 | if os.system('svn info %r >/dev/null 2>&1' % parent) > 0: 271 | if verbosity > 1: 272 | print('%sNot part of a svn working copy; cannot add directory' % pad) 273 | return 274 | cmd = ['svn', 'add', dir] 275 | if verbosity > 1: 276 | print('%sRunning: %s' % (pad, ' '.join(cmd))) 277 | proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) 278 | stdout, stderr = proc.communicate() 279 | if verbosity > 1 and stdout: 280 | print('Script output:') 281 | print(stdout) 282 | 283 | def substitute_filename(fn, vars): 284 | for var, value in vars.items(): 285 | fn = fn.replace('+%s+' % var, str(value)) 286 | return fn 287 | 288 | def substitute_content(content, vars, filename='', 289 | use_cheetah=False, template_renderer=None): 290 | global Cheetah 291 | v = standard_vars.copy() 292 | v.update(vars) 293 | vars = v 294 | if template_renderer is not None: 295 | return template_renderer(content, vars, filename=filename) 296 | if not use_cheetah: 297 | tmpl = LaxTemplate(content) 298 | try: 299 | return tmpl.substitute(TypeMapper(v)) 300 | except Exception as e: 301 | _add_except(e, ' in file %s' % filename) 302 | raise 303 | if Cheetah is None: 304 | import Cheetah.Template 305 | tmpl = Cheetah.Template.Template(source=content, 306 | searchList=[vars]) 307 | return careful_sub(tmpl, vars, filename) 308 | 309 | def careful_sub(cheetah_template, vars, filename): 310 | """ 311 | Substitutes the template with the variables, using the 312 | .body() method if it exists. It assumes that the variables 313 | were also passed in via the searchList. 314 | """ 315 | if not hasattr(cheetah_template, 'body'): 316 | return sub_catcher(filename, vars, str, cheetah_template) 317 | body = cheetah_template.body 318 | args, varargs, varkw, defaults = inspect.getargspec(body) 319 | call_vars = {} 320 | for arg in args: 321 | if arg in vars: 322 | call_vars[arg] = vars[arg] 323 | return sub_catcher(filename, vars, body, **call_vars) 324 | 325 | def sub_catcher(filename, vars, func, *args, **kw): 326 | """ 327 | Run a substitution, returning the value. If an error occurs, show 328 | the filename. If the error is a NameError, show the variables. 329 | """ 330 | try: 331 | return func(*args, **kw) 332 | except SkipTemplate as e: 333 | print('Skipping file %s' % filename) 334 | if str(e): 335 | print(str(e)) 336 | raise 337 | except Exception as e: 338 | print('Error in file %s:' % filename) 339 | if isinstance(e, NameError): 340 | for name, value in sorted(vars.items()): 341 | print('%s = %r' % (name, value)) 342 | raise 343 | 344 | def html_quote(s): 345 | if s is None: 346 | return '' 347 | return html.escape(str(s), 1) 348 | 349 | def url_quote(s): 350 | if s is None: 351 | return '' 352 | return quote(str(s)) 353 | 354 | def test(conf, true_cond, false_cond=None): 355 | if conf: 356 | return true_cond 357 | else: 358 | return false_cond 359 | 360 | def skip_template(condition=True, *args): 361 | """ 362 | Raise SkipTemplate, which causes copydir to skip the template 363 | being processed. If you pass in a condition, only raise if that 364 | condition is true (allows you to use this with string.Template) 365 | 366 | If you pass any additional arguments, they will be used to 367 | instantiate SkipTemplate (generally use like 368 | ``skip_template(license=='GPL', 'Skipping file; not using GPL')``) 369 | """ 370 | if condition: 371 | raise SkipTemplate(*args) 372 | 373 | def _add_except(exc, info): 374 | if not hasattr(exc, 'args') or exc.args is None: 375 | return 376 | args = list(exc.args) 377 | if args: 378 | args[0] += ' ' + info 379 | else: 380 | args = [info] 381 | exc.args = tuple(args) 382 | return 383 | 384 | 385 | standard_vars = { 386 | 'nothing': None, 387 | 'html_quote': html_quote, 388 | 'url_quote': url_quote, 389 | 'empty': '""', 390 | 'test': test, 391 | 'repr': repr, 392 | 'str': str, 393 | 'bool': bool, 394 | 'SkipTemplate': SkipTemplate, 395 | 'skip_template': skip_template, 396 | } 397 | 398 | class TypeMapper(dict): 399 | 400 | def __getitem__(self, item): 401 | options = item.split('|') 402 | for op in options[:-1]: 403 | try: 404 | value = eval_with_catch(op, dict(self.items())) 405 | break 406 | except (NameError, KeyError): 407 | pass 408 | else: 409 | value = eval(options[-1], dict(self.items())) 410 | if value is None: 411 | return '' 412 | else: 413 | return str(value) 414 | 415 | def eval_with_catch(expr, vars): 416 | try: 417 | return eval(expr, vars) 418 | except Exception as e: 419 | _add_except(e, 'in expression %r' % expr) 420 | raise 421 | 422 | class LaxTemplate(string.Template): 423 | # This change of pattern allows for anything in braces, but 424 | # only identifiers outside of braces: 425 | pattern = r""" 426 | \$(?: 427 | (?P\$) | # Escape sequence of two delimiters 428 | (?P[_a-z][_a-z0-9]*) | # delimiter and a Python identifier 429 | {(?P.*?)} | # delimiter and a braced identifier 430 | (?P) # Other ill-formed delimiter exprs 431 | ) 432 | """ 433 | -------------------------------------------------------------------------------- /paste/script/default_sysconfig.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | """ 4 | This module contains default sysconfig settings. 5 | 6 | The command object is inserted into this module as a global variable 7 | ``paste_command``, and can be used inside functions. 8 | """ 9 | 10 | def add_custom_options(parser): 11 | """ 12 | This method can modify the ``parser`` object (which is an 13 | ``optparse.OptionParser`` instance). This can be used to add new 14 | options to the command. 15 | """ 16 | pass 17 | 18 | def default_config_filename(installer): 19 | """ 20 | This function can return a default filename or directory for the 21 | configuration file, if none was explicitly given. 22 | 23 | Return None to mean no preference. The first non-None returning 24 | value will be used. 25 | 26 | Pay attention to ``installer.expect_config_directory`` here, 27 | and to ``installer.default_config_filename``. 28 | """ 29 | return installer.default_config_filename 30 | 31 | def install_variables(installer): 32 | """ 33 | Returns a dictionary of variables for use later in the process 34 | (e.g., filling a configuration file). These are combined from all 35 | sysconfig files. 36 | """ 37 | return {} 38 | 39 | def post_setup_hook(installer, config_file): 40 | """ 41 | This is called at the very end of ``paster setup-app``. You 42 | might use it to register an application globally. 43 | """ 44 | pass 45 | -------------------------------------------------------------------------------- /paste/script/entrypoints.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | import textwrap 4 | import os 5 | import pkg_resources 6 | from .command import Command, BadCommand 7 | import fnmatch 8 | import re 9 | import traceback 10 | from io import StringIO 11 | import inspect 12 | import types 13 | 14 | class EntryPointCommand(Command): 15 | 16 | usage = "ENTRY_POINT" 17 | summary = "Show information about entry points" 18 | 19 | description = """\ 20 | Shows information about one or many entry points (you can use 21 | wildcards for entry point names). Entry points are used for Egg 22 | plugins, and are named resources -- like an application, template 23 | plugin, or other resource. Entry points have a [group] which 24 | defines what kind of object they describe, and inside groups each 25 | entry point is named. 26 | """ 27 | 28 | max_args = 2 29 | 30 | parser = Command.standard_parser(verbose=False) 31 | parser.add_option('--list', '-l', 32 | dest='list_entry_points', 33 | action='store_true', 34 | help='List all the kinds of entry points on the system') 35 | parser.add_option('--egg', '-e', 36 | dest='show_egg', 37 | help="Show all the entry points for the given Egg") 38 | parser.add_option('--regex', 39 | dest='use_regex', 40 | action='store_true', 41 | help="Make pattern match as regular expression, not just a wildcard pattern") 42 | 43 | def command(self): 44 | if self.options.list_entry_points: 45 | return self.list_entry_points() 46 | if self.options.show_egg: 47 | return self.show_egg(self.options.show_egg) 48 | if not self.args: 49 | raise BadCommand("You must give an entry point (or --list)") 50 | pattern = self.get_pattern(self.args[0]) 51 | groups = self.get_groups_by_pattern(pattern) 52 | if not groups: 53 | raise BadCommand('No group matched %s' % self.args[0]) 54 | ep_pat = None 55 | if len(self.args) > 1: 56 | ep_pat = self.get_pattern(self.args[1]) 57 | for group in groups: 58 | desc = self.get_group_description(group) 59 | print('[%s]' % group) 60 | if desc: 61 | print(self.wrap(desc)) 62 | print() 63 | self.print_entry_points_by_group(group, ep_pat) 64 | 65 | def print_entry_points_by_group(self, group, ep_pat): 66 | env = pkg_resources.Environment() 67 | project_names = sorted(env) 68 | for project_name in project_names: 69 | dists = list(env[project_name]) 70 | assert dists 71 | dist = dists[0] 72 | entries = list(dist.get_entry_map(group).values()) 73 | if ep_pat: 74 | entries = [e for e in entries 75 | if ep_pat.search(e.name)] 76 | if not entries: 77 | continue 78 | if len(dists) > 1: 79 | print('%s (+ %i older versions)' % ( 80 | dist, len(dists)-1)) 81 | else: 82 | print('%s' % dist) 83 | entries.sort(key=lambda entry: entry.name) 84 | for entry in entries: 85 | print(self._ep_description(entry)) 86 | desc = self.get_entry_point_description(entry, group) 87 | if desc and desc.description: 88 | print(self.wrap(desc.description, indent=4)) 89 | 90 | def show_egg(self, egg_name): 91 | group_pat = None 92 | if self.args: 93 | group_pat = self.get_pattern(self.args[0]) 94 | ep_pat = None 95 | if len(self.args) > 1: 96 | ep_pat = self.get_pattern(self.args[1]) 97 | if egg_name.startswith('egg:'): 98 | egg_name = egg_name[4:] 99 | dist = pkg_resources.get_distribution(egg_name) 100 | entry_map = dist.get_entry_map() 101 | entry_groups = sorted(entry_map.items()) 102 | for group, points in entry_groups: 103 | if group_pat and not group_pat.search(group): 104 | continue 105 | print('[%s]' % group) 106 | points = sorted(points.items()) 107 | for name, entry in points: 108 | if ep_pat: 109 | if not ep_pat.search(name): 110 | continue 111 | print(self._ep_description(entry)) 112 | desc = self.get_entry_point_description(entry, group) 113 | if desc and desc.description: 114 | print(self.wrap(desc.description, indent=2)) 115 | print() 116 | 117 | def wrap(self, text, indent=0): 118 | text = dedent(text) 119 | width = int(os.environ.get('COLUMNS', 70)) - indent 120 | text = '\n'.join([line.rstrip() for line in text.splitlines()]) 121 | paras = text.split('\n\n') 122 | new_paras = [] 123 | for para in paras: 124 | if para.lstrip() == para: 125 | # leading whitespace means don't rewrap 126 | para = '\n'.join(textwrap.wrap(para, width)) 127 | new_paras.append(para) 128 | text = '\n\n'.join(new_paras) 129 | lines = [' '*indent + line 130 | for line in text.splitlines()] 131 | return '\n'.join(lines) 132 | 133 | def _ep_description(self, ep, pad_name=None): 134 | name = ep.name 135 | if pad_name is not None: 136 | name = name + ' '*(pad_name-len(name)) 137 | dest = ep.module_name 138 | if ep.attrs: 139 | dest = dest + ':' + '.'.join(ep.attrs) 140 | return '%s = %s' % (name, dest) 141 | 142 | def get_pattern(self, s): 143 | if not s: 144 | return None 145 | if self.options.use_regex: 146 | return re.compile(s) 147 | else: 148 | return re.compile(fnmatch.translate(s), re.I) 149 | 150 | def list_entry_points(self): 151 | pattern = self.get_pattern(self.args and self.args[0]) 152 | groups = self.get_groups_by_pattern(pattern) 153 | print('%i entry point groups found:' % len(groups)) 154 | for group in groups: 155 | desc = self.get_group_description(group) 156 | print('[%s]' % group) 157 | if desc: 158 | if hasattr(desc, 'description'): 159 | desc = desc.description 160 | print(self.wrap(desc, indent=2)) 161 | 162 | def get_groups_by_pattern(self, pattern): 163 | env = pkg_resources.Environment() 164 | eps = {} 165 | for project_name in env: 166 | for dist in env[project_name]: 167 | for name in pkg_resources.get_entry_map(dist): 168 | if pattern and not pattern.search(name): 169 | continue 170 | if (not pattern 171 | and name.startswith('paste.description.')): 172 | continue 173 | eps[name] = None 174 | return sorted(eps.keys()) 175 | 176 | def get_group_description(self, group): 177 | for entry in pkg_resources.iter_entry_points('paste.entry_point_description'): 178 | if entry.name == group: 179 | ep = entry.load() 180 | if hasattr(ep, 'description'): 181 | return ep.description 182 | else: 183 | return ep 184 | return None 185 | 186 | def get_entry_point_description(self, ep, group): 187 | try: 188 | return self._safe_get_entry_point_description(ep, group) 189 | except Exception as e: 190 | out = StringIO() 191 | traceback.print_exc(file=out) 192 | return ErrorDescription(e, out.getvalue()) 193 | 194 | def _safe_get_entry_point_description(self, ep, group): 195 | ep.dist.activate() 196 | meta_group = 'paste.description.'+group 197 | meta = ep.dist.get_entry_info(meta_group, ep.name) 198 | if not meta: 199 | generic = list(pkg_resources.iter_entry_points( 200 | meta_group, 'generic')) 201 | if not generic: 202 | return super_generic(ep.load()) 203 | # @@: Error if len(generic) > 1? 204 | obj = generic[0].load() 205 | desc = obj(ep, group) 206 | else: 207 | desc = meta.load() 208 | return desc 209 | 210 | class EntryPointDescription(object): 211 | 212 | def __init__(self, group): 213 | self.group = group 214 | 215 | # Should define: 216 | # * description 217 | 218 | class SuperGeneric(object): 219 | 220 | def __init__(self, doc_object): 221 | self.doc_object = doc_object 222 | self.description = dedent(self.doc_object.__doc__) 223 | try: 224 | if isinstance(self.doc_object, type): 225 | func = self.doc_object.__init__ 226 | elif (hasattr(self.doc_object, '__call__') 227 | and not isinstance(self.doc_object, types.FunctionType)): 228 | func = self.doc_object.__call__ 229 | else: 230 | func = self.doc_object 231 | if hasattr(func, '__paste_sig__'): 232 | sig = func.__paste_sig__ 233 | else: 234 | sig = str(inspect.signature(func)) 235 | except TypeError: 236 | sig = None 237 | if sig: 238 | if self.description: 239 | self.description = '%s\n\n%s' % ( 240 | sig, self.description) 241 | else: 242 | self.description = sig 243 | 244 | def dedent(s): 245 | if s is None: 246 | return s 247 | s = s.strip('\n').strip('\r') 248 | return textwrap.dedent(s) 249 | 250 | def super_generic(obj): 251 | desc = SuperGeneric(obj) 252 | if not desc.description: 253 | return None 254 | return desc 255 | 256 | class ErrorDescription(object): 257 | 258 | def __init__(self, exc, tb): 259 | self.exc = exc 260 | self.tb = '\n'.join(tb) 261 | self.description = 'Error loading: %s' % exc 262 | 263 | -------------------------------------------------------------------------------- /paste/script/epdesc.py: -------------------------------------------------------------------------------- 1 | class MetaEntryPointDescription(object): 2 | description = """ 3 | This is an entry point that describes other entry points. 4 | """ 5 | 6 | class CreateTemplateDescription(object): 7 | description = """ 8 | Entry point for creating the file layout for a new project 9 | from a template. 10 | """ 11 | 12 | class PasterCommandDescription(object): 13 | description = """ 14 | Entry point that adds a command to the ``paster`` script 15 | to a project that has specifically enabled the command. 16 | """ 17 | 18 | class GlobalPasterCommandDescription(object): 19 | description = """ 20 | Entry point that adds a command to the ``paster`` script 21 | globally. 22 | """ 23 | 24 | class AppInstallDescription(object): 25 | description = """ 26 | This defines a runner that can install the application given a 27 | configuration file. 28 | """ 29 | 30 | ################################################## 31 | ## Not in Paste per se, but we'll document 32 | ## them... 33 | 34 | class ConsoleScriptsDescription(object): 35 | description = """ 36 | When a package is installed, any entry point listed here will be 37 | turned into a command-line script. 38 | """ 39 | 40 | class DistutilsCommandsDescription(object): 41 | description = """ 42 | This will add a new command when running 43 | ``python setup.py entry-point-name`` if the 44 | package uses setuptools. 45 | """ 46 | 47 | class SetupKeywordsDescription(object): 48 | description = """ 49 | This adds a new keyword to setup.py's setup() function, and a 50 | validator to validate the value. 51 | """ 52 | 53 | class EggInfoWriters(object): 54 | description = """ 55 | This adds a new writer that creates files in the PkgName.egg-info/ 56 | directory. 57 | """ 58 | -------------------------------------------------------------------------------- /paste/script/exe.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | import re 4 | import os 5 | import sys 6 | import shlex 7 | import pkg_resources 8 | from . import command 9 | 10 | class ExeCommand(command.Command): 11 | 12 | parser = command.Command.standard_parser(verbose=False) 13 | summary = "Run #! executable files" 14 | description = """\ 15 | Use this at the top of files like: 16 | 17 | #!/usr/bin/env /path/to/paster exe subcommand 18 | 19 | The rest of the file will be used as a config file for the given 20 | command, if it wants a config file. 21 | 22 | You can also include an [exe] section in the file, which looks 23 | like: 24 | 25 | [exe] 26 | command = serve 27 | log_file = /path/to/log 28 | add = /path/to/other/config.ini 29 | 30 | Which translates to: 31 | 32 | paster serve --log-file=/path/to/log /path/to/other/config.ini 33 | """ 34 | 35 | hidden = True 36 | 37 | _exe_section_re = re.compile(r'^\s*\[\s*exe\s*\]\s*$') 38 | _section_re = re.compile(r'^\s*\[') 39 | 40 | def run(self, argv): 41 | if argv and argv[0] in ('-h', '--help'): 42 | print(self.description) 43 | return 44 | 45 | if os.environ.get('REQUEST_METHOD'): 46 | # We're probably in a CGI environment 47 | sys.stdout = sys.stderr 48 | os.environ['PASTE_DEFAULT_QUIET'] = 'true' 49 | # Maybe import cgitb or something? 50 | 51 | if '_' not in os.environ: 52 | print("Warning: this command is intended to be run with a #! like:") 53 | print(" #!/usr/bin/env paster exe") 54 | print("It only works with /usr/bin/env, and only as a #! line.") 55 | # Should I actually shlex.split the args? 56 | filename = argv[-1] 57 | args = argv[:-1] 58 | extra_args = [] 59 | else: 60 | filename = os.environ['_'] 61 | extra_args = argv[:] 62 | args = [] 63 | while extra_args: 64 | if extra_args[0] == filename: 65 | extra_args.pop(0) 66 | break 67 | args.append(extra_args.pop(0)) 68 | vars = {'here': os.path.dirname(filename), 69 | '__file__': filename} 70 | f = open(filename) 71 | lines = f.readlines() 72 | f.close() 73 | options = {} 74 | lineno = 1 75 | while lines: 76 | if self._exe_section_re.search(lines[0]): 77 | lines.pop(0) 78 | break 79 | lines.pop(0) 80 | lineno += 1 81 | options = args 82 | for line in lines: 83 | lineno += 1 84 | line = line.strip() 85 | if not line or line.startswith('#'): 86 | continue 87 | if self._section_re.search(line): 88 | break 89 | if '=' not in line: 90 | raise command.BadCommand('Missing = in %s at %s: %r' 91 | % (filename, lineno, line)) 92 | name, value = line.split('=', 1) 93 | name = name.strip() 94 | value = value.strip() 95 | if name == 'require': 96 | pkg_resources.require(value) 97 | elif name == 'command' or name == 'add': 98 | options.extend(shlex.split(value)) 99 | elif name == 'plugin': 100 | options[:0] = ['--plugin', value] 101 | else: 102 | value = value % vars 103 | options.append('--%s=%s' % (name.replace('_', '-'), value)) 104 | os.environ['PASTE_CONFIG_FILE'] = filename 105 | options.extend(extra_args) 106 | command.run(options) 107 | -------------------------------------------------------------------------------- /paste/script/filemaker.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | import os 4 | import pkg_resources 5 | from paste.script import pluginlib, copydir 6 | from paste.script.command import BadCommand 7 | difflib = None 8 | import subprocess 9 | 10 | class FileOp(object): 11 | """ 12 | Enhance the ease of file copying/processing from a package into a target 13 | project 14 | """ 15 | 16 | def __init__(self, simulate=False, 17 | verbose=True, 18 | interactive=True, 19 | source_dir=None, 20 | template_vars=None): 21 | """ 22 | Initialize our File operation helper object 23 | 24 | source_dir 25 | Should refer to the directory within the package 26 | that contains the templates to be used for the other copy 27 | operations. It is assumed that packages will keep all their 28 | templates under a hierarchy starting here. 29 | 30 | This should be an absolute path passed in, for example:: 31 | 32 | FileOp(source_dir=os.path.dirname(__file__) + '/templates') 33 | """ 34 | self.simulate = simulate 35 | self.verbose = verbose 36 | self.interactive = interactive 37 | if template_vars is None: 38 | template_vars = {} 39 | self.template_vars = template_vars 40 | self.source_dir = source_dir 41 | self.use_pkg_resources = isinstance(source_dir, tuple) 42 | 43 | def copy_file(self, template, dest, filename=None, add_py=True, package=True, 44 | template_renderer=None): 45 | """ 46 | Copy a file from the source location to somewhere in the 47 | destination. 48 | 49 | template 50 | The filename underneath self.source_dir to copy/process 51 | dest 52 | The destination directory in the project relative to where 53 | this command is being run 54 | filename 55 | What to name the file in the target project, use the same name 56 | as the template if not provided 57 | add_py 58 | Add a .py extension to all files copied 59 | package 60 | Whether or not this file is part of a Python package, and any 61 | directories created should contain a __init__.py file as well. 62 | template_renderer 63 | An optional template renderer 64 | 65 | """ 66 | if not filename: 67 | filename = template.split('/')[0] 68 | if filename.endswith('_tmpl'): 69 | filename = filename[:-5] 70 | base_package, cdir = self.find_dir(dest, package) 71 | self.template_vars['base_package'] = base_package 72 | content = self.load_content(base_package, cdir, filename, template, 73 | template_renderer=template_renderer) 74 | if add_py: 75 | # @@: Why is it a default to add a .py extension? 76 | filename = '%s.py' % filename 77 | dest = os.path.join(cdir, filename) 78 | self.ensure_file(dest, content, package) 79 | 80 | def copy_dir(self, template_dir, dest, destname=None, package=True): 81 | """ 82 | Copy a directory recursively, processing any files within it 83 | that need to be processed (end in _tmpl). 84 | 85 | template_dir 86 | Directory under self.source_dir to copy/process 87 | dest 88 | Destination directory into which this directory will be copied 89 | to. 90 | destname 91 | Use this name instead of the original template_dir name for 92 | creating the directory 93 | package 94 | This directory will be a Python package and needs to have a 95 | __init__.py file. 96 | """ 97 | # @@: This should actually be implemented 98 | raise NotImplementedError 99 | 100 | def load_content(self, base_package, base, name, template, 101 | template_renderer=None): 102 | blank = os.path.join(base, name + '.py') 103 | read_content = True 104 | if not os.path.exists(blank): 105 | if self.use_pkg_resources: 106 | fullpath = '/'.join([self.source_dir[1], template]) 107 | content = pkg_resources.resource_string( 108 | self.source_dir[0], fullpath) 109 | read_content = False 110 | blank = fullpath 111 | else: 112 | blank = os.path.join(self.source_dir, 113 | template) 114 | if read_content: 115 | f = open(blank, 'r') 116 | content = f.read() 117 | f.close() 118 | if blank.endswith('_tmpl'): 119 | content = copydir.substitute_content( 120 | content, self.template_vars, filename=blank, 121 | template_renderer=template_renderer) 122 | return content 123 | 124 | def find_dir(self, dirname, package=False): 125 | egg_info = pluginlib.find_egg_info_dir(os.getcwd()) 126 | # @@: Should give error about egg_info when top_level.txt missing 127 | f = open(os.path.join(egg_info, 'top_level.txt')) 128 | packages = [l.strip() for l in f.readlines() 129 | if l.strip() and not l.strip().startswith('#')] 130 | f.close() 131 | if not len(packages): 132 | raise BadCommand("No top level dir found for %s" % dirname) 133 | # @@: This doesn't support deeper servlet directories, 134 | # or packages not kept at the top level. 135 | base = os.path.dirname(egg_info) 136 | possible = [] 137 | for pkg in packages: 138 | d = os.path.join(base, pkg, dirname) 139 | if os.path.exists(d): 140 | possible.append((pkg, d)) 141 | if not possible: 142 | self.ensure_dir(os.path.join(base, packages[0], dirname), 143 | package=package) 144 | return self.find_dir(dirname) 145 | if len(possible) > 1: 146 | raise BadCommand( 147 | "Multiple %s dirs found (%s)" % (dirname, possible)) 148 | return possible[0] 149 | 150 | def parse_path_name_args(self, name): 151 | """ 152 | Given the name, assume that the first argument is a path/filename 153 | combination. Return the name and dir of this. If the name ends with 154 | '.py' that will be erased. 155 | 156 | Examples: 157 | comments -> comments, '' 158 | admin/comments -> comments, 'admin' 159 | h/ab/fred -> fred, 'h/ab' 160 | """ 161 | if name.endswith('.py'): 162 | # Erase extensions 163 | name = name[:-3] 164 | if '.' in name: 165 | # Turn into directory name: 166 | name = name.replace('.', os.path.sep) 167 | if '/' != os.path.sep: 168 | name = name.replace('/', os.path.sep) 169 | parts = name.split(os.path.sep) 170 | name = parts[-1] 171 | if not parts[:-1]: 172 | dir = '' 173 | elif len(parts[:-1]) == 1: 174 | dir = parts[0] 175 | else: 176 | dir = os.path.join(*parts[:-1]) 177 | return name, dir 178 | 179 | def ensure_dir(self, dir, svn_add=True, package=False): 180 | """ 181 | Ensure that the directory exists, creating it if necessary. 182 | Respects verbosity and simulation. 183 | 184 | Adds directory to subversion if ``.svn/`` directory exists in 185 | parent, and directory was created. 186 | 187 | package 188 | If package is True, any directories created will contain a 189 | __init__.py file. 190 | 191 | """ 192 | dir = dir.rstrip(os.sep) 193 | if not dir: 194 | # we either reached the parent-most directory, or we got 195 | # a relative directory 196 | # @@: Should we make sure we resolve relative directories 197 | # first? Though presumably the current directory always 198 | # exists. 199 | return 200 | if not os.path.exists(dir): 201 | self.ensure_dir(os.path.dirname(dir), svn_add=svn_add, package=package) 202 | if self.verbose: 203 | print('Creating %s' % self.shorten(dir)) 204 | if not self.simulate: 205 | os.mkdir(dir) 206 | if (svn_add and 207 | os.path.exists(os.path.join(os.path.dirname(dir), '.svn'))): 208 | self.svn_command('add', dir) 209 | if package: 210 | initfile = os.path.join(dir, '__init__.py') 211 | f = open(initfile, 'wb') 212 | f.write("#\n") 213 | f.close() 214 | print('Creating %s' % self.shorten(initfile)) 215 | if (svn_add and 216 | os.path.exists(os.path.join(os.path.dirname(dir), '.svn'))): 217 | self.svn_command('add', initfile) 218 | else: 219 | if self.verbose > 1: 220 | print("Directory already exists: %s" % self.shorten(dir)) 221 | 222 | def ensure_file(self, filename, content, svn_add=True, package=False): 223 | """ 224 | Ensure a file named ``filename`` exists with the given 225 | content. If ``--interactive`` has been enabled, this will ask 226 | the user what to do if a file exists with different content. 227 | """ 228 | global difflib 229 | self.ensure_dir(os.path.dirname(filename), svn_add=svn_add, package=package) 230 | if not os.path.exists(filename): 231 | if self.verbose: 232 | print('Creating %s' % filename) 233 | if not self.simulate: 234 | f = open(filename, 'wb') 235 | f.write(content) 236 | f.close() 237 | if svn_add and os.path.exists(os.path.join(os.path.dirname(filename), '.svn')): 238 | self.svn_command('add', filename) 239 | return 240 | f = open(filename, 'rb') 241 | old_content = f.read() 242 | f.close() 243 | if content == old_content: 244 | if self.verbose > 1: 245 | print('File %s matches expected content' % filename) 246 | return 247 | if self.interactive: 248 | print('Warning: file %s does not match expected content' % filename) 249 | if difflib is None: 250 | import difflib 251 | diff = difflib.context_diff( 252 | content.splitlines(), 253 | old_content.splitlines(), 254 | 'expected ' + filename, 255 | filename) 256 | print('\n'.join(diff)) 257 | if self.interactive: 258 | while 1: 259 | s = input( 260 | 'Overwrite file with new content? [y/N] ').strip().lower() 261 | if not s: 262 | s = 'n' 263 | if s.startswith('y'): 264 | break 265 | if s.startswith('n'): 266 | return 267 | print('Unknown response; Y or N please') 268 | else: 269 | return 270 | 271 | if self.verbose: 272 | print('Overwriting %s with new content' % filename) 273 | if not self.simulate: 274 | f = open(filename, 'wb') 275 | f.write(content) 276 | f.close() 277 | 278 | def shorten(self, fn, *paths): 279 | """ 280 | Return a shorted form of the filename (relative to the current 281 | directory), typically for displaying in messages. If 282 | ``*paths`` are present, then use os.path.join to create the 283 | full filename before shortening. 284 | """ 285 | if paths: 286 | fn = os.path.join(fn, *paths) 287 | if fn.startswith(os.getcwd()): 288 | return fn[len(os.getcwd()):].lstrip(os.path.sep) 289 | else: 290 | return fn 291 | 292 | _svn_failed = False 293 | 294 | def svn_command(self, *args, **kw): 295 | """ 296 | Run an svn command, but don't raise an exception if it fails. 297 | """ 298 | try: 299 | return self.run_command('svn', *args, **kw) 300 | except OSError as e: 301 | if not self._svn_failed: 302 | print('Unable to run svn command (%s); proceeding anyway' % e) 303 | self._svn_failed = True 304 | 305 | def run_command(self, cmd, *args, **kw): 306 | """ 307 | Runs the command, respecting verbosity and simulation. 308 | Returns stdout, or None if simulating. 309 | """ 310 | cwd = popdefault(kw, 'cwd', os.getcwd()) 311 | capture_stderr = popdefault(kw, 'capture_stderr', False) 312 | expect_returncode = popdefault(kw, 'expect_returncode', False) 313 | assert not kw, ("Arguments not expected: %s" % kw) 314 | if capture_stderr: 315 | stderr_pipe = subprocess.STDOUT 316 | else: 317 | stderr_pipe = subprocess.PIPE 318 | try: 319 | proc = subprocess.Popen([cmd] + list(args), 320 | cwd=cwd, 321 | stderr=stderr_pipe, 322 | stdout=subprocess.PIPE) 323 | except OSError as e: 324 | if e.errno != 2: 325 | # File not found 326 | raise 327 | raise OSError( 328 | "The expected executable %s was not found (%s)" 329 | % (cmd, e)) 330 | if self.verbose: 331 | print('Running %s %s' % (cmd, ' '.join(args))) 332 | if self.simulate: 333 | return None 334 | stdout, stderr = proc.communicate() 335 | if proc.returncode and not expect_returncode: 336 | if not self.verbose: 337 | print('Running %s %s' % (cmd, ' '.join(args))) 338 | print('Error (exit code: %s)' % proc.returncode) 339 | if stderr: 340 | print(stderr) 341 | raise OSError("Error executing command %s" % cmd) 342 | if self.verbose > 2: 343 | if stderr: 344 | print('Command error output:') 345 | print(stderr) 346 | if stdout: 347 | print('Command output:') 348 | print(stdout) 349 | return stdout 350 | 351 | def popdefault(dict, name, default=None): 352 | if name not in dict: 353 | return default 354 | else: 355 | v = dict[name] 356 | del dict[name] 357 | return v 358 | 359 | -------------------------------------------------------------------------------- /paste/script/flup_server.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | from paste.deploy.converters import aslist, asbool 4 | from paste.script.serve import ensure_port_cleanup 5 | import warnings 6 | 7 | def warn(name, stacklevel=3): 8 | # Deprecated 2007-12-17 9 | warnings.warn( 10 | 'The egg:PasteScript#flup_%s entry point is deprecated; please use egg:Flup#%s instead' 11 | % (name, name), 12 | DeprecationWarning, stacklevel=stacklevel) 13 | 14 | def run_ajp_thread(wsgi_app, global_conf, 15 | scriptName='', host='localhost', port='8009', 16 | allowedServers='127.0.0.1'): 17 | import flup.server.ajp 18 | warn('ajp_thread') 19 | addr = (host, int(port)) 20 | ensure_port_cleanup([addr]) 21 | s = flup.server.ajp.WSGIServer( 22 | wsgi_app, 23 | scriptName=scriptName, 24 | bindAddress=addr, 25 | allowedServers=aslist(allowedServers), 26 | ) 27 | s.run() 28 | 29 | def run_ajp_fork(wsgi_app, global_conf, 30 | scriptName='', host='localhost', port='8009', 31 | allowedServers='127.0.0.1'): 32 | import flup.server.ajp_fork 33 | warn('ajp_fork') 34 | addr = (host, int(port)) 35 | ensure_port_cleanup([addr]) 36 | s = flup.server.ajp_fork.WSGIServer( 37 | wsgi_app, 38 | scriptName=scriptName, 39 | bindAddress=addr, 40 | allowedServers=aslist(allowedServers), 41 | ) 42 | s.run() 43 | 44 | def run_fcgi_thread(wsgi_app, global_conf, 45 | host=None, port=None, 46 | socket=None, umask=None, 47 | multiplexed=False): 48 | import flup.server.fcgi 49 | warn('fcgi_thread') 50 | if socket: 51 | assert host is None and port is None 52 | sock = socket 53 | elif host: 54 | assert host is not None and port is not None 55 | sock = (host, int(port)) 56 | ensure_port_cleanup([sock]) 57 | else: 58 | sock = None 59 | if umask is not None: 60 | umask = int(umask) 61 | s = flup.server.fcgi.WSGIServer( 62 | wsgi_app, 63 | bindAddress=sock, umask=umask, 64 | multiplexed=asbool(multiplexed)) 65 | s.run() 66 | 67 | def run_fcgi_fork(wsgi_app, global_conf, 68 | host=None, port=None, 69 | socket=None, umask=None, 70 | multiplexed=False): 71 | import flup.server.fcgi_fork 72 | warn('fcgi_fork') 73 | if socket: 74 | assert host is None and port is None 75 | sock = socket 76 | elif host: 77 | assert host is not None and port is not None 78 | sock = (host, int(port)) 79 | ensure_port_cleanup([sock]) 80 | else: 81 | sock = None 82 | if umask is not None: 83 | umask = int(umask) 84 | s = flup.server.fcgi_fork.WSGIServer( 85 | wsgi_app, 86 | bindAddress=sock, umask=umask, 87 | multiplexed=asbool(multiplexed)) 88 | s.run() 89 | 90 | def run_scgi_thread(wsgi_app, global_conf, 91 | scriptName='', host='localhost', port='4000', 92 | allowedServers='127.0.0.1'): 93 | import flup.server.scgi 94 | warn('scgi_thread') 95 | addr = (host, int(port)) 96 | ensure_port_cleanup([addr]) 97 | s = flup.server.scgi.WSGIServer( 98 | wsgi_app, 99 | scriptName=scriptName, 100 | bindAddress=addr, 101 | allowedServers=aslist(allowedServers), 102 | ) 103 | s.run() 104 | 105 | def run_scgi_fork(wsgi_app, global_conf, 106 | scriptName='', host='localhost', port='4000', 107 | allowedServers='127.0.0.1'): 108 | import flup.server.scgi_fork 109 | warn('scgi_fork') 110 | addr = (host, int(port)) 111 | ensure_port_cleanup([addr]) 112 | s = flup.server.scgi_fork.WSGIServer( 113 | wsgi_app, 114 | scriptName=scriptName, 115 | bindAddress=addr, 116 | allowedServers=aslist(allowedServers), 117 | ) 118 | s.run() 119 | 120 | -------------------------------------------------------------------------------- /paste/script/grep.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | import os 4 | import py_compile 5 | import marshal 6 | import inspect 7 | import re 8 | import tokenize 9 | from .command import Command 10 | from . import pluginlib 11 | 12 | class GrepCommand(Command): 13 | 14 | summary = 'Search project for symbol' 15 | usage = 'SYMBOL' 16 | 17 | max_args = 1 18 | min_args = 1 19 | 20 | bad_names = ['.svn', 'CVS', '_darcs'] 21 | 22 | parser = Command.standard_parser() 23 | 24 | parser.add_option( 25 | '-x', '--exclude-module', 26 | metavar="module.name", 27 | dest="exclude_modules", 28 | action="append", 29 | help="Don't search the given module") 30 | 31 | parser.add_option( 32 | '-t', '--add-type', 33 | metavar=".ext", 34 | dest="add_types", 35 | action="append", 36 | help="Search the given type of files") 37 | 38 | def command(self): 39 | self.exclude_modules = self.options.exclude_modules or [] 40 | self.add_types = self.options.add_types or [] 41 | self.symbol = self.args[0] 42 | self.basedir = os.path.dirname( 43 | pluginlib.find_egg_info_dir(os.getcwd())) 44 | if self.verbose: 45 | print("Searching in %s" % self.basedir) 46 | self.total_files = 0 47 | self.search_dir(self.basedir) 48 | if self.verbose > 1: 49 | print("Searched %i files" % self.total_files) 50 | 51 | def search_dir(self, dir): 52 | names = os.listdir(dir) 53 | names.sort() 54 | dirs = [] 55 | for name in names: 56 | full = os.path.join(dir, name) 57 | if name in self.bad_names: 58 | continue 59 | if os.path.isdir(full): 60 | # Breadth-first; we'll do this later... 61 | dirs.append(full) 62 | continue 63 | for t in self.add_types: 64 | if name.lower().endswith(t.lower()): 65 | self.search_text(full) 66 | if not name.endswith('.py'): 67 | continue 68 | self.search_file(full) 69 | for dir in dirs: 70 | self.search_dir(dir) 71 | 72 | def search_file(self, filename): 73 | self.total_files += 1 74 | if not filename.endswith('.py'): 75 | self.search_text(filename) 76 | return 77 | pyc = filename[:-2]+'pyc' 78 | if not os.path.exists(pyc): 79 | try: 80 | py_compile.compile(filename) 81 | except OSError: 82 | # ignore permission error if the .pyc cannot be written 83 | pass 84 | if not os.path.exists(pyc): 85 | # Invalid syntax... 86 | self.search_text(filename, as_module=True) 87 | return 88 | with open(pyc, 'rb') as f: 89 | # .pyc Header: 90 | f.read(8) 91 | try: 92 | code = marshal.load(f) 93 | except ValueError: 94 | # Fail to load the byteload. For example, Python 3.4 cannot 95 | # load Python 2.7 bytecode. 96 | pass 97 | else: 98 | self.search_code(code, filename, []) 99 | 100 | def search_code(self, code, filename, path): 101 | if code.co_name != "?": 102 | path = path + [code.co_name] 103 | else: 104 | path = path 105 | sym = self.symbol 106 | if sym in code.co_varnames: 107 | self.found(code, filename, path) 108 | elif sym in code.co_names: 109 | self.found(code, filename, path) 110 | for const in code.co_consts: 111 | if const == sym: 112 | self.found(code, filename, path) 113 | if inspect.iscode(const): 114 | if not const.co_filename == filename: 115 | continue 116 | self.search_code(const, filename, path) 117 | 118 | def _open(self, filename): 119 | if filename.endswith('.py') and hasattr(tokenize, 'open'): 120 | # On Python 3.2 and newer, open Python files with tokenize.open(). 121 | # This functions uses the encoding cookie to get the encoding. 122 | return tokenize.open(filename) 123 | else: 124 | return open(filename) 125 | 126 | def search_text(self, filename, as_module=False): 127 | with self._open(filename) as f: 128 | lineno = 0 129 | any = False 130 | for line in f: 131 | lineno += 1 132 | if line.find(self.symbol) != -1: 133 | if not any: 134 | any = True 135 | if as_module: 136 | print('%s (unloadable)' % self.module_name(filename)) 137 | else: 138 | print(self.relative_name(filename)) 139 | print(' %3i %s' % (lineno, line)) 140 | if not self.verbose: 141 | break 142 | 143 | def found(self, code, filename, path): 144 | print(self.display(filename, path)) 145 | self.find_occurance(code) 146 | 147 | def find_occurance(self, code): 148 | with self._open(code.co_filename) as f: 149 | lineno = 0 150 | for index, line in zip(range(code.co_firstlineno), f): 151 | lineno += 1 152 | pass 153 | first_indent = None 154 | for line in f: 155 | lineno += 1 156 | if line.find(self.symbol) != -1: 157 | this_indent = len(re.match(r'^[ \t]*', line).group(0)) 158 | if first_indent is None: 159 | first_indent = this_indent 160 | else: 161 | if this_indent < first_indent: 162 | break 163 | print(' %3i %s' % (lineno, line[first_indent:].rstrip())) 164 | if not self.verbose: 165 | break 166 | 167 | def module_name(self, filename): 168 | #assert filename, startswith(self.basedir) 169 | mod = filename[len(self.basedir):].strip('/').strip(os.path.sep) 170 | mod = os.path.splitext(mod)[0] 171 | mod = mod.replace(os.path.sep, '.').replace('/', '.') 172 | return mod 173 | 174 | def relative_name(self, filename): 175 | #assert filename, startswith(self.basedir) 176 | name = filename[len(self.basedir):].strip('/').strip(os.path.sep) 177 | return name 178 | 179 | def display(self, filename, path): 180 | parts = '.'.join(path) 181 | if parts: 182 | parts = ':' + parts 183 | return self.module_name(filename) + parts 184 | 185 | -------------------------------------------------------------------------------- /paste/script/help.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | from .command import Command, get_commands 4 | from .command import parser as base_parser 5 | 6 | class HelpCommand(Command): 7 | 8 | summary = "Display help" 9 | usage = '[COMMAND]' 10 | 11 | max_args = 1 12 | 13 | parser = Command.standard_parser() 14 | 15 | def command(self): 16 | if not self.args: 17 | self.generic_help() 18 | return 19 | 20 | name = self.args[0] 21 | commands = get_commands() 22 | if name not in commands: 23 | print('No such command: %s' % name) 24 | self.generic_help() 25 | return 26 | 27 | command = commands[name].load() 28 | runner = command(name) 29 | runner.run(['-h']) 30 | 31 | def generic_help(self): 32 | base_parser.print_help() 33 | print() 34 | commands_grouped = {} 35 | commands = get_commands() 36 | longest = max([len(n) for n in commands.keys()]) 37 | for name, command in commands.items(): 38 | try: 39 | command = command.load() 40 | except Exception as e: 41 | print('Cannot load command %s: %s' % (name, e)) 42 | continue 43 | if getattr(command, 'hidden', False): 44 | continue 45 | commands_grouped.setdefault( 46 | command.group_name, []).append((name, command)) 47 | commands_grouped = commands_grouped.items() 48 | commands_grouped = sorted(commands_grouped) 49 | print('Commands:') 50 | for group, commands in commands_grouped: 51 | if group: 52 | print(group + ':') 53 | commands.sort() 54 | for name, command in commands: 55 | print(' %s %s' % (self.pad(name, length=longest), 56 | command.summary)) 57 | #if command.description: 58 | # print self.indent_block(command.description, 4) 59 | print() 60 | 61 | -------------------------------------------------------------------------------- /paste/script/interfaces.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | class IAppInstall(object): 4 | 5 | """ 6 | The interface for objects in the entry point group 7 | ``paste.app_install`` 8 | """ 9 | 10 | def __init__(distribution, entry_group, entry_name): 11 | """ 12 | An object representing a specific application (the 13 | distribution is a pkg_resource.Distribution object), for the 14 | given entry point name in the given group. Right now the only 15 | group used for this is ``'paste.app_factory'``. 16 | """ 17 | 18 | def description(sys_config): 19 | """ 20 | Return a text description of the application and its 21 | configuration. ``sys_config`` is a dictionary representing 22 | the system configuration, and can be used for giving more 23 | explicit defaults if the application preparation uses the 24 | system configuration. It may be None, in which case the 25 | description should be more abstract. 26 | 27 | Applications are free to ignore ``sys_config``. 28 | """ 29 | 30 | def write_config(command, filename, sys_config): 31 | """ 32 | Write a fresh config file to ``filename``. ``command`` is a 33 | ``paste.script.command.Command`` object, and should be used 34 | for the actual operations. It handles things like simulation 35 | and verbosity. 36 | 37 | ``sys_config`` is (if given) a dictionary of system-wide 38 | configuration options. 39 | """ 40 | 41 | def setup_config(command, config_filename, 42 | config_section, sys_config): 43 | """ 44 | Set up the application, using ``command`` (to ensure simulate, 45 | etc). The application is described by the configuration file 46 | ``config_filename``. ``sys_config`` is the system 47 | configuration (though probably the values from it should have 48 | already been encorporated into the configuration file). 49 | """ 50 | -------------------------------------------------------------------------------- /paste/script/paster-templates/basic_package/+package+/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /paste/script/paster-templates/basic_package/setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = dev 3 | tag_svn_revision = true 4 | -------------------------------------------------------------------------------- /paste/script/paster-templates/basic_package/setup.py_tmpl: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import sys, os 3 | 4 | version = {{repr(version or "0.0")}} 5 | 6 | setup(name={{repr(project)}}, 7 | version=version, 8 | description="{{description or ''}}", 9 | long_description="""\ 10 | {{long_description or ''}}""", 11 | classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers 12 | keywords={{repr(keywords or '')}}, 13 | author={{repr(author or '')}}, 14 | author_email={{repr(author_email or '')}}, 15 | url={{repr(url or '')}}, 16 | license={{repr(license_name or '')}}, 17 | packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), 18 | include_package_data=True, 19 | zip_safe={{repr(bool(zip_safe or False))}}, 20 | install_requires=[ 21 | # -*- Extra requirements: -*- 22 | ], 23 | entry_points=""" 24 | # -*- Entry points: -*- 25 | """, 26 | ) 27 | -------------------------------------------------------------------------------- /paste/script/pluginlib.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | import os 4 | import pkg_resources 5 | 6 | def add_plugin(egg_info_dir, plugin_name): 7 | """ 8 | Add the plugin to the given distribution (or spec), in 9 | .egg-info/paster_plugins.txt 10 | """ 11 | fn = os.path.join(egg_info_dir, 'paster_plugins.txt') 12 | if not os.path.exists(fn): 13 | lines = [] 14 | else: 15 | f = open(fn) 16 | lines = [l.strip() for l in f.readlines() if l.strip()] 17 | f.close() 18 | if plugin_name in lines: 19 | # Nothing to do 20 | return 21 | lines.append(plugin_name) 22 | if not os.path.exists(os.path.dirname(fn)): 23 | os.makedirs(os.path.dirname(fn)) 24 | f = open(fn, 'w') 25 | for line in lines: 26 | f.write(line) 27 | f.write('\n') 28 | f.close() 29 | 30 | def remove_plugin(egg_info_dir, plugin_name): 31 | """ 32 | Remove the plugin to the given distribution (or spec), in 33 | .egg-info/paster_plugins.txt. Raises ValueError if the 34 | plugin is not in the file. 35 | """ 36 | fn = os.path.join(egg_info_dir, 'paster_plugins.txt') 37 | if not os.path.exists(fn): 38 | raise ValueError( 39 | "Cannot remove plugin from %s; file does not exist" 40 | % fn) 41 | f = open(fn) 42 | lines = [l.strip() for l in f.readlines() if l.strip()] 43 | f.close() 44 | for line in lines: 45 | # What about version specs? 46 | if line.lower() == plugin_name.lower(): 47 | break 48 | else: 49 | raise ValueError( 50 | "Plugin %s not found in file %s (from: %s)" 51 | % (plugin_name, fn, lines)) 52 | lines.remove(line) 53 | print('writing', lines) 54 | f = open(fn, 'w') 55 | for line in lines: 56 | f.write(line) 57 | f.write('\n') 58 | f.close() 59 | 60 | def find_egg_info_dir(dir): 61 | while 1: 62 | try: 63 | filenames = os.listdir(dir) 64 | except OSError: 65 | # Probably permission denied or something 66 | return None 67 | for fn in filenames: 68 | if (fn.endswith('.egg-info') 69 | and os.path.isdir(os.path.join(dir, fn))): 70 | return os.path.join(dir, fn) 71 | parent = os.path.dirname(dir) 72 | if parent == dir: 73 | # Top-most directory 74 | return None 75 | dir = parent 76 | 77 | def resolve_plugins(plugin_list): 78 | found = [] 79 | while plugin_list: 80 | plugin = plugin_list.pop() 81 | try: 82 | pkg_resources.require(plugin) 83 | except pkg_resources.DistributionNotFound as e: 84 | msg = '%sNot Found%s: %s (did you run python setup.py develop?)' 85 | if str(e) != plugin: 86 | e.args = (msg % (str(e) + ': ', ' for', plugin)), 87 | else: 88 | e.args = (msg % ('', '', plugin)), 89 | raise 90 | found.append(plugin) 91 | dist = get_distro(plugin) 92 | if dist.has_metadata('paster_plugins.txt'): 93 | data = dist.get_metadata('paster_plugins.txt') 94 | for add_plugin in parse_lines(data): 95 | if add_plugin not in found: 96 | plugin_list.append(add_plugin) 97 | return list(map(get_distro, found)) 98 | 99 | def get_distro(spec): 100 | return pkg_resources.get_distribution(spec) 101 | 102 | def load_commands_from_plugins(plugins): 103 | commands = {} 104 | for plugin in plugins: 105 | commands.update(pkg_resources.get_entry_map( 106 | plugin, group='paste.paster_command')) 107 | return commands 108 | 109 | def parse_lines(data): 110 | result = [] 111 | for line in data.splitlines(): 112 | line = line.strip() 113 | if line and not line.startswith('#'): 114 | result.append(line) 115 | return result 116 | 117 | def load_global_commands(): 118 | commands = {} 119 | for p in pkg_resources.iter_entry_points('paste.global_paster_command'): 120 | commands[p.name] = p 121 | return commands 122 | 123 | def egg_name(dist_name): 124 | return pkg_resources.to_filename(pkg_resources.safe_name(dist_name)) 125 | 126 | def egg_info_dir(base_dir, dist_name): 127 | all = [] 128 | for dir_extension in ['.'] + os.listdir(base_dir): 129 | full = os.path.join(base_dir, dir_extension, 130 | egg_name(dist_name)+'.egg-info') 131 | all.append(full) 132 | if os.path.exists(full): 133 | return full 134 | raise IOError("No egg-info directory found (looked in %s)" 135 | % ', '.join(all)) 136 | -------------------------------------------------------------------------------- /paste/script/request.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | import os 4 | import re 5 | import sys 6 | from urllib.parse import quote, urljoin 7 | 8 | from .command import Command, BadCommand 9 | from paste.deploy import loadapp 10 | from paste.wsgilib import raw_interactive 11 | 12 | class RequestCommand(Command): 13 | 14 | min_args = 2 15 | usage = 'CONFIG_FILE URL [OPTIONS/ARGUMENTS]' 16 | takes_config_file = 1 17 | summary = "Run a request for the described application" 18 | description = """\ 19 | This command makes an artifical request to a web application that 20 | uses a paste.deploy configuration file for the server and 21 | application. 22 | 23 | Use 'paster request config.ini /url' to request /url. Use 24 | 'paster post config.ini /url < data' to do a POST with the given 25 | request body. 26 | 27 | If the URL is relative (doesn't begin with /) it is interpreted as 28 | relative to /.command/. The variable environ['paste.command_request'] 29 | will be set to True in the request, so your application can distinguish 30 | these calls from normal requests. 31 | 32 | Note that you can pass options besides the options listed here; any unknown 33 | options will be passed to the application in environ['QUERY_STRING']. 34 | """ 35 | 36 | parser = Command.standard_parser(quiet=True) 37 | parser.add_option('-n', '--app-name', 38 | dest='app_name', 39 | metavar='NAME', 40 | help="Load the named application (default main)") 41 | parser.add_option('--config-var', 42 | dest='config_vars', 43 | metavar='NAME:VALUE', 44 | action='append', 45 | help="Variable to make available in the config for %()s substitution " 46 | "(you can use this option multiple times)") 47 | parser.add_option('--header', 48 | dest='headers', 49 | metavar='NAME:VALUE', 50 | action='append', 51 | help="Header to add to request (you can use this option multiple times)") 52 | parser.add_option('--display-headers', 53 | dest='display_headers', 54 | action='store_true', 55 | help='Display headers before the response body') 56 | 57 | ARG_OPTIONS = ['-n', '--app-name', '--config-var', '--header'] 58 | OTHER_OPTIONS = ['--display-headers'] 59 | 60 | ## FIXME: some kind of verbosity? 61 | ## FIXME: allow other methods than POST and GET? 62 | 63 | _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I) 64 | 65 | def command(self): 66 | vars = {} 67 | app_spec = self.args[0] 68 | url = self.args[1] 69 | url = urljoin('/.command/', url) 70 | if self.options.config_vars: 71 | for item in self.option.config_vars: 72 | if ':' not in item: 73 | raise BadCommand( 74 | "Bad option, should be name:value : --config-var=%s" % item) 75 | name, value = item.split(':', 1) 76 | vars[name] = value 77 | headers = {} 78 | if self.options.headers: 79 | for item in self.options.headers: 80 | if ':' not in item: 81 | raise BadCommand( 82 | "Bad option, should be name:value : --header=%s" % item) 83 | name, value = item.split(':', 1) 84 | headers[name] = value.strip() 85 | if not self._scheme_re.search(app_spec): 86 | app_spec = 'config:'+app_spec 87 | if self.options.app_name: 88 | if '#' in app_spec: 89 | app_spec = app_spec.split('#', 1)[0] 90 | app_spec = app_spec + '#' + self.options.app_name 91 | app = loadapp(app_spec, relative_to=os.getcwd(), global_conf=vars) 92 | if self.command_name.lower() == 'post': 93 | request_method = 'POST' 94 | else: 95 | request_method = 'GET' 96 | qs = [] 97 | for item in self.args[2:]: 98 | if '=' in item: 99 | item = quote(item.split('=', 1)[0]) + '=' + quote(item.split('=', 1)[1]) 100 | else: 101 | item = quote(item) 102 | qs.append(item) 103 | qs = '&'.join(qs) 104 | 105 | environ = { 106 | 'REQUEST_METHOD': request_method, 107 | ## FIXME: shouldn't be static (an option?): 108 | 'CONTENT_TYPE': 'text/plain', 109 | 'wsgi.run_once': True, 110 | 'wsgi.multithread': False, 111 | 'wsgi.multiprocess': False, 112 | 'wsgi.errors': sys.stderr, 113 | 'QUERY_STRING': qs, 114 | 'HTTP_ACCEPT': 'text/plain;q=1.0, */*;q=0.1', 115 | 'paste.command_request': True, 116 | } 117 | if request_method == 'POST': 118 | environ['wsgi.input'] = sys.stdin 119 | environ['CONTENT_LENGTH'] = '-1' 120 | for name, value in headers.items(): 121 | if name.lower() == 'content-type': 122 | name = 'CONTENT_TYPE' 123 | else: 124 | name = 'HTTP_'+name.upper().replace('-', '_') 125 | environ[name] = value 126 | 127 | status, headers, output, errors = raw_interactive(app, url, **environ) 128 | assert not errors, "errors should be printed directly to sys.stderr" 129 | if self.options.display_headers: 130 | for name, value in headers: 131 | sys.stdout.write('%s: %s\n' % (name, value)) 132 | sys.stdout.write('\n') 133 | sys.stdout.flush() 134 | sys.stdout.buffer.write(output) 135 | sys.stdout.buffer.flush() 136 | sys.stdout.flush() 137 | status_int = int(status.split()[0]) 138 | if status_int != 200: 139 | return status_int 140 | 141 | def parse_args(self, args): 142 | if args == ['-h']: 143 | Command.parse_args(self, args) 144 | return 145 | # These are the arguments parsed normally: 146 | normal_args = [] 147 | # And these are arguments passed to the URL: 148 | extra_args = [] 149 | # This keeps track of whether we have the two required positional arguments: 150 | pos_args = 0 151 | while args: 152 | start = args[0] 153 | if not start.startswith('-'): 154 | if pos_args < 2: 155 | pos_args += 1 156 | normal_args.append(start) 157 | args.pop(0) 158 | continue 159 | else: 160 | normal_args.append(start) 161 | args.pop(0) 162 | continue 163 | else: 164 | found = False 165 | for option in self.ARG_OPTIONS: 166 | if start == option: 167 | normal_args.append(start) 168 | args.pop(0) 169 | if not args: 170 | raise BadCommand( 171 | "Option %s takes an argument" % option) 172 | normal_args.append(args.pop(0)) 173 | found = True 174 | break 175 | elif start.startswith(option+'='): 176 | normal_args.append(start) 177 | args.pop(0) 178 | found = True 179 | break 180 | if found: 181 | continue 182 | if start in self.OTHER_OPTIONS: 183 | normal_args.append(start) 184 | args.pop(0) 185 | continue 186 | extra_args.append(start) 187 | args.pop(0) 188 | Command.parse_args(self, normal_args) 189 | # Add the extra arguments back in: 190 | self.args = self.args + extra_args 191 | 192 | -------------------------------------------------------------------------------- /paste/script/templates.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | import sys 4 | import os 5 | import inspect 6 | from . import copydir 7 | from . import command 8 | 9 | from paste.util.template import paste_script_template_renderer 10 | 11 | class Template: 12 | 13 | # Subclasses must define: 14 | # _template_dir (or template_dir()) 15 | # summary 16 | 17 | # Variables this template uses (mostly for documentation now) 18 | # a list of instances of var() 19 | vars = [] 20 | 21 | # Eggs that should be added as plugins: 22 | egg_plugins = [] 23 | 24 | # Templates that must be applied first: 25 | required_templates = [] 26 | 27 | # Use Cheetah for substituting templates: 28 | use_cheetah = False 29 | # If true, then read all the templates to find the variables: 30 | read_vars_from_templates = False 31 | 32 | # You can also give this function/method to use something other 33 | # than Cheetah or string.Template. The function should be of the 34 | # signature template_renderer(content, vars, filename=filename). 35 | # Careful you don't turn this into a method by putting a function 36 | # here (without staticmethod)! 37 | template_renderer = None 38 | 39 | def __init__(self, name): 40 | self.name = name 41 | self._read_vars = None 42 | 43 | def module_dir(self): 44 | """Returns the module directory of this template.""" 45 | mod = sys.modules[self.__class__.__module__] 46 | return os.path.dirname(mod.__file__) 47 | 48 | def template_dir(self): 49 | assert self._template_dir is not None, ( 50 | "Template %r didn't set _template_dir" % self) 51 | if isinstance( self._template_dir, tuple): 52 | return self._template_dir 53 | else: 54 | return os.path.join(self.module_dir(), self._template_dir) 55 | 56 | def run(self, command, output_dir, vars): 57 | self.pre(command, output_dir, vars) 58 | self.write_files(command, output_dir, vars) 59 | self.post(command, output_dir, vars) 60 | 61 | def check_vars(self, vars, cmd): 62 | expect_vars = self.read_vars(cmd) 63 | if not expect_vars: 64 | # Assume that variables aren't defined 65 | return vars 66 | converted_vars = {} 67 | unused_vars = vars.copy() 68 | errors = [] 69 | for var in expect_vars: 70 | if var.name not in unused_vars: 71 | if cmd.interactive: 72 | prompt = 'Enter %s' % var.full_description() 73 | response = cmd.challenge(prompt, var.default, var.should_echo) 74 | converted_vars[var.name] = response 75 | elif var.default is command.NoDefault: 76 | errors.append('Required variable missing: %s' 77 | % var.full_description()) 78 | else: 79 | converted_vars[var.name] = var.default 80 | else: 81 | converted_vars[var.name] = unused_vars.pop(var.name) 82 | if errors: 83 | raise command.BadCommand( 84 | 'Errors in variables:\n%s' % '\n'.join(errors)) 85 | converted_vars.update(unused_vars) 86 | vars.update(converted_vars) 87 | return converted_vars 88 | 89 | def read_vars(self, command=None): 90 | if self._read_vars is not None: 91 | return self._read_vars 92 | assert (not self.read_vars_from_templates 93 | or self.use_cheetah), ( 94 | "You can only read variables from templates if using Cheetah") 95 | if not self.read_vars_from_templates: 96 | self._read_vars = self.vars 97 | return self.vars 98 | 99 | vars = self.vars[:] 100 | var_names = [var.name for var in self.vars] 101 | read_vars = find_args_in_dir( 102 | self.template_dir(), 103 | verbose=command and command.verbose > 1).items() 104 | read_vars.sort() 105 | for var_name, var in read_vars: 106 | if var_name not in var_names: 107 | vars.append(var) 108 | self._read_vars = vars 109 | return vars 110 | 111 | def write_files(self, command, output_dir, vars): 112 | template_dir = self.template_dir() 113 | if not os.path.exists(output_dir): 114 | print("Creating directory %s" % output_dir) 115 | if not command.simulate: 116 | # Don't let copydir create this top-level directory, 117 | # since copydir will svn add it sometimes: 118 | os.makedirs(output_dir) 119 | copydir.copy_dir(template_dir, output_dir, 120 | vars, 121 | verbosity=command.verbose, 122 | simulate=command.options.simulate, 123 | interactive=command.interactive, 124 | overwrite=command.options.overwrite, 125 | indent=1, 126 | use_cheetah=self.use_cheetah, 127 | template_renderer=self.template_renderer) 128 | 129 | def print_vars(self, indent=0): 130 | vars = self.read_vars() 131 | var.print_vars(vars) 132 | 133 | def pre(self, command, output_dir, vars): 134 | """ 135 | Called before template is applied. 136 | """ 137 | pass 138 | 139 | def post(self, command, output_dir, vars): 140 | """ 141 | Called after template is applied. 142 | """ 143 | pass 144 | 145 | NoDefault = command.NoDefault 146 | 147 | class var(object): 148 | 149 | def __init__(self, name, description, 150 | default='', should_echo=True): 151 | self.name = name 152 | self.description = description 153 | self.default = default 154 | self.should_echo = should_echo 155 | 156 | def __repr__(self): 157 | return '<%s %s default=%r should_echo=%s>' % ( 158 | self.__class__.__name__, 159 | self.name, self.default, self.should_echo) 160 | 161 | def full_description(self): 162 | if self.description: 163 | return '%s (%s)' % (self.name, self.description) 164 | else: 165 | return self.name 166 | 167 | def print_vars(cls, vars, indent=0): 168 | max_name = max([len(v.name) for v in vars]) 169 | for var in vars: 170 | if var.description: 171 | print('%s%s%s %s' % ( 172 | ' '*indent, 173 | var.name, 174 | ' '*(max_name-len(var.name)), 175 | var.description)) 176 | else: 177 | print(' %s' % var.name) 178 | if var.default is not command.NoDefault: 179 | print(' default: %r' % var.default) 180 | if var.should_echo is True: 181 | print(' should_echo: %s' % var.should_echo) 182 | print() 183 | 184 | print_vars = classmethod(print_vars) 185 | 186 | class BasicPackage(Template): 187 | 188 | _template_dir = 'paster-templates/basic_package' 189 | summary = "A basic setuptools-enabled package" 190 | vars = [ 191 | var('version', 'Version (like 0.1)'), 192 | var('description', 'One-line description of the package'), 193 | var('long_description', 'Multi-line description (in reST)'), 194 | var('keywords', 'Space-separated keywords/tags'), 195 | var('author', 'Author name'), 196 | var('author_email', 'Author email'), 197 | var('url', 'URL of homepage'), 198 | var('license_name', 'License name'), 199 | var('zip_safe', 'True/False: if the package can be distributed as a .zip file', default=False), 200 | ] 201 | 202 | template_renderer = staticmethod(paste_script_template_renderer) 203 | 204 | _skip_variables = ['VFN', 'currentTime', 'self', 'VFFSL', 'dummyTrans', 205 | 'getmtime', 'trans'] 206 | 207 | def find_args_in_template(template): 208 | if isinstance(template, str): 209 | # Treat as filename: 210 | import Cheetah.Template 211 | template = Cheetah.Template.Template(file=template) 212 | if not hasattr(template, 'body'): 213 | # Don't know... 214 | return None 215 | method = template.body 216 | args, varargs, varkw, defaults = inspect.getargspec(method) 217 | defaults=list(defaults or []) 218 | vars = [] 219 | while args: 220 | if len(args) == len(defaults): 221 | default = defaults.pop(0) 222 | else: 223 | default = command.NoDefault 224 | arg = args.pop(0) 225 | if arg in _skip_variables: 226 | continue 227 | # @@: No way to get description yet 228 | vars.append( 229 | var(arg, description=None, 230 | default=default)) 231 | return vars 232 | 233 | def find_args_in_dir(dir, verbose=False): 234 | all_vars = {} 235 | for fn in os.listdir(dir): 236 | if fn.startswith('.') or fn == 'CVS' or fn == '_darcs': 237 | continue 238 | full = os.path.join(dir, fn) 239 | if os.path.isdir(full): 240 | inner_vars = find_args_in_dir(full) 241 | elif full.endswith('_tmpl'): 242 | inner_vars = {} 243 | found = find_args_in_template(full) 244 | if found is None: 245 | # Couldn't read variables 246 | if verbose: 247 | print('Template %s has no parseable variables' % full) 248 | continue 249 | for var in found: 250 | inner_vars[var.name] = var 251 | else: 252 | # Not a template, don't read it 253 | continue 254 | if verbose: 255 | print('Found variable(s) %s in Template %s' % ( 256 | ', '.join(inner_vars.keys()), full)) 257 | for var_name, var in inner_vars.items(): 258 | # Easy case: 259 | if var_name not in all_vars: 260 | all_vars[var_name] = var 261 | continue 262 | # Emit warnings if the variables don't match well: 263 | cur_var = all_vars[var_name] 264 | if not cur_var.description: 265 | cur_var.description = var.description 266 | elif (cur_var.description and var.description 267 | and var.description != cur_var.description): 268 | print(( 269 | "Variable descriptions do not match: %s: %s and %s" 270 | % (var_name, cur_var.description, var.description)), file=sys.stderr) 271 | if (cur_var.default is not command.NoDefault 272 | and var.default is not command.NoDefault 273 | and cur_var.default != var.default): 274 | print(( 275 | "Variable defaults do not match: %s: %r and %r" 276 | % (var_name, cur_var.default, var.default)), file=sys.stderr) 277 | return all_vars 278 | 279 | -------------------------------------------------------------------------------- /paste/script/testapp.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | try: 4 | import html 5 | except ImportError: 6 | import cgi as html 7 | 8 | html_page_template = ''' 9 | 10 | 11 | Test Application 12 | 13 | 14 |

Test Application: Working!

15 | 16 | 17 | %(environ)s 18 |
19 | 20 |

21 | Note: to see an error report, append ?error=true 22 | to the URL 23 |

24 | 25 | 26 | 27 | ''' 28 | 29 | html_row_template = ''' 30 | 31 | %(key)s 32 | %(value_literal)s 33 | 34 | ''' 35 | 36 | text_page_template = '%(environ)s' 37 | text_row_template = '%(key)s: %(value_repr)s\n' 38 | 39 | def make_literal(value): 40 | value = html.escape(value, 1) 41 | value = value.replace('\n\r', '\n') 42 | value = value.replace('\r', '\n') 43 | value = value.replace('\n', '
\n') 44 | return value 45 | 46 | class TestApplication(object): 47 | 48 | """ 49 | A test WSGI application, that prints out all the environmental 50 | variables, and if you add ``?error=t`` to the URL it will 51 | deliberately throw an exception. 52 | """ 53 | 54 | def __init__(self, global_conf=None, text=False): 55 | self.global_conf = global_conf 56 | self.text = text 57 | 58 | def __call__(self, environ, start_response): 59 | if environ.get('QUERY_STRING', '').find('error=') >= 0: 60 | assert 0, "Here is your error report, ordered and delivered" 61 | if self.text: 62 | page_template = text_page_template 63 | row_template = text_row_template 64 | content_type = 'text/plain; charset=utf8' 65 | else: 66 | page_template = html_page_template 67 | row_template = html_row_template 68 | content_type = 'text/html; charset=utf8' 69 | keys = sorted(environ.keys()) 70 | rows = [] 71 | for key in keys: 72 | data = {'key': key} 73 | value = environ[key] 74 | data['value'] = value 75 | try: 76 | value = repr(value) 77 | except Exception as e: 78 | value = 'Cannot use repr(): %s' % e 79 | data['value_repr'] = value 80 | data['value_literal'] = make_literal(value) 81 | row = row_template % data 82 | rows.append(row) 83 | rows = ''.join(rows) 84 | page = page_template % {'environ': rows} 85 | if isinstance(page, str): 86 | page = page.encode('utf8') 87 | headers = [('Content-type', content_type)] 88 | start_response('200 OK', headers) 89 | return [page] 90 | 91 | 92 | def make_test_application(global_conf, text=False, lint=False): 93 | from paste.deploy.converters import asbool 94 | text = asbool(text) 95 | lint = asbool(lint) 96 | app = TestApplication(global_conf=global_conf, text=text) 97 | if lint: 98 | from paste.lint import middleware 99 | app = middleware(app) 100 | return app 101 | 102 | make_test_application.__doc__ = TestApplication.__doc__ 103 | -------------------------------------------------------------------------------- /paste/script/twisted_web2_server.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | # @@: THIS IS INCOMPLETE! 4 | 5 | def run_twisted(wsgi_app, global_conf, 6 | host='127.0.0.1', port='8080'): 7 | host = host or None 8 | import twisted.web2.wsgi 9 | import twisted.web2.log 10 | import twisted.web2.channel 11 | import twisted.web2.server 12 | import twisted.internet.reactor 13 | wsgi_resource = twisted.web2.wsgi.WSGIResource(wsgi_app) 14 | resource = twisted.web2.log.LogWrapperResource(wsgi_resource) 15 | twisted.web2.log.DefaultCommonAccessLoggingObserver().start() 16 | site = twisted.web2.server.Site(resource) 17 | factory = twisted.web2.channel.HTTPFactory(site) 18 | # --- start reactor for listen port 19 | twisted.internet.reactor.listenTCP(int(port), factory, interface=host) 20 | twisted.internet.reactor.run() 21 | -------------------------------------------------------------------------------- /paste/script/util/__init__.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | # 4 | -------------------------------------------------------------------------------- /paste/script/util/logging_config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2001-2005 by Vinay Sajip. All Rights Reserved. 2 | # 3 | # Permission to use, copy, modify, and distribute this software and its 4 | # documentation for any purpose and without fee is hereby granted, 5 | # provided that the above copyright notice appear in all copies and that 6 | # both that copyright notice and this permission notice appear in 7 | # supporting documentation, and that the name of Vinay Sajip 8 | # not be used in advertising or publicity pertaining to distribution 9 | # of the software without specific, written prior permission. 10 | # VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 11 | # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 12 | # VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 13 | # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 14 | # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | """ 18 | Configuration functions for the logging package for Python. The core package 19 | is based on PEP 282 and comments thereto in comp.lang.python, and influenced 20 | by Apache's log4j system. 21 | 22 | Should work under Python versions >= 1.5.2, except that source line 23 | information is not available unless 'sys._getframe()' is. 24 | 25 | Copyright (C) 2001-2004 Vinay Sajip. All Rights Reserved. 26 | 27 | To use, simply 'import logging' and log away! 28 | """ 29 | 30 | import logging 31 | import logging.handlers 32 | import os 33 | import socket 34 | import struct 35 | import sys 36 | import traceback 37 | 38 | try: 39 | import threading 40 | except ImportError: 41 | thread = None 42 | 43 | from socketserver import ThreadingTCPServer, StreamRequestHandler 44 | 45 | 46 | DEFAULT_LOGGING_CONFIG_PORT = 9030 47 | 48 | if sys.platform == "win32": 49 | RESET_ERROR = 10054 #WSAECONNRESET 50 | else: 51 | RESET_ERROR = 104 #ECONNRESET 52 | 53 | # 54 | # The following code implements a socket listener for on-the-fly 55 | # reconfiguration of logging. 56 | # 57 | # _listener holds the server object doing the listening 58 | _listener = None 59 | 60 | log_levelNames = { 61 | 'CRITICAL' : logging.CRITICAL, 62 | 'ERROR' : logging.ERROR, 63 | 'WARN' : logging.WARNING, 64 | 'WARNING' : logging.WARNING, 65 | 'INFO' : logging.INFO, 66 | 'DEBUG' : logging.DEBUG, 67 | 'NOTSET' : logging.NOTSET, 68 | } 69 | 70 | def fileConfig(fname, defaults=None): 71 | """ 72 | Read the logging configuration from a ConfigParser-format file. 73 | 74 | This can be called several times from an application, allowing an end user 75 | the ability to select from various pre-canned configurations (if the 76 | developer provides a mechanism to present the choices and load the chosen 77 | configuration). 78 | In versions of ConfigParser which have the readfp method [typically 79 | shipped in 2.x versions of Python], you can pass in a file-like object 80 | rather than a filename, in which case the file-like object will be read 81 | using readfp. 82 | """ 83 | import configparser 84 | 85 | cp = configparser.RawConfigParser(defaults) 86 | if hasattr(cp, 'readfp') and hasattr(fname, 'readline'): 87 | cp.readfp(fname) 88 | else: 89 | cp.read(fname) 90 | 91 | formatters = _create_formatters(cp) 92 | 93 | # critical section 94 | with logging._lock: 95 | logging._handlers.clear() 96 | if hasattr(logging, '_handlerList'): 97 | del logging._handlerList[:] 98 | # Handlers add themselves to logging._handlers 99 | handlers = _install_handlers(cp, formatters) 100 | _install_loggers(cp, handlers) 101 | 102 | 103 | def _resolve(name): 104 | """Resolve a dotted name to a global object.""" 105 | name = name.split('.') 106 | used = name.pop(0) 107 | found = __import__(used) 108 | for n in name: 109 | used = used + '.' + n 110 | try: 111 | found = getattr(found, n) 112 | except AttributeError: 113 | __import__(used) 114 | found = getattr(found, n) 115 | return found 116 | 117 | 118 | def _create_formatters(cp): 119 | """Create and return formatters""" 120 | flist = cp.get("formatters", "keys") 121 | if not len(flist): 122 | return {} 123 | flist = flist.split(",") 124 | formatters = {} 125 | for form in flist: 126 | form = form.strip() 127 | sectname = "formatter_%s" % form 128 | opts = cp.options(sectname) 129 | if "format" in opts: 130 | fs = cp.get(sectname, "format") 131 | else: 132 | fs = None 133 | if "datefmt" in opts: 134 | dfs = cp.get(sectname, "datefmt") 135 | else: 136 | dfs = None 137 | c = logging.Formatter 138 | if "class" in opts: 139 | class_name = cp.get(sectname, "class") 140 | if class_name: 141 | c = _resolve(class_name) 142 | f = c(fs, dfs) 143 | formatters[form] = f 144 | return formatters 145 | 146 | 147 | def _install_handlers(cp, formatters): 148 | """Install and return handlers""" 149 | hlist = cp.get("handlers", "keys") 150 | if not len(hlist): 151 | return {} 152 | hlist = hlist.split(",") 153 | handlers = {} 154 | fixups = [] #for inter-handler references 155 | for hand in hlist: 156 | hand = hand.strip() 157 | sectname = "handler_%s" % hand 158 | klass = cp.get(sectname, "class") 159 | opts = cp.options(sectname) 160 | if "formatter" in opts: 161 | fmt = cp.get(sectname, "formatter") 162 | else: 163 | fmt = "" 164 | try: 165 | klass = eval(klass, vars(logging)) 166 | except (AttributeError, NameError): 167 | klass = _resolve(klass) 168 | args = cp.get(sectname, "args") 169 | args = eval(args, vars(logging)) 170 | h = klass(*args) 171 | if "level" in opts: 172 | level = cp.get(sectname, "level") 173 | h.setLevel(log_levelNames[level]) 174 | if len(fmt): 175 | h.setFormatter(formatters[fmt]) 176 | #temporary hack for FileHandler and MemoryHandler. 177 | if klass == logging.handlers.MemoryHandler: 178 | if "target" in opts: 179 | target = cp.get(sectname,"target") 180 | else: 181 | target = "" 182 | if len(target): #the target handler may not be loaded yet, so keep for later... 183 | fixups.append((h, target)) 184 | handlers[hand] = h 185 | #now all handlers are loaded, fixup inter-handler references... 186 | for h, t in fixups: 187 | h.setTarget(handlers[t]) 188 | return handlers 189 | 190 | 191 | def _install_loggers(cp, handlers): 192 | """Create and install loggers""" 193 | 194 | # configure the root first 195 | llist = cp.get("loggers", "keys") 196 | llist = llist.split(",") 197 | llist = [x.strip() for x in llist] 198 | llist.remove("root") 199 | sectname = "logger_root" 200 | root = logging.root 201 | log = root 202 | opts = cp.options(sectname) 203 | if "level" in opts: 204 | level = cp.get(sectname, "level") 205 | log.setLevel(log_levelNames[level]) 206 | for h in root.handlers[:]: 207 | root.removeHandler(h) 208 | hlist = cp.get(sectname, "handlers") 209 | if len(hlist): 210 | hlist = hlist.split(",") 211 | for hand in hlist: 212 | log.addHandler(handlers[hand.strip()]) 213 | 214 | #and now the others... 215 | #we don't want to lose the existing loggers, 216 | #since other threads may have pointers to them. 217 | #existing is set to contain all existing loggers, 218 | #and as we go through the new configuration we 219 | #remove any which are configured. At the end, 220 | #what's left in existing is the set of loggers 221 | #which were in the previous configuration but 222 | #which are not in the new configuration. 223 | existing = list(root.manager.loggerDict.keys()) 224 | #now set up the new ones... 225 | for log in llist: 226 | sectname = "logger_%s" % log 227 | qn = cp.get(sectname, "qualname") 228 | opts = cp.options(sectname) 229 | if "propagate" in opts: 230 | propagate = cp.getint(sectname, "propagate") 231 | else: 232 | propagate = 1 233 | logger = logging.getLogger(qn) 234 | if qn in existing: 235 | existing.remove(qn) 236 | if "level" in opts: 237 | level = cp.get(sectname, "level") 238 | logger.setLevel(log_levelNames[level]) 239 | for h in logger.handlers[:]: 240 | logger.removeHandler(h) 241 | logger.propagate = propagate 242 | logger.disabled = 0 243 | hlist = cp.get(sectname, "handlers") 244 | if len(hlist): 245 | hlist = hlist.split(",") 246 | for hand in hlist: 247 | logger.addHandler(handlers[hand.strip()]) 248 | 249 | #Disable any old loggers. There's no point deleting 250 | #them as other threads may continue to hold references 251 | #and by disabling them, you stop them doing any logging. 252 | for log in existing: 253 | root.manager.loggerDict[log].disabled = 1 254 | 255 | 256 | def listen(port=DEFAULT_LOGGING_CONFIG_PORT): 257 | """ 258 | Start up a socket server on the specified port, and listen for new 259 | configurations. 260 | 261 | These will be sent as a file suitable for processing by fileConfig(). 262 | Returns a Thread object on which you can call start() to start the server, 263 | and which you can join() when appropriate. To stop the server, call 264 | stopListening(). 265 | """ 266 | if not thread: 267 | raise NotImplementedError("listen() needs threading to work") 268 | 269 | class ConfigStreamHandler(StreamRequestHandler): 270 | """ 271 | Handler for a logging configuration request. 272 | 273 | It expects a completely new logging configuration and uses fileConfig 274 | to install it. 275 | """ 276 | def handle(self): 277 | """ 278 | Handle a request. 279 | 280 | Each request is expected to be a 4-byte length, packed using 281 | struct.pack(">L", n), followed by the config file. 282 | Uses fileConfig() to do the grunt work. 283 | """ 284 | import tempfile 285 | try: 286 | conn = self.connection 287 | chunk = conn.recv(4) 288 | if len(chunk) == 4: 289 | slen = struct.unpack(">L", chunk)[0] 290 | chunk = self.connection.recv(slen) 291 | while len(chunk) < slen: 292 | chunk = chunk + conn.recv(slen - len(chunk)) 293 | #Apply new configuration. We'd like to be able to 294 | #create a StringIO and pass that in, but unfortunately 295 | #1.5.2 ConfigParser does not support reading file 296 | #objects, only actual files. So we create a temporary 297 | #file and remove it later. 298 | file = tempfile.mktemp(".ini") 299 | f = open(file, "w") 300 | f.write(chunk) 301 | f.close() 302 | try: 303 | fileConfig(file) 304 | except (KeyboardInterrupt, SystemExit): 305 | raise 306 | except: 307 | traceback.print_exc() 308 | os.remove(file) 309 | except socket.error as e: 310 | if type(e.args) != tuple: 311 | raise 312 | else: 313 | errcode = e.args[0] 314 | if errcode != RESET_ERROR: 315 | raise 316 | 317 | class ConfigSocketReceiver(ThreadingTCPServer): 318 | """ 319 | A simple TCP socket-based logging config receiver. 320 | """ 321 | 322 | allow_reuse_address = 1 323 | 324 | def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT, 325 | handler=None): 326 | ThreadingTCPServer.__init__(self, (host, port), handler) 327 | with logging._lock: 328 | self.abort = 0 329 | self.timeout = 1 330 | 331 | def serve_until_stopped(self): 332 | import select 333 | abort = 0 334 | while not abort: 335 | rd, wr, ex = select.select([self.socket.fileno()], 336 | [], [], 337 | self.timeout) 338 | if rd: 339 | self.handle_request() 340 | with logging._lock: 341 | abort = self.abort 342 | 343 | def serve(rcvr, hdlr, port): 344 | server = rcvr(port=port, handler=hdlr) 345 | global _listener 346 | with logging._lock: 347 | _listener = server 348 | server.serve_until_stopped() 349 | 350 | return threading.Thread(target=serve, 351 | args=(ConfigSocketReceiver, 352 | ConfigStreamHandler, port)) 353 | 354 | def stopListening(): 355 | """ 356 | Stop the listening server which was created with a call to listen(). 357 | """ 358 | global _listener 359 | if _listener: 360 | with logging._lock: 361 | _listener.abort = 1 362 | _listener = None 363 | -------------------------------------------------------------------------------- /paste/script/util/secret.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | """ 4 | Create random secrets. 5 | """ 6 | 7 | import base64 8 | import os 9 | import random 10 | 11 | def random_bytes(length): 12 | """ 13 | Return a string of the given length. Uses ``os.urandom`` if it 14 | can, or just pseudo-random numbers otherwise. 15 | """ 16 | try: 17 | return os.urandom(length) 18 | except AttributeError: 19 | return b''.join([ 20 | bytes((random.randrange(256),)) for i in range(length)]) 21 | 22 | def secret_string(length=25): 23 | """ 24 | Returns a random string of the given length. The string 25 | is a base64-encoded version of a set of random bytes, truncated 26 | to the given length (and without any newlines). 27 | """ 28 | s = random_bytes(length) 29 | s = base64.b64encode(s) 30 | s = s.decode('ascii') 31 | for badchar in '\n\r=': 32 | s = s.replace(badchar, '') 33 | # We're wasting some characters here. But random characters are 34 | # cheap ;) 35 | return s[:length] 36 | -------------------------------------------------------------------------------- /paste/script/wsgiutils_server.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | from paste.script.serve import ensure_port_cleanup 4 | from paste.translogger import TransLogger 5 | 6 | def run_server(wsgi_app, global_conf, host='localhost', 7 | port=8080): 8 | import wsgiserver 9 | 10 | logged_app = TransLogger(wsgi_app) 11 | port = int(port) 12 | # For some reason this is problematic on this server: 13 | ensure_port_cleanup([(host, port)], maxtries=2, sleeptime=0.5) 14 | server = wsgiserver.WSGIServer(logged_app, host=host, port=port) 15 | logged_app.logger.info('Starting HTTP server on http://%s:%s', 16 | host, port) 17 | server.start() 18 | 19 | -------------------------------------------------------------------------------- /regen-docs: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir -p docs/_static docs/_build 4 | sphinx-build -E -b html docs/ docs/_build || exit 1 5 | if [ "$1" = "publish" ] ; then 6 | cd docs/ 7 | echo "Uploading files..." 8 | scp -r _build/* ianb@webwareforpython.org:/home/paste/htdocs/script/ 9 | fi 10 | -------------------------------------------------------------------------------- /scripts/paster: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | relative_paste = os.path.join( 6 | os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'paste') 7 | 8 | if os.path.exists(relative_paste): 9 | sys.path.insert(0, os.path.dirname(relative_paste)) 10 | 11 | from paste.script import command 12 | command.run() 13 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [nosetests] 5 | tests = tests 6 | 7 | [egg_info] 8 | tag_build = 9 | tag_date = 0 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Procedure to release a new version: 2 | # 3 | # - run tests: run tox 4 | # - update version in setup.py (version) 5 | # - check that "python setup.py sdist" contains all files tracked by 6 | # the SCM (Mercurial): update MANIFEST.in if needed 7 | # - update changelog: docs/news.txt 8 | # 9 | # - increment version in setup.py (version) 10 | # - git commit 11 | # - git tag -s VERSION 12 | # - git push 13 | # - python setup.py sdist bdist_wheel upload --sign 14 | 15 | from setuptools import setup, find_packages 16 | import os 17 | import re 18 | import sys 19 | 20 | version = '3.7.0' 21 | 22 | news = os.path.join(os.path.dirname(__file__), 'docs', 'news.txt') 23 | found_news = '' 24 | if os.path.exists(news): 25 | with open(news) as fp: 26 | news = fp.read() 27 | parts = re.split(r'([0-9\.]+)\s*\n\r?-+\n\r?', news) 28 | for i in range(len(parts)-1): 29 | if parts[i] == version: 30 | found_news = parts[i+i] 31 | break 32 | if not found_news: 33 | print('Warning: no news for this version found', file=sys.stderr) 34 | 35 | with open("README.rst") as fp: 36 | long_description = fp.read().strip() 37 | 38 | if found_news: 39 | title = 'Changes in %s' % version 40 | long_description += "\n%s\n%s\n" % (title, '-'*len(title)) 41 | long_description += found_news 42 | 43 | setup( 44 | name="PasteScript", 45 | version=version, 46 | description="A pluggable command-line frontend, including commands to setup package file layouts", 47 | long_description=long_description, 48 | classifiers=[ 49 | "Development Status :: 6 - Mature", 50 | "Intended Audience :: Developers", 51 | "License :: OSI Approved :: MIT License", 52 | "Programming Language :: Python :: 3", 53 | "Programming Language :: Python :: 3.8", 54 | "Programming Language :: Python :: 3.9", 55 | "Programming Language :: Python :: 3.10", 56 | "Programming Language :: Python :: 3.11", 57 | "Programming Language :: Python :: 3.12", 58 | "Programming Language :: Python :: 3.13", 59 | "Topic :: Internet :: WWW/HTTP", 60 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content", 61 | "Topic :: Software Development :: Libraries :: Python Modules", 62 | "Framework :: Paste", 63 | ], 64 | keywords='web wsgi setuptools framework command-line setup', 65 | author="Ian Bicking", 66 | author_email="ianb@colorstudy.com", 67 | url="https://pastescript.readthedocs.io/", 68 | namespace_packages=['paste'], 69 | license='MIT', 70 | python_requires='>=3.8', 71 | packages=find_packages(exclude=['tests','tests.*']), 72 | package_data={ 73 | 'paste.script': ['paster-templates/basic_package/setup.*', 74 | 'paster-templates/basic_package/tests/*.py', 75 | # @@: docs/ doesn't have any files :( 76 | 'paster-templates/basic_package/+package+/*.py'], 77 | }, 78 | zip_safe=False, 79 | extras_require={ 80 | 'Templating': [], 81 | 'Cheetah': ['Cheetah'], 82 | 'Config': ['PasteDeploy'], 83 | 'WSGIUtils': ['WSGIserver'], 84 | 'Flup': ['Flup'], 85 | # the Paste feature means the complete set of features; 86 | # (other features are truly optional) 87 | 'Paste': ['PasteDeploy', 'Cheetah'], 88 | }, 89 | entry_points=""" 90 | [paste.global_paster_command] 91 | help=paste.script.help:HelpCommand 92 | create=paste.script.create_distro:CreateDistroCommand [Templating] 93 | serve=paste.script.serve:ServeCommand [Config] 94 | request=paste.script.request:RequestCommand [Config] 95 | post=paste.script.request:RequestCommand [Config] 96 | exe=paste.script.exe:ExeCommand 97 | points=paste.script.entrypoints:EntryPointCommand 98 | make-config=paste.script.appinstall:MakeConfigCommand 99 | setup-app=paste.script.appinstall:SetupCommand 100 | 101 | [paste.paster_command] 102 | grep = paste.script.grep:GrepCommand 103 | 104 | [paste.paster_create_template] 105 | basic_package=paste.script.templates:BasicPackage 106 | 107 | [paste.server_runner] 108 | wsgiutils=paste.script.wsgiutils_server:run_server [WSGIUtils] 109 | flup_ajp_thread=paste.script.flup_server:run_ajp_thread [Flup] 110 | flup_ajp_fork=paste.script.flup_server:run_ajp_fork [Flup] 111 | flup_fcgi_thread=paste.script.flup_server:run_fcgi_thread [Flup] 112 | flup_fcgi_fork=paste.script.flup_server:run_fcgi_fork [Flup] 113 | flup_scgi_thread=paste.script.flup_server:run_scgi_thread [Flup] 114 | flup_scgi_fork=paste.script.flup_server:run_scgi_fork [Flup] 115 | cgi=paste.script.cgi_server:paste_run_cgi 116 | cherrypy=paste.script.cherrypy_server:cpwsgi_server 117 | twisted=paste.script.twisted_web2_server:run_twisted 118 | 119 | [paste.app_factory] 120 | test=paste.script.testapp:make_test_application 121 | 122 | [paste.entry_point_description] 123 | paste.entry_point_description = paste.script.epdesc:MetaEntryPointDescription 124 | paste.paster_create_template = paste.script.epdesc:CreateTemplateDescription 125 | paste.paster_command = paste.script.epdesc:PasterCommandDescription 126 | paste.global_paster_command = paste.script.epdesc:GlobalPasterCommandDescription 127 | paste.app_install = paste.script.epdesc:AppInstallDescription 128 | 129 | # These aren't part of Paste Script particularly, but 130 | # we'll document them here 131 | console_scripts = paste.script.epdesc:ConsoleScriptsDescription 132 | # @@: Need non-console scripts... 133 | distutils.commands = paste.script.epdesc:DistutilsCommandsDescription 134 | distutils.setup_keywords = paste.script.epdesc:SetupKeywordsDescription 135 | egg_info.writers = paste.script.epdesc:EggInfoWriters 136 | # @@: Not sure what this does: 137 | #setuptools.file_finders = paste.script.epdesc:SetuptoolsFileFinders 138 | 139 | [console_scripts] 140 | paster=paste.script.command:run 141 | 142 | [distutils.setup_keywords] 143 | paster_plugins = setuptools.dist:assert_string_list 144 | 145 | [egg_info.writers] 146 | paster_plugins.txt = setuptools.command.egg_info:write_arg 147 | """, 148 | install_requires=[ 149 | 'Paste>=3.0', 150 | 'PasteDeploy', 151 | 'setuptools', 152 | ], 153 | ) 154 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import shutil 4 | import pkg_resources 5 | 6 | here = os.path.dirname(__file__) 7 | base = os.path.dirname(here) 8 | fake_packages = os.path.join(here, 'fake_packages') 9 | sys.path.append(fake_packages) 10 | sys.path.append(here) 11 | sys.path.insert(0, base) 12 | 13 | here = os.path.dirname(__file__) 14 | egg_info_dir = os.path.join(here, 'fake_packages', 'FakePlugin.egg', 15 | 'EGG-INFO') 16 | info_dir = os.path.join(here, 'fake_packages', 'FakePlugin.egg', 17 | 'FakePlugin.egg-info') 18 | 19 | if not os.path.exists(egg_info_dir): 20 | try: 21 | os.symlink(info_dir, egg_info_dir) 22 | except: 23 | shutil.copytree(info_dir, egg_info_dir) 24 | 25 | pkg_resources.working_set.add_entry(fake_packages) 26 | pkg_resources.working_set.add_entry(base) 27 | 28 | if not os.environ.get('PASTE_TESTING'): 29 | output_dir = os.path.join(here, 'appsetup', 'output') 30 | if os.path.exists(output_dir): 31 | shutil.rmtree(output_dir) 32 | 33 | pkg_resources.require('FakePlugin') 34 | 35 | -------------------------------------------------------------------------------- /tests/appsetup/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pasteorg/pastescript/00ec57d05831186183384e3f2624cc5a28f3a1d2/tests/appsetup/__init__.py -------------------------------------------------------------------------------- /tests/appsetup/test_make_project.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from unittest import SkipTest 4 | from paste.fixture import TestFileEnvironment 5 | import pkg_resources 6 | for spec in ['PasteScript', 'Paste', 'PasteDeploy', 'PasteWebKit', 7 | 'ZPTKit']: 8 | try: 9 | pkg_resources.require(spec) 10 | except pkg_resources.DistributionNotFound as dnf: 11 | raise SkipTest(repr(dnf)) 12 | 13 | template_path = os.path.join( 14 | os.path.dirname(__file__), 'testfiles') 15 | 16 | test_environ = os.environ.copy() 17 | test_environ['PASTE_TESTING'] = 'true' 18 | 19 | testenv = TestFileEnvironment( 20 | os.path.join(os.path.dirname(__file__), 'output'), 21 | template_path=template_path, 22 | environ=test_environ) 23 | 24 | def svn_repos_setup(): 25 | res = testenv.run('svnadmin', 'create', 'REPOS', 26 | printresult=False) 27 | testenv.svn_url = 'file://' + testenv.base_path + '/REPOS' 28 | assert 'REPOS' in res.files_created 29 | testenv.ignore_paths.append('REPOS') 30 | 31 | def paster_create(): 32 | global projenv 33 | res = testenv.run('paster', 'create', '--verbose', 34 | '--svn-repository=' + testenv.svn_url, 35 | '--template=paste_deploy', 36 | '--template=webkit', 37 | '--template=zpt', 38 | '--no-interactive', 39 | 'ProjectName', 40 | 'version=0.1', 41 | 'author=Test Author', 42 | 'author_email=test@example.com') 43 | expect_fn = ['tests', 'docs', 'projectname', 'docs', 44 | 'setup.py', 'ProjectName.egg-info', 45 | ] 46 | for fn in expect_fn: 47 | fn = os.path.join('ProjectName', fn) 48 | assert fn in res.files_created 49 | assert fn in res.stdout 50 | setup = res.files_created['ProjectName/setup.py'] 51 | setup.mustcontain('test@example.com') 52 | setup.mustcontain('Test Author') 53 | setup.mustcontain('0.1') 54 | setup.mustcontain('projectname.wsgiapp:make_app') 55 | # ZPTKit should add this: 56 | setup.mustcontain("include_package_data") 57 | assert '0.1' in setup 58 | sitepage = res.files_created['ProjectName/projectname/sitepage.py'] 59 | proj_dir = os.path.join(testenv.cwd, 'ProjectName') 60 | testenv.run('svn commit -m "new project"', 61 | cwd=proj_dir) 62 | testenv.run('python setup.py egg_info', 63 | cwd=proj_dir, 64 | expect_stderr=True) 65 | testenv.run('svn', 'commit', '-m', 'Created project', 'ProjectName') 66 | # A new environment with a new 67 | projenv = TestFileEnvironment( 68 | os.path.join(testenv.base_path, 'ProjectName'), 69 | start_clear=False, 70 | template_path=template_path, 71 | environ=test_environ) 72 | projenv.environ['PYTHONPATH'] = ( 73 | projenv.environ.get('PYTHONPATH', '') + ':' 74 | + projenv.base_path) 75 | projenv.proj_dir = proj_dir 76 | 77 | def make_servlet(): 78 | res = projenv.run( 79 | 'paster servlet --verbose --simulate test1', 80 | cwd=projenv.proj_dir) 81 | assert not res.files_created and not res.files_updated 82 | res = projenv.run('paster servlet -vvv test1') 83 | assert 'projectname/web/test1.py' in res.files_created 84 | assert 'projectname/templates/test1.pt' in res.files_created 85 | res = projenv.run('paster servlet -vvv ack.test2') 86 | assert 'projectname/web/ack/test2.py' in res.files_created 87 | assert 'projectname/templates/ack/test2.pt' in res.files_created 88 | res = projenv.run('paster servlet --no-servlet -vvv test3') 89 | assert 'projectname/web/test3.py' not in res.files_created 90 | assert 'projectname/templates/test3.pt' in res.files_created 91 | res = projenv.run('svn status') 92 | # Make sure all files are added to the repository: 93 | assert '?' not in res.stdout 94 | 95 | def do_pytest(): 96 | res = projenv.run('py.test tests/', 97 | cwd=os.path.join(testenv.cwd, 'ProjectName'), 98 | expect_stderr=True) 99 | assert len(res.stderr.splitlines()) <= 1, ( 100 | "Too much info on stderr: %s" % res.stderr) 101 | 102 | def config_permissions(): 103 | projenv.writefile('ProjectName.egg-info/iscape.txt', 104 | frompath='iscape.txt') 105 | projenv.writefile('projectname/web/admin/index.py', 106 | frompath='admin_index.py') 107 | projenv.writefile('tests/test_forbidden.py', 108 | frompath='test_forbidden.py') 109 | res = projenv.run('py.test tests/test_forbidden.py', 110 | expect_stderr=True) 111 | assert len(res.stderr.splitlines()) <= 1, ( 112 | "Too much info on stderr: %s" % res.stderr) 113 | 114 | def make_tag(): 115 | global tagenv 116 | res = projenv.run('svn commit -m "updates"') 117 | res = projenv.run('python setup.py svntag --version=0.5') 118 | assert 'Tagging 0.5 version' in res.stdout 119 | assert 'Auto-update of version strings' in res.stdout 120 | res = testenv.run('svn co %s/ProjectName/tags/0.5 Proj-05' 121 | % testenv.svn_url) 122 | setup = res.files_created['Proj-05/setup.py'] 123 | setup.mustcontain('0.5') 124 | assert 'Proj-05/setup.cfg' not in res.files_created 125 | tagenv = TestFileEnvironment( 126 | os.path.join(testenv.base_path, 'Proj-05'), 127 | start_clear=False, 128 | template_path=template_path) 129 | 130 | def test_project(): 131 | global projenv 132 | projenv = None 133 | yield svn_repos_setup 134 | yield paster_create 135 | yield make_servlet 136 | yield do_pytest 137 | yield config_permissions 138 | yield make_tag 139 | 140 | -------------------------------------------------------------------------------- /tests/appsetup/testfiles/admin_index.py: -------------------------------------------------------------------------------- 1 | from projectname.sitepage import SitePage 2 | 3 | class index(SitePage): 4 | 5 | link_id = 'admin_index' 6 | 7 | -------------------------------------------------------------------------------- /tests/appsetup/testfiles/conftest.py: -------------------------------------------------------------------------------- 1 | # This disables py.test for this directory 2 | import py 3 | 4 | class DisableDirectory(py.test.collect.Directory): 5 | 6 | def buildname2items(self): 7 | return {} 8 | 9 | Directory = DisableDirectory 10 | -------------------------------------------------------------------------------- /tests/appsetup/testfiles/iscape.txt: -------------------------------------------------------------------------------- 1 | [tool:projectname] 2 | name = ProjectName 3 | id = projectname 4 | roles = editor 5 | home = / 6 | admin_index.href = admin/ 7 | admin_index.title = ProjectName Admin 8 | admin_index.roles = admin 9 | -------------------------------------------------------------------------------- /tests/appsetup/testfiles/test_forbidden.py: -------------------------------------------------------------------------------- 1 | from paste.fixture import TestApp 2 | 3 | def test_forbidden(): 4 | # Unfortunately, auth isn't enabled in the standard app 5 | # yet, so we ignore that 6 | return 7 | app = TestApp() # Of what? 8 | app.get('/') 9 | #app.get('/admin/', status=401) 10 | #app.get('/admin/', status=403, 11 | # extra_environ={'REMOTE_USER': 'bob'}) 12 | 13 | -------------------------------------------------------------------------------- /tests/fake_packages/FakePlugin.egg/FakePlugin.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: FakePlugin 3 | Version: 0.1 4 | Summary: UNKNOWN 5 | Home-page: UNKNOWN 6 | Author: UNKNOWN 7 | Author-email: UNKNOWN 8 | License: UNKNOWN 9 | Description: UNKNOWN 10 | Platform: UNKNOWN 11 | -------------------------------------------------------------------------------- /tests/fake_packages/FakePlugin.egg/FakePlugin.egg-info/entry_points.txt: -------------------------------------------------------------------------------- 1 | [paste.paster_command] 2 | testcom = fakeplugin.testcom:TestCommand 3 | -------------------------------------------------------------------------------- /tests/fake_packages/FakePlugin.egg/FakePlugin.egg-info/paster_plugins.txt: -------------------------------------------------------------------------------- 1 | PasteScript 2 | 3 | -------------------------------------------------------------------------------- /tests/fake_packages/FakePlugin.egg/FakePlugin.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | fakeplugin 2 | -------------------------------------------------------------------------------- /tests/fake_packages/FakePlugin.egg/fakeplugin/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /tests/fake_packages/FakePlugin.egg/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='FakePlugin', 5 | version='0.1', 6 | packages=find_packages()) 7 | -------------------------------------------------------------------------------- /tests/sample_templates/test1.txt: -------------------------------------------------------------------------------- 1 | #def body(c, d): 2 | This shouldn't work, since it doesn't end with _tmpl 3 | #end def 4 | -------------------------------------------------------------------------------- /tests/sample_templates/test2.py_tmpl: -------------------------------------------------------------------------------- 1 | #def body(a) 2 | This is a real template 3 | with a $a variable 4 | #end def 5 | 6 | #def inner(foo) 7 | This variable shouldn't be read 8 | #end def 9 | -------------------------------------------------------------------------------- /tests/sample_templates/test3.ini_tmpl: -------------------------------------------------------------------------------- 1 | #def body(b=1) 2 | This method has a default 3 | #end def 4 | -------------------------------------------------------------------------------- /tests/sample_templates/test4.html_tmpl: -------------------------------------------------------------------------------- 1 | 2 | $title 3 | 4 | -------------------------------------------------------------------------------- /tests/test_command.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from paste.script import command 3 | from paste.script import create_distro 4 | from paste.script import entrypoints 5 | import contextlib 6 | import io 7 | import os 8 | import re 9 | import shutil 10 | import sys 11 | import tempfile 12 | import textwrap 13 | import unittest 14 | 15 | 16 | @contextlib.contextmanager 17 | def capture_stdout(): 18 | stdout = sys.stdout 19 | try: 20 | sys.stdout = io.StringIO() 21 | yield sys.stdout 22 | finally: 23 | sys.stdout = stdout 24 | 25 | 26 | @contextlib.contextmanager 27 | def temporary_dir(): 28 | old_dir = os.getcwd() 29 | try: 30 | if hasattr(tempfile, 'TemporaryDirectory'): 31 | # Python 3 32 | with tempfile.TemporaryDirectory() as tmpdir: 33 | os.chdir(tmpdir) 34 | yield 35 | else: 36 | # Python 2 37 | tmpdir = tempfile.mkdtemp() 38 | try: 39 | os.chdir(tmpdir) 40 | yield 41 | finally: 42 | shutil.rmtree(tmpdir) 43 | finally: 44 | os.chdir(old_dir) 45 | 46 | 47 | class CommandTest(unittest.TestCase): 48 | maxDiff = 1024 49 | 50 | def test_help(self): 51 | usage = textwrap.dedent(''' 52 | Usage: [paster_options] COMMAND [command_options] 53 | 54 | Options: 55 | --version show program's version number and exit 56 | --plugin=PLUGINS Add a plugin to the list of commands (plugins are Egg 57 | specs; will also require() the Egg) 58 | -h, --help Show this help message 59 | 60 | Commands: 61 | create Create the file layout for a Python distribution 62 | grep Search project for symbol 63 | help Display help 64 | make-config Install a package and create a fresh config file/directory 65 | points Show information about entry points 66 | post Run a request for the described application 67 | request Run a request for the described application 68 | serve Serve the described application 69 | setup-app Setup an application, given a config file 70 | ''').strip() + "\n\n" 71 | 72 | with capture_stdout() as stdout: 73 | argv = sys.argv 74 | sys.argv = ['', '--help'] 75 | try: 76 | try: 77 | command.run(['--help']) 78 | except SystemExit as exc: 79 | self.assertEqual(exc.code, 0) 80 | else: 81 | self.fail("SystemExit not raised") 82 | finally: 83 | sys.argv = argv 84 | 85 | 86 | class CreateDistroCommandTest(unittest.TestCase): 87 | maxDiff = 1024 88 | 89 | def setUp(self): 90 | self.cmd = create_distro.CreateDistroCommand('create_distro') 91 | 92 | def test_list_templates(self): 93 | templates = textwrap.dedent(''' 94 | Available templates: 95 | basic_package: A basic setuptools-enabled package 96 | paste_deploy: A web application deployed through paste.deploy 97 | ''').strip() + "\n" 98 | with capture_stdout() as stdout: 99 | self.cmd.run(['--list-templates']) 100 | self.assertEqual(templates, stdout.getvalue()) 101 | 102 | def test_basic_package(self): 103 | inputs = [ 104 | '1.0', # Version 105 | 'description', # Description 106 | 'long description', # Long description 107 | 'keyword1 keyword2', # Keywords 108 | 'author name', # Author name 109 | 'author@domain.com', # Author email 110 | 'http://example.com', # URL of homepage 111 | 'license', # License 112 | 'True', # zip_safe 113 | ] 114 | name = 'test' 115 | 116 | setup_cfg = textwrap.dedent(''' 117 | [egg_info] 118 | tag_build = dev 119 | tag_svn_revision = true 120 | ''').strip() + '\n' 121 | 122 | setup_py = textwrap.dedent(r''' 123 | from setuptools import setup, find_packages 124 | import sys, os 125 | 126 | version = '1.0' 127 | 128 | setup(name='test', 129 | version=version, 130 | description="description", 131 | long_description="""\ 132 | long description""", 133 | classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers 134 | keywords='keyword1 keyword2', 135 | author='author name', 136 | author_email='author@domain.com', 137 | url='http://example.com', 138 | license='license', 139 | packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), 140 | include_package_data=True, 141 | zip_safe=True, 142 | install_requires=[ 143 | # -*- Extra requirements: -*- 144 | ], 145 | entry_points=""" 146 | # -*- Entry points: -*- 147 | """, 148 | ) 149 | ''').strip() + "\n" 150 | 151 | with temporary_dir(): 152 | stdin = sys.stdin 153 | try: 154 | sys.stdin = io.StringIO('\n'.join(inputs)) 155 | with capture_stdout(): 156 | self.cmd.run(['--template=basic_package', name]) 157 | finally: 158 | sys.stdin = stdin 159 | 160 | os.chdir(name) 161 | 162 | with open("setup.cfg") as f: 163 | self.assertEqual(setup_cfg, f.read()) 164 | 165 | with open("setup.py") as f: 166 | self.assertEqual(setup_py, f.read()) 167 | 168 | with open(os.path.join(name, "__init__.py")) as f: 169 | self.assertEqual("#\n", f.read()) 170 | 171 | 172 | class EntryPointsTest(unittest.TestCase): 173 | maxDiff = 4096 174 | 175 | def setUp(self): 176 | self.cmd = entrypoints.EntryPointCommand('entrypoint') 177 | 178 | def test_paster_command(self): 179 | # Issue #20: Check that SuperGeneric works on Python 3 180 | paster = textwrap.dedent(''' 181 | create = paste.script.create_distro:CreateDistroCommand 182 | (self, name) 183 | exe = paste.script.exe:ExeCommand 184 | (self, name) 185 | help = paste.script.help:HelpCommand 186 | (self, name) 187 | make-config = paste.script.appinstall:MakeConfigCommand 188 | (self, name) 189 | points = paste.script.entrypoints:EntryPointCommand 190 | (self, name) 191 | post = paste.script.request:RequestCommand 192 | (self, name) 193 | request = paste.script.request:RequestCommand 194 | (self, name) 195 | serve = paste.script.serve:ServeCommand 196 | (self, name) 197 | setup-app = paste.script.appinstall:SetupCommand 198 | (self, name) 199 | ''').strip() 200 | with capture_stdout() as stdout: 201 | res = self.cmd.run(['paster_command']) 202 | self.assertEqual(res, 0) 203 | out = stdout.getvalue() 204 | 205 | self.assertIn(paster, out) 206 | 207 | 208 | class PostTest(unittest.TestCase): 209 | maxDiff = 4096 210 | 211 | def test_post(self): 212 | config = os.path.join('docs', 'example_app.ini') 213 | url = '/' 214 | with capture_stdout() as stdout: 215 | stdout.buffer = io.BytesIO() 216 | try: 217 | command.run(['post', config, url]) 218 | except SystemExit as exc: 219 | self.assertEqual(exc.code, 0) 220 | else: 221 | self.fail("SystemExit not raised") 222 | out = stdout.buffer.getvalue() 223 | out = out.decode('utf-8') 224 | html_regex = textwrap.dedent(''' 225 | 226 | 227 | Test Application 228 | 229 | 230 | .* 231 | 232 | 233 | ''').strip() 234 | html_regex = '\n%s\n' % html_regex 235 | html_regex = re.compile(html_regex, re.DOTALL) 236 | self.assertRegex(out, html_regex) 237 | 238 | if __name__ == "__main__": 239 | unittest.main() 240 | -------------------------------------------------------------------------------- /tests/test_egg_finder.py: -------------------------------------------------------------------------------- 1 | import os 2 | from paste.script import pluginlib 3 | 4 | def test_egg_info(): 5 | egg_dir = os.path.join(os.path.dirname(__file__), 6 | 'fake_packages', 'FakePlugin.egg') 7 | found = pluginlib.find_egg_info_dir(os.path.join(egg_dir, 'fakeplugin')) 8 | assert found == os.path.join(egg_dir, 'FakePlugin.egg-info') 9 | found = pluginlib.find_egg_info_dir(os.path.dirname(__file__)) 10 | assert found == os.path.join( 11 | os.path.dirname(os.path.dirname(__file__)), 12 | 'PasteScript.egg-info') 13 | 14 | def test_resolve_plugins(): 15 | plugins = ['FakePlugin'] 16 | all = pluginlib.resolve_plugins(plugins) 17 | assert all 18 | assert len(all) == 2 19 | 20 | def test_find_commands(): 21 | all = pluginlib.resolve_plugins(['PasteScript', 'FakePlugin']) 22 | commands = pluginlib.load_commands_from_plugins(all) 23 | print(commands) 24 | assert 'testcom' in commands 25 | 26 | -------------------------------------------------------------------------------- /tests/test_logging_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2001-2004 by Vinay Sajip. All Rights Reserved. 4 | # 5 | # Permission to use, copy, modify, and distribute this software and its 6 | # documentation for any purpose and without fee is hereby granted, 7 | # provided that the above copyright notice appear in all copies and that 8 | # both that copyright notice and this permission notice appear in 9 | # supporting documentation, and that the name of Vinay Sajip 10 | # not be used in advertising or publicity pertaining to distribution 11 | # of the software without specific, written prior permission. 12 | # VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 13 | # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 14 | # VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 15 | # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 16 | # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 17 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | # 19 | # This file is part of the Python logging distribution. See 20 | # http://www.red-dove.com/python_logging.html 21 | # 22 | """Test harness for the logging module. Run all tests. 23 | 24 | Copyright (C) 2001-2002 Vinay Sajip. All Rights Reserved. 25 | """ 26 | 27 | import logging 28 | import os 29 | import sys 30 | import tempfile 31 | 32 | from paste.script.util import logging_config 33 | 34 | def message(s): 35 | sys.stdout.write("%s\n" % s) 36 | 37 | #---------------------------------------------------------------------------- 38 | # Test 4 39 | #---------------------------------------------------------------------------- 40 | 41 | # config0 is a standard configuration. 42 | config0 = """ 43 | [loggers] 44 | keys=root 45 | 46 | [handlers] 47 | keys=hand1 48 | 49 | [formatters] 50 | keys=form1 51 | 52 | [logger_root] 53 | level=NOTSET 54 | handlers=hand1 55 | 56 | [handler_hand1] 57 | class=StreamHandler 58 | level=NOTSET 59 | formatter=form1 60 | args=(sys.stdout,) 61 | 62 | [formatter_form1] 63 | format=%(levelname)s:%(name)s:%(message)s 64 | datefmt= 65 | """ 66 | 67 | # config1 adds a little to the standard configuration. 68 | config1 = """ 69 | [loggers] 70 | keys=root,parser 71 | 72 | [handlers] 73 | keys=hand1, hand2 74 | 75 | [formatters] 76 | keys=form1, form2 77 | 78 | [logger_root] 79 | level=NOTSET 80 | handlers=hand1,hand2 81 | 82 | [logger_parser] 83 | level=DEBUG 84 | handlers=hand1 85 | propagate=1 86 | qualname=compiler.parser 87 | 88 | [handler_hand1] 89 | class=StreamHandler 90 | level=NOTSET 91 | formatter=form1 92 | args=(sys.stdout,) 93 | 94 | [handler_hand2] 95 | class=StreamHandler 96 | level=NOTSET 97 | formatter=form2 98 | args=(sys.stderr,) 99 | 100 | [formatter_form1] 101 | format=%(levelname)s:%(name)s:%(message)s 102 | datefmt= 103 | 104 | [formatter_form2] 105 | format=:%(message)s 106 | datefmt= 107 | """ 108 | 109 | # config2 has a subtle configuration error that should be reported 110 | config2 = config1.replace("sys.stdout", "sys.stbout") 111 | 112 | # config3 has a less subtle configuration error 113 | config3 = config1.replace("formatter=form1", "formatter=misspelled_name") 114 | 115 | # config4: support custom Handler classes 116 | config4 = config1.replace("class=StreamHandler", "class=logging.StreamHandler") 117 | 118 | def test4(): 119 | for i in range(5): 120 | conf = globals()['config%d' % i] 121 | sys.stdout.write('config%d: ' % i) 122 | loggerDict = logging.getLogger().manager.loggerDict 123 | with logging._lock: 124 | saved_handlers = logging._handlers.copy() 125 | if hasattr(logging, '_handlerList'): 126 | saved_handler_list = logging._handlerList[:] 127 | saved_loggers = loggerDict.copy() 128 | try: 129 | fn = tempfile.mktemp(".ini") 130 | f = open(fn, "w") 131 | f.write(conf) 132 | f.close() 133 | try: 134 | logging_config.fileConfig(fn) 135 | #call again to make sure cleanup is correct 136 | logging_config.fileConfig(fn) 137 | except: 138 | if i not in (2, 3): 139 | raise 140 | t = sys.exc_info()[0] 141 | message(str(t) + ' (expected)') 142 | else: 143 | message('ok.') 144 | os.remove(fn) 145 | finally: 146 | with logging._lock: 147 | logging._handlers.clear() 148 | logging._handlers.update(saved_handlers) 149 | if hasattr(logging, '_handlerList'): 150 | logging._handlerList[:] = saved_handler_list 151 | loggerDict = logging.getLogger().manager.loggerDict 152 | loggerDict.clear() 153 | loggerDict.update(saved_loggers) 154 | 155 | #---------------------------------------------------------------------------- 156 | # Test 5 157 | #---------------------------------------------------------------------------- 158 | 159 | test5_config = """ 160 | [loggers] 161 | keys=root 162 | 163 | [handlers] 164 | keys=hand1 165 | 166 | [formatters] 167 | keys=form1 168 | 169 | [logger_root] 170 | level=NOTSET 171 | handlers=hand1 172 | 173 | [handler_hand1] 174 | class=StreamHandler 175 | level=NOTSET 176 | formatter=form1 177 | args=(sys.stdout,) 178 | 179 | [formatter_form1] 180 | #class=test.test_logging.FriendlyFormatter 181 | class=test_logging_config.FriendlyFormatter 182 | format=%(levelname)s:%(name)s:%(message)s 183 | datefmt= 184 | """ 185 | 186 | class FriendlyFormatter (logging.Formatter): 187 | def formatException(self, ei): 188 | return "%s... Don't panic!" % str(ei[0]) 189 | 190 | 191 | def test5(): 192 | loggerDict = logging.getLogger().manager.loggerDict 193 | with logging._lock: 194 | saved_handlers = logging._handlers.copy() 195 | if hasattr(logging, '_handlerList'): 196 | saved_handler_list = logging._handlerList[:] 197 | saved_loggers = loggerDict.copy() 198 | try: 199 | fn = tempfile.mktemp(".ini") 200 | f = open(fn, "w") 201 | f.write(test5_config) 202 | f.close() 203 | logging_config.fileConfig(fn) 204 | try: 205 | raise KeyError 206 | except KeyError: 207 | logging.exception("just testing") 208 | os.remove(fn) 209 | hdlr = logging.getLogger().handlers[0] 210 | logging.getLogger().handlers.remove(hdlr) 211 | finally: 212 | with logging._lock: 213 | logging._handlers.clear() 214 | logging._handlers.update(saved_handlers) 215 | if hasattr(logging, '_handlerList'): 216 | logging._handlerList[:] = saved_handler_list 217 | loggerDict = logging.getLogger().manager.loggerDict 218 | loggerDict.clear() 219 | loggerDict.update(saved_loggers) 220 | -------------------------------------------------------------------------------- /tests/test_plugin_adder.py: -------------------------------------------------------------------------------- 1 | import os 2 | from paste.script import pluginlib 3 | 4 | egg_dir = os.path.join(os.path.dirname(__file__), 5 | 'fake_packages', 'FakePlugin.egg') 6 | 7 | plugin_file = os.path.join(egg_dir, 'paster_plugins.txt') 8 | 9 | def plugin_lines(): 10 | if not os.path.exists(plugin_file): 11 | return [] 12 | f = open(plugin_file) 13 | lines = f.readlines() 14 | f.close() 15 | return [l.strip() for l in lines if l.strip()] 16 | 17 | def test_add_remove(): 18 | prev = plugin_lines() 19 | pluginlib.add_plugin(egg_dir, 'Test') 20 | assert 'Test' in plugin_lines() 21 | pluginlib.remove_plugin(egg_dir, 'Test') 22 | assert 'Test' not in plugin_lines() 23 | assert prev == plugin_lines() 24 | if not prev and os.path.exists(plugin_file): 25 | os.unlink(plugin_file) 26 | -------------------------------------------------------------------------------- /tests/test_template_introspect.py: -------------------------------------------------------------------------------- 1 | from unittest import SkipTest 2 | import os 3 | from paste.script import templates 4 | 5 | tmpl_dir = os.path.join(os.path.dirname(__file__), 'sample_templates') 6 | 7 | def test_find(): 8 | try: 9 | import Cheetah.Template 10 | except ImportError: 11 | raise SkipTest("need Cheetah.Template") 12 | 13 | vars = templates.find_args_in_dir(tmpl_dir, True) 14 | assert 'a' in vars 15 | assert vars['a'].default is templates.NoDefault 16 | assert 'b' in vars 17 | assert vars['b'].default == 1 18 | assert len(vars) == 2 19 | -------------------------------------------------------------------------------- /tests/test_util.py: -------------------------------------------------------------------------------- 1 | from paste.script.util import secret 2 | 3 | def test_random(): 4 | for length in (1, 10, 100): 5 | data = secret.random_bytes(length) 6 | assert isinstance(data, bytes) 7 | assert len(data) == length 8 | 9 | def test_secret_string(): 10 | data = secret.secret_string() 11 | assert isinstance(data, str) 12 | 13 | data = secret.secret_string(20) 14 | assert isinstance(data, str) 15 | assert len(data) == 20 16 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = py{38,39,310,311,312,313}, pypy, pypy3 8 | 9 | [testenv] 10 | deps = 11 | Paste>=1.3 12 | PasteDeploy 13 | commands = 14 | python -m unittest discover tests {posargs:-v} 15 | --------------------------------------------------------------------------------