diff options
| author | Paul Everitt <paul@agendaless.com> | 2013-08-11 09:53:00 -0400 |
|---|---|---|
| committer | Paul Everitt <paul@agendaless.com> | 2013-08-11 09:53:00 -0400 |
| commit | ae901436a61563968cc31cc636f1fbeb5e85b528 (patch) | |
| tree | 8ce7a18dd4913f7195c5a013ab03eb64ae21180d /docs/getting_started/quick_glance.rst | |
| parent | d4bd291fd5ca51bbbeec487f2011476706a5952f (diff) | |
| download | pyramid-ae901436a61563968cc31cc636f1fbeb5e85b528.tar.gz pyramid-ae901436a61563968cc31cc636f1fbeb5e85b528.tar.bz2 pyramid-ae901436a61563968cc31cc636f1fbeb5e85b528.zip | |
Per discussion with Chris, just wrap up "getting started" as the Quick Tour. Still need to do more linking and perhaps add a section on root factories, authorization, authentication.
Diffstat (limited to 'docs/getting_started/quick_glance.rst')
| -rw-r--r-- | docs/getting_started/quick_glance.rst | 806 |
1 files changed, 0 insertions, 806 deletions
diff --git a/docs/getting_started/quick_glance.rst b/docs/getting_started/quick_glance.rst deleted file mode 100644 index f9a60c0eb..000000000 --- a/docs/getting_started/quick_glance.rst +++ /dev/null @@ -1,806 +0,0 @@ -============ -Quick Glance -============ - -Pyramid lets you start small and finish big. This :doc:`index` guide -walks you through many of Pyramid's key features. Let's put the -emphasis on *start* by doing a quick tour through Pyramid, with -snippets of code to illustrate major concepts. - -.. note:: - - Like the rest of Getting Started, we're using Python 3 in - our samples. Pyramid was one of the first (October 2011) web - frameworks to fully support Python 3. You can use Python 3 - as well for this guide, but you can also use Python 2.7. - -Python Setup -============ - -First things first: we need our Python environment in ship-shape. -Pyramid encourages standard Python development practices (virtual -environments, packaging tools, logging, etc.) so let's get our working -area in place. For Python 3.3: - -.. code-block:: bash - - $ pyvenv-3.3 env33 - $ source env33/bin/activate - $ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | python - $ easy_install-3.3 pip - -We make a :term:`virtualenv` then activate it. We then get Python -packaging tools installed so we can use the popular ``pip`` tool for -installing packages. Normal first steps for any Python project. - -Pyramid Installation -==================== - -We now have a standard starting point for Python. Getting Pyramid -installed is easy: - -.. code-block:: bash - - $ pip install pyramid - -Our virtual environment now has the Pyramid software available to its -Python. - -Hello World -=========== - -Microframeworks have shown that learning starts best from a very small -first step. Here's a tiny application in Pyramid: - -.. literalinclude:: quick_glance/hello_world/app.py - :linenos: - -This simple example is easy to run. Save this as ``app.py`` and run it: - -.. code-block:: bash - - $ python ./app.py - -Next, open `http://localhost:6543/ <http://localhost:6543/>`_ in a -browser and you will see the ``Hello World!`` message. - -New to Python web programming? If so, some lines in module merit -explanation: - -#. *Line 10*. The ``if __name__ == '__main__':`` is Python's way of - saying "Start here when running from the command line". - -#. *Lines 11-13*. Use Pyramid's :term:`configurator` to connect - :term:`view` code to particular URL :term:`route`. - -#. *Lines 6-7*. Implement the view code that generates the - :term:`response`. - -#. *Lines 14-16*. Publish a :term:`WSGI` app using an HTTP server. - -As shown in this example, the :term:`configurator` plays a central role -in Pyramid development. Building an application from loosely-coupled -parts via :doc:`../narr/configuration` is a central idea in Pyramid, -one that we will revisit regurlarly in this *Getting Started* document. - -Handling Web Requests and Responses -=================================== - -Developing for the web means processing web requests. As this is a -critical part of a web application, web developers need a robust, -mature set of software for web requests. - -Pyramid has always fit nicely into the existing world of Python web -development (virtual environments, packaging, scaffolding, -first to embrace Python 3, etc.) For request handling, Pyramid turned -to the well-regarded :term:`WebOb` Python library for request and -response handling. In our example -above, Pyramid hands ``hello_world`` a ``request`` that is -:ref:`based on WebOb <webob_chapter>`. - -Let's see some features of requests and responses in action: - -.. literalinclude:: quick_glance/requests/app.py - :pyobject: hello_world - -In this Pyramid view, we get the URL being visited from ``request.url``. -Also, if you visited ``http://localhost:6543/?name=alice``, -the name is included in the body of the response:: - - URL http://localhost:6543/?name=alice with name: alice - -Finally, we set the response's content type and return the Response. - -Views -===== - -For the examples above, the ``hello_world`` function is a "view". In -Pyramid, views are the primary way to accept web requests and return -responses. - -So far our examples place everything in one file: - -- the view function - -- its registration with the configurator - -- the route to map it to a URL - -- the WSGI application launcher - -Let's move the views out to their own ``views.py`` module and change -the ``app.py`` to scan that module, looking for decorators that setup -the views. First, our revised ``app.py``: - -.. literalinclude:: quick_glance/views/app.py - :linenos: - -We added some more routes, but we also removed the view code. -Our views, and their registrations (via decorators) are now in a module -``views.py`` which is scanned via ``config.scan('views')``. - -We now have a ``views.py`` module that is focused on handling requests -and responses: - -.. literalinclude:: quick_glance/views/views.py - :linenos: - -We have 4 views, each leading to the other. If you start at -``http://localhost:6543/``, you get a response with a link to the next -view. The ``hello_view`` (available at the URL ``/howdy``) has a link -to the ``redirect_view``, which shows issuing a redirect to the final -view. - -Earlier we saw ``config.add_view`` as one way to configure a view. This -section introduces ``@view_config``. Pyramid's configuration supports -:term:`imperative configuration`, such as the ``config.add_view`` in -the previous example. You can also use :term:`declarative -configuration`, in which a Python :term:`decorator` is placed on the -line above the view. Both approaches result in the same final -configuration, thus usually, it is simply a matter of taste. - -.. note:: - - We're focusing on one topic at a time, thus we are leaving out - handling errors, logging, restarting, etc. These will be covered in - sections of the *Getting Started* guide. - - -Routing -======= - -Writing web applications usually means sophisticated URL design. We -just saw some Pyramid machinery for requests and views. Let's look at -features that help in routing. - -Above we saw the basics of routing URLs to views in Pyramid: - -- Your project's "setup" code registers a route name to be used when - matching part of the URL - -- Elsewhere, a view is configured to be called for that route name - -.. note:: - - Why do this twice? Other systems don't make us repeat this! As - illustrated in :ref:`routes_need_ordering`, multiple routes might - match the same URL pattern. Rather than provide ways to help guess, - Pyramid lets you be explicit in ordering. Pyramid also gives - facilities to avoid the problem. - -What if we want part of the URL to be available as data in my view? This -route declaration: - -.. literalinclude:: quick_glance/routing/app.py - :start-after: Start Route 1 - :end-before: End Route 1 - -With this, URLs such as ``/howdy/amy/smith`` will assign ``amy`` to -``first`` and ``smith`` to ``last``. We can then use this data in our -view: - -.. literalinclude:: quick_glance/routing/views.py - :start-after: Start Route 1 - :end-before: End Route 1 - -``request.matchdict`` contains values from the URL that match the -"replacement patterns" (the curly braces) in the route declaration. -This information can then be used in your view. - -As you might imagine, Pyramid provides -:doc:`many more features in routing <../narr/urldispatch>` -(route factories, custom predicates, security statements, -etc.) We will see more features later in *Geting Started*. - -Templating -========== - -Ouch. We have been making our own ``Response`` and filling the response -body with HTML. You usually won't embed an HTML string directly in -Python, but instead, will use a templating language. - -Pyramid doesn't mandate a particular database system, form library, -etc. It encourages replaceability. This applies equally to templating, -which is fortunate: developers have strong views about template -languages. That said, Pyramid bundles Chameleon and Mako, -so in this step, let's use Chameleon as an example: - -.. literalinclude:: quick_glance/templating/views.py - :start-after: Start View 1 - :end-before: End View 1 - -Ahh, that looks better. We have a view that is focused on Python code. -Our ``@view_config`` decorator specifies a :term:`renderer` that points -our template file. Our view then simply returns data which is then -supplied to our template: - -.. literalinclude:: quick_glance/templating/hello_world.pt - :language: html - -Since our view returned ``dict(name=request.matchdict['name'])``, -we can use ``name`` as a variable in our template via -``${name}``. - -Templating With ``jinja2`` -========================== - -We just said Pyramid doesn't prefer one templating language over -another. Time to prove it. Jinja2 is a popular templating system, -modelled after Django's templates. Let's add ``pyramid_jinja2``, -a Pyramid :term:`add-on` which enables Jinja2 as a :term:`renderer` in -our Pyramid applications: - -.. code-block:: bash - - $ pip install pyramid_jinja2 - -With the package installed, we can include the template bindings into -our configuration: - -.. code-block:: python - - config.include('pyramid_jinja2') - -The only change in our view...point the renderer at the ``.jinja2`` file: - -.. literalinclude:: quick_glance/jinja2/views.py - :start-after: Start View 1 - :end-before: End View 1 - -Our Jinja2 template is very similar to our previous template: - -.. literalinclude:: quick_glance/jinja2/hello_world.jinja2 - :language: jinja - -Pyramid's templating add-ons register a new kind of renderer into your -application. The renderer registration maps to different kinds of -filename extensions. In this case, changing the extension from ``.pt`` -to ``.jinja2`` passed the view response through the ``pyramid_jinja2`` -renderer. - -.. note:: - - At the time of this writing, Jinja2 had not released a version - compatible with Python 3.3. - - -Static Assets -============= - -Of course the Web is more than just markup. You need static assets: -CSS, JS, and images. Let's point our web app at a directory where -Pyramid will serve some static assets. First, another call to the -:term:`configurator`: - -.. literalinclude:: quick_glance/static_assets/app.py - :start-after: Start Static 1 - :end-before: End Static 1 - -This tells our WSGI application to map requests under -``http://localhost:6543/static/`` to files and directories inside a -``static`` directory alongside our Python module. - -Next, make a directory ``static`` and place ``app.css`` inside: - -.. literalinclude:: quick_glance/static_assets/static/app.css - :language: css - -All we need to do now is point to it in the ``<head>`` of our Jinja2 -template: - -.. literalinclude:: quick_glance/static_assets/hello_world.pt - :language: html - :start-after: Start Link 1 - :end-before: End Link 1 - -This link presumes that our CSS is at a URL starting with ``/static/``. -What if the site is later moved under ``/somesite/static/``? Or perhaps -web developer changes the arrangement on disk? Pyramid gives a helper -that provides flexibility on URL generation: - -.. literalinclude:: quick_glance/static_assets/hello_world.pt - :language: html - :start-after: Start Link 2 - :end-before: End Link 2 - -By using ``request.static_url`` to generate the full URL to the static -assets, you both ensure you stay in sync with the configuration and -gain refactoring flexibility later. - -Returning JSON -============== - -Modern web apps are more than rendered HTML. Dynamic pages now use -JavaScript to update the UI in the browser by requesting server data as -JSON. Pyramid supports this with a JSON renderer: - -.. literalinclude:: quick_glance/json/views.py - :start-after: Start View 1 - :end-before: End View 2 - -This wires up a view that returns some data through the JSON -:term:`renderer`, which calls Python's JSON support to serialize the data -into JSON and set the appropriate HTTP headers. - -View Classes -============ - -So far our views have been simple, free-standing functions. Many times -your views are related: different ways to look at or work on the same -data or a REST API that handles multiple operations. Grouping these -together as a -:ref:`view class <class_as_view>` makes sense: - -- Group views - -- Centralize some repetitive defaults - -- Share some state and helpers - -The following shows a "Hello World" example with three operations: view -a form, save a change, or press the delete button: - -.. literalinclude:: quick_glance/view_classes/views.py - :start-after: Start View 1 - :end-before: End View 1 - -As you can see, the three views are logically grouped together. -Specifically: - -- The first view is returned when you go to ``/howdy/amy``. This URL is - mapped to the ``hello`` route that we centrally set using the optional - ``@view_defaults``. - -- The second view is returned when the form data contains a field with - ``form.edit``, such as clicking on - ``<input type="submit" name="form.edit" value="Save"/>``. This rule - is specified in the ``@view_config`` for that view. - -- The third view is returned when clicking on a button such - as ``<input type="submit" name="form.delete" value="Delete"/>``. - -Only one route needed, stated in one place atop the view class. Also, -the assignment of the ``name`` is done in the ``__init__``. Our -templates can then use ``{{ view.name }}``. - -Quick Project Startup with Scaffolds -==================================== - -So far we have done all of our *Quick Glance* as a single Python file. -No Python packages, no structure. Most Pyramid projects, though, -aren't developed this way. - -To ease the process of getting started, Pyramid provides *scaffolds* -that generate sample projects from templates in Pyramid and Pyramid -add-ons. Pyramid's ``pcreate`` command can list the available scaffolds: - -.. code-block:: bash - - $ pcreate --list - Available scaffolds: - alchemy: Pyramid SQLAlchemy project using url dispatch - pyramid_jinja2_starter: pyramid jinja2 starter project - starter: Pyramid starter project - zodb: Pyramid ZODB project using traversal - -The ``pyramid_jinja2`` add-on gave us a scaffold that we can use. From -the parent directory of where we want our Python package to be generated, -let's use that scaffold to make our project: - -.. code-block:: bash - - $ pcreate --scaffold pyramid_jinja2_starter hello_world - -We next use the normal Python development to setup our package for -development: - -.. code-block:: bash - - $ cd hello_world - $ python ./setup.py develop - -We are moving in the direction of a full-featured Pyramid project, -with a proper setup for Python standards (packaging) and Pyramid -configuration. This includes a new way of running your application: - -.. code-block:: bash - - $ pserve development.ini - -Let's look at ``pserve`` and configuration in more depth. - -Application Running with ``pserve`` -=================================== - -Prior to scaffolds, our project mixed a number of operations details -into our code. Why should my main code care with HTTP server I want and -what port number to run on? - -``pserve`` is Pyramid's application runner, separating operational -details from your code. When you install Pyramid, a small command -program called ``pserve`` is written to your ``bin`` directory. This -program is an executable Python module. It's very small, getting most -of its brains via import. - -You can run ``pserve`` with ``--help`` to see some of its options. -Doing so reveals that you can ask ``pserve`` to watch your development -files and reload the server when they change: - -.. code-block:: bash - - $ pserve development.ini --reload - -The ``pserve`` command has a number of other options and operations. -Most of the work, though, comes from your project's wiring, as -expressed in the configuration file you supply to ``pserve``. Let's -take a look at this configuration file. - -Configuration with ``.ini`` Files -================================= - -Earlier in *Quick Glance* we first met Pyramid's configuration system. -At that point we did all configuration in Python code. For example, -the port number chosen for our HTTP server was right there in Python -code. Our scaffold has moved this decision, and more, into the -``development.ini`` file: - -.. literalinclude:: quick_glance/package/development.ini - :language: ini - -Let's take a quick high-level look. First, the ``.ini`` file is divided -into sections: - -- ``[app:hello_world]`` configures our WSGI app - -- ``[pipeline:main]`` sets up our WSGI "pipeline" - -- ``[server:main]`` holds our WSGI server settings - -- Various sections afterwards configure our Python logging system - -We have a few decisions made for us in this configuration: - -#. *Choice of web server*. The ``use = egg:pyramid#wsgiref`` tell - ``pserve`` to the ``wsgiref`` server that is wrapped in the Pyramid - package. - -#. *Port number*. ``port = 6543`` tells ``wsgiref`` to listen on port - 6543. - -#. *WSGI app*. What package has our WSGI application in it? - ``use = egg:hello_world`` in the app section tells the - configuration what application to load. - -#. *Easier development by automatic template reloading*. In development - mode, you shouldn't have to restart the server when editing a Jinja2 - template. ``reload_templates = true`` sets this policy, - which might be different in production. - -Additionally, the ``development.ini`` generated by this scaffold wired -up Python's standard logging. We'll now see in the console, for example, -a log on every request that comes in, as well traceback information. - -Easier Development with ``debugtoolbar`` -======================================== - -As we introduce the basics we also want to show how to be productive in -development and debugging. For example, we just discussed template -reloading and earlier we showed ``--reload`` for application reloading. - -``pyramid_debugtoolbar`` is a popular Pyramid add-on which makes -several tools available in your browser. Adding it to your project -illustrates several points about configuration. - -First, change your ``setup.py`` to say: - -.. literalinclude:: quick_glance/package/setup.py - :start-after: Start Requires - :end-before: End Requires - -...and re-run your setup: - -.. code-block:: bash - - $ python ./setup.py develop - -The Python package was now installed into our environment. The package -is a Pyramid add-on, which means we need to include its configuration -into our web application. We could do this with imperative -configuration, as we did above for the ``pyramid_jinja2`` add-on: - -.. literalinclude:: quick_glance/package/hello_world/__init__.py - :start-after: Start Include - :end-before: End Include - -Now that we have a configuration file, we can use the -``pyramid.includes`` facility and place this in our -``development.ini`` instead: - -.. literalinclude:: quick_glance/package/development.ini - :language: ini - :start-after: Start Includes - :end-before: End Includes - -You'll now see an attractive (and -collapsible) menu in the right of your browser, providing introspective -access to debugging information. Even better, if your web application -generates an error, you will see a nice traceback on the screen. When -you want to disable this toolbar, no need to change code: you can -remove it from ``pyramid.includes`` in the relevant ``.ini`` -configuration file. - -Unit Tests and ``nose`` -======================= - -Yikes! We got this far and we haven't yet discussed tests. Particularly -egregious, as Pyramid has had a deep commitment to full test coverage -since before it was released. - -Our ``pyramid_jinja2_starter`` scaffold generated a ``tests.py`` module -with one unit test in it. To run it, let's install the handy ``nose`` -test runner by editing ``setup.py``. While we're at it, we'll throw in -the ``coverage`` tool which yells at us for code that isn't tested: - -.. code-block:: python - - setup(name='hello_world', - # Some lines removed... - extras_require={ - 'testing': ['nose', 'coverage'], - } - ) - -We changed ``setup.py`` which means we need to re-run -``python ./setup.py develop``. We can now run all our tests: - -.. code-block:: bash - - $ cd hello_world - $ nosetests . - . - Name Stmts Miss Cover Missing - --------------------------------------------------- - hello_world 12 8 33% 11-23 - hello_world.models 5 1 80% 8 - hello_world.tests 14 0 100% - hello_world.views 4 0 100% - --------------------------------------------------- - TOTAL 35 9 74% - ---------------------------------------------------------------------- - Ran 1 test in 0.931s - - OK - -Our unit test passed. What did our test look like? - -.. literalinclude:: quick_glance/package/hello_world/tests.py - -Pyramid supplies helpers for test writing, which we use in the -test setup and teardown. Our one test imports the view, -makes a dummy request, and sees if the view returns what we expected. - -Logging -======= - -It's important to know what is going on inside our web application. -In development we might need to collect some output. In production, -we might need to detect situations when other people use the site. We -need *logging*. - -Fortunately Pyramid uses the normal Python approach to logging. The -scaffold generated, in your ``development.ini``, a number of lines that -configure the logging for you to some reasonable defaults. You then see -messages sent by Pyramid (for example, when a new request comes in.) - -Maybe you would like to log messages in your code? In your Python -module, import and setup the logging: - -.. literalinclude:: quick_glance/package/hello_world/views.py - :start-after: Start Logging 1 - :end-before: End Logging 1 - -You can now, in your code, log messages: - -.. literalinclude:: quick_glance/package/hello_world/views.py - :start-after: Start Logging 2 - :end-before: End Logging 2 - -This will log ``Some Message`` at a ``debug`` log level, -to the application-configured logger in your ``development.ini``. What -controls that? These sections in the configuration file: - -.. literalinclude:: quick_glance/package/development.ini - :language: ini - :start-after: Start Sphinx Include - :end-before: End Sphinx Include - -Our application, a package named ``hello_world``, is setup as a logger -and configured to log messages at a ``DEBUG`` or higher level. When you -visit ``http://localhost:6543`` your console will now show:: - - 2013-08-09 10:42:42,968 DEBUG [hello_world.views][MainThread] Some Message - -Sessions -======== - -When people use your web application, they frequently perform a task -that requires semi-permanent data to be saved. For example, a shopping -cart. This is called a :term:`session`. - -Pyramid has basic built-in support for sessions, with add-ons such as -*Beaker* (or your own custom sessioning engine) that provide richer -session support. Let's take a look at the -:doc:`built-in sessioning support <../narr/sessions>`. In our -``__init__.py`` we first import the kind of sessioning we want: - -.. literalinclude:: quick_glance/package/hello_world/__init__.py - :start-after: Start Sphinx Include 1 - :end-before: End Sphinx Include 1 - -.. warning:: - - As noted in the session docs, this example implementation is - not intended for use in settings with security implications. - -Now make a "factory" and pass it to the :term:`configurator`'s -``session_factory`` argument: - -.. literalinclude:: quick_glance/package/hello_world/__init__.py - :start-after: Start Sphinx Include 2 - :end-before: End Sphinx Include 2 - -Pyramid's :term:`request` object now has a ``session`` attribute -that we can use in our view code: - -.. literalinclude:: quick_glance/package/hello_world/views.py - :start-after: Start Sphinx Include 1 - :end-before: End Sphinx Include 1 - -With this, each reload will increase the counter displayed in our -Jinja2 template: - -.. literalinclude:: quick_glance/package/hello_world/templates/mytemplate.jinja2 - :language: jinja - :start-after: Start Sphinx Include 1 - :end-before: End Sphinx Include 1 - - -Databases -========= - -Web applications mean data. Data means databases. Frequently SQL -databases. SQL Databases frequently mean an "ORM" -(object-relational mapper.) In Python, ORM usually leads to the -mega-quality *SQLAlchemy*, a Python package that greatly eases working -with databases. - -Pyramid and SQLAlchemy are great friends. That friendship includes a -scaffold! - -.. code-block:: bash - - $ pcreate --scaffold alchemy sqla_demo - $ cd sqla_demo - $ python setup.py develop - -We now have a working sample SQLAlchemy application with all -dependencies installed. The sample project provides a console script to -initialize a SQLite database with tables. Let's run it and then start -the application: - -.. code-block:: bash - - $ initialize_sqla_demo_db development.ini - $ pserve development.ini - -The ORM eases the mapping of database structures into a programming -language. SQLAlchemy uses "models" for this mapping. The scaffold -generated a sample model: - -.. literalinclude:: quick_glance/sqla_demo/sqla_demo/models.py - :start-after: Start Sphinx Include - :end-before: End Sphinx Include - -View code, which mediates the logic between web requests and the rest -of the system, can then easily get at the data thanks to SQLAlchemy: - -.. literalinclude:: quick_glance/sqla_demo/sqla_demo/views.py - :start-after: Start Sphinx Include - :end-before: End Sphinx Include - -Forms -===== - -Developers have lots of opinions about web forms, and thus there are many -form libraries for Python. Pyramid doesn't directly bundle a form -library, but *Deform* is a popular choice for forms, -along with its related *Colander* schema system. - -As an example, imagine we want a form that edits a wiki page. The form -should have two fields on it, one of them a required title and the -other a rich text editor for the body. With Deform we can express this -as a Colander schema: - -.. code-block:: python - - class WikiPage(colander.MappingSchema): - title = colander.SchemaNode(colander.String()) - body = colander.SchemaNode( - colander.String(), - widget=deform.widget.RichTextWidget() - ) - -With this in place, we can render the HTML for a form, -perhaps with form data from an existing page: - -.. code-block:: python - - form = self.wiki_form.render() - -We'd like to handle form submission, validation, and saving: - -.. code-block:: python - - # Get the form data that was posted - controls = self.request.POST.items() - try: - # Validate and either raise a validation error - # or return deserialized data from widgets - appstruct = wiki_form.validate(controls) - except deform.ValidationFailure as e: - # Bail out and render form with errors - return dict(title=title, page=page, form=e.render()) - - # Change the content and redirect to the view - page['title'] = appstruct['title'] - page['body'] = appstruct['body'] - -Deform and Colander provide a very flexible combination for forms, -widgets, schemas, and validation. Recent versions of Deform also -include a -`retail mode <http://docs.pylonsproject -.org/projects/deform/en/latest/retail.html>`_ -for gaining Deform -features on custom forms. - -Also, the ``deform_bootstrap`` Pyramid add-on restyles the stock Deform -widgets using attractive CSS from Bootstrap and more powerful widgets -from Chosen. - -Awesome Pyramid Features -======================== - -For the most part this *Quick Glance* has covered concepts that are -common in Python web frameworks. Pyramid has a unique niche, -though. It helps you start with a small project that grows into a -larger project. Let's look at some of the unique facilities that help. - - - - -Conclusion -========== - -This *Quick Glance* was a little about a lot. We introduced a long list -of concepts in Pyramid, many of which are expanded on more fully later -in *Getting Started* and certainly in the Pyramid developer docs.
\ No newline at end of file |
