From bb754144b1c0d3c75156385ea8c6e20ddda6d430 Mon Sep 17 00:00:00 2001 From: Patricio Paez Date: Mon, 12 Mar 2012 14:32:54 -0700 Subject: Added the Design chapter on both wiki tutorials --- docs/tutorials/wiki/design.rst | 134 ++++++++++++++++++++++++++++++++++++++++ docs/tutorials/wiki/index.rst | 1 + docs/tutorials/wiki2/design.rst | 132 +++++++++++++++++++++++++++++++++++++++ docs/tutorials/wiki2/index.rst | 1 + 4 files changed, 268 insertions(+) create mode 100644 docs/tutorials/wiki/design.rst create mode 100644 docs/tutorials/wiki2/design.rst diff --git a/docs/tutorials/wiki/design.rst b/docs/tutorials/wiki/design.rst new file mode 100644 index 000000000..ea7076f60 --- /dev/null +++ b/docs/tutorials/wiki/design.rst @@ -0,0 +1,134 @@ +========== +Design +========== + +Following is a quick overview of our wiki application, to help +us understand the changes that we will be doing next in our +default files generated by the paster scafffold. + +Overall +------- + +We choose to use ``reStructuredText`` markup in the wiki text. +Translation from reStructuredText to HTML is provided by the +widely used docutils Python module. We will add this module +in the dependency list on the project ``setup.py`` file. + +Models +------ + +The root resource, named *Wiki*, will be a mapping of wiki page +names to page resources. The page resources will be instances +of a *Page* class and they store the text content. + +URLs like ``/PageName`` will be traversed using Wiki[ +*PageName* ] => page, and the context that results is the page +resource of an existing page. + +To add a page to the wiki, a new instance of the page resource +is created and its name and reference are added to the Wiki +mapping. + +A page named *FrontPage* containing the text *This is the front +page*, will be created when the storage is initialized, and will +be used as the wiki home page. + +Views +----- + +There will be four views to handle the normal operations of +viewing, editing and adding wiki pages. Two additional views +will handle the login and logout tasks related to security. + +Security +-------- + +- USERS, a dictionary mapping users names to their + corresponding passwords. +- GROUPS, a dictionary mapping user names to a + list of groups they belong to. +- *groupfinder*, an *authorization callback* that looks up + USERS and GROUPS. It will be provided in a new + *security.py* file. +- An :term:`ACL` is attached to the root resource. Each + row below details an :term:`ACE`: + + +----------+----------------+----------------+ + | Action | Principal | Permission | + +==========+================+================+ + | Allow | Everyone | View | + +----------+----------------+----------------+ + | Allow | group:editors | Edit | + +----------+----------------+----------------+ + +- Permission declarations for the views. + + +Summary +------- + +The URL, context, actions, template and permission associated to each view are +listed in the following table: + ++----------------------+-------------+-----------------+-----------------------+------------+------------+ +| URL | View | Context | Action | Template | Permission | +| | | | | | | ++======================+=============+=================+=======================+============+============+ +| / | view_wiki | Wiki | Redirect to | | | +| | | | /FrontPage | | | ++----------------------+-------------+-----------------+-----------------------+------------+------------+ +| /PageName | view_page | Page | Display existing | view.pt | view | +| | [1]_ | | page [2]_ | | | +| | | | | | | +| | | | | | | +| | | | | | | ++----------------------+-------------+-----------------+-----------------------+------------+------------+ +| /PageName/edit_page | edit_page | Page | Display edit form | edit.pt | edit | +| | | | with existing | | | +| | | | content. | | | +| | | | | | | +| | | | If the form was | | | +| | | | submitted, redirect | | | +| | | | to /PageName | | | ++----------------------+-------------+-----------------+-----------------------+------------+------------+ +| /add_page/PageName | add_page | Wiki | Create the page | edit.pt | edit | +| | | | *PageName* in | | | +| | | | storage, display | | | +| | | | the edit form | | | +| | | | without content. | | | +| | | | | | | +| | | | If the form was | | | +| | | | submitted, | | | +| | | | redirect to | | | +| | | | /PageName | | | ++----------------------+-------------+-----------------+-----------------------+------------+------------+ +| /login | login | Wiki, | Display login form. | login.pt | | +| | | Forbidden [3]_ | | | | +| | | | If the form was | | | +| | | | submitted, | | | +| | | | authenticate. | | | +| | | | | | | +| | | | - If authentication | | | +| | | | successful, | | | +| | | | redirect to the | | | +| | | | page that we | | | +| | | | came from. | | | +| | | | | | | +| | | | - If authentication | | | +| | | | fails, display | | | +| | | | login form with | | | +| | | | "login failed" | | | +| | | | message. | | | +| | | | | | | ++----------------------+-------------+-----------------+-----------------------+------------+------------+ +| /logout | logout | Wiki | Redirect to | | | +| | | | /FrontPage | | | ++----------------------+-------------+-----------------+-----------------------+------------+------------+ + +.. [1] This is the default view for a Page context + when there is no view name. +.. [2] Pyramid will return a default 404 Not Found page + if the page *PageName* does not exist yet. +.. [3] pyramid.exceptions.Forbidden is reached when a + user tries to invoke a view that is + not authorized by the authorization policy. diff --git a/docs/tutorials/wiki/index.rst b/docs/tutorials/wiki/index.rst index 3edc6ba04..f07f45bd0 100644 --- a/docs/tutorials/wiki/index.rst +++ b/docs/tutorials/wiki/index.rst @@ -18,6 +18,7 @@ tutorial can be browsed at :maxdepth: 2 background + design installation basiclayout definingmodels diff --git a/docs/tutorials/wiki2/design.rst b/docs/tutorials/wiki2/design.rst new file mode 100644 index 000000000..ade40ab18 --- /dev/null +++ b/docs/tutorials/wiki2/design.rst @@ -0,0 +1,132 @@ +========== +Design +========== + +Following is a quick overview of our wiki application, to help +us understand the changes that we will be doing next in our +default files generated by the paster scafffold. + +Overall +------- + +We choose to use ``reStructuredText`` markup in the wiki text. +Translation from reStructuredText to HTML is provided by the +widely used docutils Python module. We will add this module +in the dependency list on the project ``setup.py`` file. + +Models +------ + +We define a single table named `tables`, whose elements will +store the wiki pages. There are two columns: `name` and +`data`. + +URLs like ``/PageName`` will try to find an element in +the table whose `name` corresponds. + +To add a page to the wiki, a new row is created and the text +is stored in `data`. + +A page named *FrontPage* containing the text *This is the front +page*, will be created when the storage is initialized, and will +be used as the wiki home page. + +Views +----- + +There will be four views to handle the normal operations of +viewing, editing and adding wiki pages. Two additional views +will handle the login and logout tasks related to security. + +Security +-------- + +- USERS, a dictionary mapping users names to their + corresponding passwords. +- GROUPS, a dictionary mapping user names to a + list of groups they belong to. +- *groupfinder*, an *authorization callback* that looks up + USERS and GROUPS. It will be provided in a new + *security.py* file. +- An :term:`ACL` is attached to the root resource. Each + row below details an :term:`ACE`: + + +----------+----------------+----------------+ + | Action | Principal | Permission | + +==========+================+================+ + | Allow | Everyone | View | + +----------+----------------+----------------+ + | Allow | group:editors | Edit | + +----------+----------------+----------------+ + +- Permission declarations for the views. + + +Summary +------- + +The URL, context, actions, template and permission associated to each view are +listed in the following table: + ++----------------------+-------------+-----------------+-----------------------+------------+------------+ +| URL | View | Context | Action | Template | Permission | +| | | | | | | ++======================+=============+=================+=======================+============+============+ +| / | view_wiki | Wiki | Redirect to | | | +| | | | /FrontPage | | | ++----------------------+-------------+-----------------+-----------------------+------------+------------+ +| /PageName | view_page | Page | Display existing | view.pt | view | +| | [1]_ | | page [2]_ | | | +| | | | | | | +| | | | | | | +| | | | | | | ++----------------------+-------------+-----------------+-----------------------+------------+------------+ +| /edit_page/PageName | edit_page | Page | Display edit form | edit.pt | edit | +| | | | with existing | | | +| | | | content. | | | +| | | | | | | +| | | | If the form was | | | +| | | | submitted, redirect | | | +| | | | to /PageName | | | ++----------------------+-------------+-----------------+-----------------------+------------+------------+ +| /add_page/PageName | add_page | Wiki | Create the page | edit.pt | edit | +| | | | *PageName* in | | | +| | | | storage, display | | | +| | | | the edit form | | | +| | | | without content. | | | +| | | | | | | +| | | | If the form was | | | +| | | | submitted, | | | +| | | | redirect to | | | +| | | | /PageName | | | ++----------------------+-------------+-----------------+-----------------------+------------+------------+ +| /login | login | Wiki, | Display login form. | login.pt | | +| | | Forbidden [3]_ | | | | +| | | | If the form was | | | +| | | | submitted, | | | +| | | | authenticate. | | | +| | | | | | | +| | | | - If authentication | | | +| | | | successful, | | | +| | | | redirect to the | | | +| | | | page that we | | | +| | | | came from. | | | +| | | | | | | +| | | | - If authentication | | | +| | | | fails, display | | | +| | | | login form with | | | +| | | | "login failed" | | | +| | | | message. | | | +| | | | | | | ++----------------------+-------------+-----------------+-----------------------+------------+------------+ +| /logout | logout | Wiki | Redirect to | | | +| | | | /FrontPage | | | ++----------------------+-------------+-----------------+-----------------------+------------+------------+ + +.. [1] This is the default view for a Page context + when there is no view name. +.. [2] Pyramid will return a default 404 Not Found page + if the page *PageName* does not exist yet. +.. [3] pyramid.exceptions.Forbidden is reached when a + user tries to invoke a view that is + not authorized by the authorization policy. diff --git a/docs/tutorials/wiki2/index.rst b/docs/tutorials/wiki2/index.rst index d05d70f3c..3a165291a 100644 --- a/docs/tutorials/wiki2/index.rst +++ b/docs/tutorials/wiki2/index.rst @@ -18,6 +18,7 @@ tutorial can be browsed at :maxdepth: 2 background + design installation basiclayout definingmodels -- cgit v1.2.3 From d20324d1d55ae987985e52ee8ab8515857d57d6d Mon Sep 17 00:00:00 2001 From: diana Date: Mon, 12 Mar 2012 17:54:19 -0400 Subject: working on removing: # pragma: no cover from pserve --- pyramid/scripts/pserve.py | 36 ++++---- pyramid/tests/test_scripts/test_pserve.py | 149 +++++++++++++++++++++++++++--- 2 files changed, 151 insertions(+), 34 deletions(-) diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py index 31a07c46f..dcc095d1a 100644 --- a/pyramid/scripts/pserve.py +++ b/pyramid/scripts/pserve.py @@ -303,7 +303,7 @@ class PServeCommand(object): if self.verbose > 1: raise if str(e): - msg = ' '+str(e) + msg = ' ' + str(e) else: msg = '' self.out('Exiting%s (-v to see traceback)' % msg) @@ -396,8 +396,7 @@ class PServeCommand(object): os.dup2(0, 1) # standard output (1) os.dup2(0, 2) # standard error (2) - def _remove_pid_file(self, written_pid, filename, - verbosity): # pragma: no cover + def _remove_pid_file(self, written_pid, filename, verbosity): current_pid = os.getpid() if written_pid != current_pid: # A forked process must be exiting, not the process that @@ -405,17 +404,16 @@ class PServeCommand(object): return if not os.path.exists(filename): return - f = open(filename) - content = f.read().strip() - f.close() + with open(filename) as f: + content = f.read().strip() try: pid_in_file = int(content) except ValueError: pass else: if pid_in_file != current_pid: - self.out("PID file %s contains %s, not expected PID %s" % ( - filename, pid_in_file, current_pid)) + msg = "PID file %s contains %s, not expected PID %s" + self.out(msg % (filename, pid_in_file, current_pid)) return if verbosity > 0: self.out("Removing PID file %s" % filename) @@ -424,24 +422,22 @@ class PServeCommand(object): return except OSError as e: # Record, but don't give traceback - self.out("Cannot remove PID file: %s" % e) + self.out("Cannot remove PID file: (%s)" % e) # well, at least lets not leave the invalid PID around... try: - f = open(filename, 'w') - f.write('') - f.close() + with open(filename, 'w') as f: + f.write('') except OSError as e: - self.out('Stale PID left in file: %s (%e)' % (filename, e)) + self.out('Stale PID left in file: %s (%s)' % (filename, e)) else: self.out('Stale PID removed') - def record_pid(self, pid_file): # pragma: no cover + def record_pid(self, pid_file): pid = os.getpid() if self.verbose > 1: self.out('Writing PID %s to %s' % (pid, pid_file)) - f = open(pid_file, 'w') - f.write(str(pid)) - f.close() + with open(pid_file, 'w') as f: + f.write(str(pid)) atexit.register(self._remove_pid_file, pid, pid_file, self.verbose) def stop_daemon(self): # pragma: no cover @@ -533,7 +529,7 @@ class PServeCommand(object): if exit_code != 3: return exit_code if self.verbose > 0: - self.out('%s %s %s' % ('-'*20, 'Restarting', '-'*20)) + self.out('%s %s %s' % ('-' * 20, 'Restarting', '-' * 20)) def change_user_group(self, user, group): # pragma: no cover if not user and not group: @@ -795,7 +791,7 @@ class Monitor(object): # pragma: no cover def periodic_reload(self): while True: - if not self.check_reload(): + if not self.check_reload(): self._exit() break time.sleep(self.poll_interval) @@ -965,7 +961,7 @@ def cherrypy_server_runner( try: protocol = is_ssl and 'https' or 'http' if host == '0.0.0.0': - print('serving on 0.0.0.0:%s view at %s://127.0.0.1:%s' % + print('serving on 0.0.0.0:%s view at %s://127.0.0.1:%s' % (port, protocol, port)) else: print('serving on %s://%s:%s' % (protocol, host, port)) diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py index e21a703d9..534bb37aa 100644 --- a/pyramid/tests/test_scripts/test_pserve.py +++ b/pyramid/tests/test_scripts/test_pserve.py @@ -1,15 +1,21 @@ -import unittest +import __builtin__ import os import tempfile +import unittest class TestPServeCommand(unittest.TestCase): def setUp(self): from pyramid.compat import NativeIO self.out_ = NativeIO() + self.pid_file = None + + def tearDown(self): + if self.pid_file and os.path.exists(self.pid_file): + os.remove(self.pid_file) def out(self, msg): self.out_.write(msg) - + def _getTargetClass(self): from pyramid.scripts.pserve import PServeCommand return PServeCommand @@ -21,6 +27,124 @@ class TestPServeCommand(unittest.TestCase): cmd.out = self.out return cmd + def _makeOneWithPidFile(self, pid): + self.pid_file = tempfile.mktemp() + inst = self._makeOne() + with open(self.pid_file, 'w') as f: + f.write(str(pid)) + return inst + + def test_remove_pid_file_verbose(self): + inst = self._makeOneWithPidFile(os.getpid()) + inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1) + self._assert_pid_file_removed(verbose=True) + + def test_remove_pid_file_not_verbose(self): + inst = self._makeOneWithPidFile(os.getpid()) + inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=0) + self._assert_pid_file_removed(verbose=False) + + def test_remove_pid_not_a_number(self): + inst = self._makeOneWithPidFile('not a number') + inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1) + self._assert_pid_file_removed(verbose=True) + + def test_remove_pid_current_pid_is_not_written_pid(self): + inst = self._makeOneWithPidFile(os.getpid()) + inst._remove_pid_file('99999', self.pid_file, verbosity=1) + self._assert_pid_file_not_removed('') + + def test_remove_pid_current_pid_is_not_pid_in_file(self): + inst = self._makeOneWithPidFile('99999') + inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1) + msg = 'PID file %s contains 99999, not expected PID %s' + self._assert_pid_file_not_removed(msg % (self.pid_file, os.getpid())) + + def test_remove_pid_no_pid_file(self): + inst = self._makeOne() + self.pid_file = 'some unknown path' + inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1) + self._assert_pid_file_removed(verbose=False) + + def test_remove_pid_file_unlink_exception(self): + inst = self._makeOneWithPidFile(os.getpid()) + self._remove_pid_unlink_exception(inst) + msg = [ + 'Removing PID file %s' % (self.pid_file), + 'Cannot remove PID file: (Some OSError - unlink)', + 'Stale PID removed'] + self._assert_pid_file_not_removed(msg=''.join(msg)) + + def test_remove_pid_file_stale_pid_write_exception(self): + inst = self._makeOneWithPidFile(os.getpid()) + self._remove_pid_unlink_and_write_exceptions(inst) + msg = [ + 'Removing PID file %s' % (self.pid_file), + 'Cannot remove PID file: (Some OSError - unlink)', + 'Stale PID left in file: %s ' % (self.pid_file), + '(Some OSError - open)'] + self._assert_pid_file_not_removed(msg=''.join(msg)) + + def test_record_pid_verbose(self): + self._assert_record_pid(verbosity=2, msg='Writing PID %d to %s') + + def test_record_pid_not_verbose(self): + self._assert_record_pid(verbosity=1, msg='') + + def _remove_pid_unlink_exception(self, inst): + old_unlink = os.unlink + + def fake_unlink(filename): + raise OSError('Some OSError - unlink') + + try: + os.unlink = fake_unlink + inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1) + finally: + os.unlink = old_unlink + + def _remove_pid_unlink_and_write_exceptions(self, inst): + old_unlink = os.unlink + old_open = __builtin__.open + + def fake_unlink(filename): + raise OSError('Some OSError - unlink') + + run_already = [] + def fake_open(*args): + if not run_already: + run_already.append(True) + return old_open(*args) + raise OSError('Some OSError - open') + + try: + os.unlink = fake_unlink + __builtin__.open = fake_open + inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1) + finally: + os.unlink = old_unlink + __builtin__.open = old_open + + def _assert_pid_file_removed(self, verbose=False): + self.assertFalse(os.path.exists(self.pid_file)) + msg = 'Removing PID file %s' % (self.pid_file) if verbose else '' + self.assertEqual(self.out_.getvalue(), msg) + + def _assert_pid_file_not_removed(self, msg): + self.assertTrue(os.path.exists(self.pid_file)) + self.assertEqual(self.out_.getvalue(), msg) + + def _assert_record_pid(self, verbosity, msg): + self.pid_file = tempfile.mktemp() + pid = os.getpid() + inst = self._makeOne() + inst.verbose = verbosity + inst.record_pid(self.pid_file) + msg = msg % (pid, self.pid_file) if msg else '' + self.assertEqual(self.out_.getvalue(), msg) + with open(self.pid_file) as f: + self.assertEqual(int(f.read()), pid) + def test_run_no_args(self): inst = self._makeOne() result = inst.run() @@ -31,15 +155,15 @@ class TestPServeCommand(unittest.TestCase): path = os.path.join(os.path.dirname(__file__), 'wontexist.pid') inst = self._makeOne('--stop-daemon', '--pid-file=%s' % path) inst.run() - self.assertEqual(self.out_.getvalue(),'No PID file exists in %s' % - path) - + msg = 'No PID file exists in %s' % path + self.assertEqual(self.out_.getvalue(), msg) + def test_run_stop_daemon_bad_pid_file(self): path = __file__ inst = self._makeOne('--stop-daemon', '--pid-file=%s' % path) inst.run() - self.assertEqual( - self.out_.getvalue(),'Not a valid PID file in %s' % path) + msg = 'Not a valid PID file in %s' % path + self.assertEqual(self.out_.getvalue(), msg) def test_run_stop_daemon_invalid_pid_in_file(self): fn = tempfile.mktemp() @@ -48,15 +172,15 @@ class TestPServeCommand(unittest.TestCase): tmp.close() inst = self._makeOne('--stop-daemon', '--pid-file=%s' % fn) inst.run() - self.assertEqual(self.out_.getvalue(), - 'PID in %s is not valid (deleting)' % fn) + msg = 'PID in %s is not valid (deleting)' % fn + self.assertEqual(self.out_.getvalue(), msg) def test_parse_vars_good(self): vars = ['a=1', 'b=2'] inst = self._makeOne('development.ini') result = inst.parse_vars(vars) self.assertEqual(result, {'a': '1', 'b': '2'}) - + def test_parse_vars_bad(self): vars = ['a'] inst = self._makeOne('development.ini') @@ -85,7 +209,7 @@ class TestLazyWriter(unittest.TestCase): finally: fp.close() os.remove(filename) - + def test_write(self): filename = tempfile.mktemp() try: @@ -138,6 +262,3 @@ class Test__methodwrapper(unittest.TestCase): class Bar(object): pass wrapper = self._makeOne(foo, Bar, None) self.assertRaises(AssertionError, wrapper, cls=1) - - - -- cgit v1.2.3 From 218ad474c5e8db6925ed6a5f4067ae3db69f68c8 Mon Sep 17 00:00:00 2001 From: Patricio Paez Date: Mon, 12 Mar 2012 15:49:58 -0700 Subject: Added links to Starting the Application - Sections 'Viewing the Application in a Browser' in the 'Defining the Domain Model' and 'Defining the Views' chapters now offer a quick link in both wiki tutorials, as suggested by Paulo. --- docs/tutorials/wiki/definingmodels.rst | 3 ++- docs/tutorials/wiki/definingviews.rst | 4 ++-- docs/tutorials/wiki/installation.rst | 2 ++ docs/tutorials/wiki2/definingmodels.rst | 3 ++- docs/tutorials/wiki2/definingviews.rst | 3 ++- docs/tutorials/wiki2/installation.rst | 2 ++ 6 files changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst index cdf3b6092..982d6ea74 100644 --- a/docs/tutorials/wiki/definingmodels.rst +++ b/docs/tutorials/wiki/definingmodels.rst @@ -88,7 +88,8 @@ View the Application in a Browser We can't. At this point, our system is in a "non-runnable" state; we'll need to change view-related files in the next chapter to be able to start the -application successfully. If you try to start the application, you'll wind +application successfully. If you try to start the application (See +:ref:`wiki-start-the-application`), you'll wind up with a Python traceback on your console that ends with this exception: .. code-block:: text diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 371cae8eb..53e60fda3 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -301,8 +301,8 @@ subdirectories) and are just referred to by URL. Viewing the Application in a Browser ==================================== -We can finally examine our application in a -browser. The views we'll try are as follows: +We can finally examine our application in a browser (See +:ref:`wiki-start-the-application`). The views we'll try are as follows: - Visiting ``http://localhost:6543/`` in a browser invokes the ``view_wiki`` view. This always redirects to the ``view_page`` view of the ``FrontPage`` diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index 330b17c86..63b30da5a 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -228,6 +228,8 @@ Looks like the code in the ``zodb`` scaffold for ZODB projects is missing some test coverage, particularly in the file named ``models.py``. +.. _wiki-start-the-application: + Start the Application ===================== diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst index 2b8932a98..66106ffee 100644 --- a/docs/tutorials/wiki2/definingmodels.rst +++ b/docs/tutorials/wiki2/definingmodels.rst @@ -122,7 +122,8 @@ Viewing the Application in a Browser We can't. At this point, our system is in a "non-runnable" state; we'll need to change view-related files in the next chapter to be able to start the -application successfully. If you try to start the application, you'll wind +application successfully. If you try to start the application (See +:ref:`wiki2-start-the-application`), you'll wind up with a Python traceback on your console that ends with this exception: .. code-block:: text diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst index a067dbd66..592b183a4 100644 --- a/docs/tutorials/wiki2/definingviews.rst +++ b/docs/tutorials/wiki2/definingviews.rst @@ -323,7 +323,8 @@ something like so: Viewing the Application in a Browser ==================================== -We can finally examine our application in a browser. The views we'll try are +We can finally examine our application in a browser (See +:ref:`wiki2-start-the-application`). The views we'll try are as follows: - Visiting ``http://localhost:6543`` in a browser invokes the diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index be97f1cb3..e5d2a0b5b 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -215,6 +215,8 @@ If successful, you will see output something like this:: Looks like our package doesn't quite have 100% test coverage. +.. _wiki2-start-the-application: + Starting the Application ======================== -- cgit v1.2.3 From d67df81cbc1b4fafa967380b1f56ce5b33c604bc Mon Sep 17 00:00:00 2001 From: dianaclarke Date: Mon, 12 Mar 2012 18:55:16 -0400 Subject: test the contents of the pid file in the exception cases --- pyramid/tests/test_scripts/test_pserve.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py index 534bb37aa..c1b91e3ae 100644 --- a/pyramid/tests/test_scripts/test_pserve.py +++ b/pyramid/tests/test_scripts/test_pserve.py @@ -74,6 +74,8 @@ class TestPServeCommand(unittest.TestCase): 'Cannot remove PID file: (Some OSError - unlink)', 'Stale PID removed'] self._assert_pid_file_not_removed(msg=''.join(msg)) + with open(self.pid_file) as f: + self.assertEqual(f.read(), '') def test_remove_pid_file_stale_pid_write_exception(self): inst = self._makeOneWithPidFile(os.getpid()) @@ -84,6 +86,8 @@ class TestPServeCommand(unittest.TestCase): 'Stale PID left in file: %s ' % (self.pid_file), '(Some OSError - open)'] self._assert_pid_file_not_removed(msg=''.join(msg)) + with open(self.pid_file) as f: + self.assertEqual(int(f.read()), os.getpid()) def test_record_pid_verbose(self): self._assert_record_pid(verbosity=2, msg='Writing PID %d to %s') -- cgit v1.2.3 From e2919c4916c9dba5c6bfce14035b2a4a5333c0fd Mon Sep 17 00:00:00 2001 From: Audrey Roy Date: Mon, 12 Mar 2012 15:57:45 -0700 Subject: More concise views.py section. --- docs/tutorials/wiki2/basiclayout.rst | 37 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst index e4200ae0f..29e272081 100644 --- a/docs/tutorials/wiki2/basiclayout.rst +++ b/docs/tutorials/wiki2/basiclayout.rst @@ -41,9 +41,7 @@ the ``main`` function we've defined in our ``__init__.py``: When you invoke the ``pserve development.ini`` command, the ``main`` function above is executed. It accepts some settings and returns a :term:`WSGI` -application. You can read :ref:`startup_chapter` for details about *how* -this function is found and called when you run ``pserve``, but for purposes -of brevity, we'll elide the details here. +application. (See :ref:`startup_chapter` for more about ``pserve``.) The main function first creates a SQLAlchemy database engine using ``engine_from_config`` from the ``sqlalchemy.`` prefixed settings in the @@ -134,24 +132,21 @@ Here is the entirety of code in the ``views.py`` file within our package: :linenos: :language: py -The important part to point out here is the ``@view_config`` decorator which -sits atop the ``my_view`` function. In fact, ``@view_config`` is so -important that we're going to ignore the rest of the code in the module at -this point just to explain it. The ``@view_config`` decorator associates the -function it decorates with a :term:`view configuration`. The view -configuration names a ``route_name`` (``home``), and names a ``renderer``, -which is a template which lives in the ``templates`` subdirectory of the -package. - -As the result of this view configuration, when the pattern associated with -the view named ``home`` is matched during a request, the function named -``my_view`` will be executed. The function named ``my_view`` returns a -dictionary; the renderer will use the ``templates/mytemplate.pt`` template to -create a response based on the values in the dictionary. - -Note that the decorated function named ``my_view`` accepts a single argument -named ``request``. This is the standard call signature for a Pyramid -:term:`view callable`. +The important part here is that the ``@view_config`` decorator associates the +function it decorates (``my_view``) with a :term:`view configuration`, +consisting of: + + * a ``route_name`` (``home``) + * a ``renderer``, which is a template from the ``templates`` subdirectory + of the package. + +When the pattern associated with the ``home`` view is matched during a request, +``my_view()`` will be executed. ``my_view()`` returns a dictionary; the +renderer will use the ``templates/mytemplate.pt`` template to create a response +based on the values in the dictionary. + +Note that ``my_view()`` accepts a single argument named ``request``. This is +the standard call signature for a Pyramid :term:`view callable`. Remember in our ``__init__.py`` when we executed the :meth:`pyramid.config.Configurator.scan` method, e.g. ``config.scan()``? The -- cgit v1.2.3 From 03dfcebb082caa29823262ed2137c29c06aa4349 Mon Sep 17 00:00:00 2001 From: dianaclarke Date: Mon, 12 Mar 2012 19:36:08 -0400 Subject: monkey patch atexit, since it seems to be gernerating this error in same cases (but all the tests still pass) Ran 2083 tests in 8.913s OK Error in atexit._run_exitfuncs: Traceback (most recent call last): File "/usr/lib/python2.6/atexit.py", line 24, in _run_exitfuncs func(*targs, **kargs) File "/mnt/hgfs/workspace/pyramid/pyramid/scripts/pserve.py", line 400, in _remove_pid_file current_pid = os.getpid() AttributeError: 'NoneType' object has no attribute 'getpid' --- pyramid/tests/test_scripts/test_pserve.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py index c1b91e3ae..6417791f3 100644 --- a/pyramid/tests/test_scripts/test_pserve.py +++ b/pyramid/tests/test_scripts/test_pserve.py @@ -1,3 +1,4 @@ +import atexit import __builtin__ import os import tempfile @@ -97,7 +98,6 @@ class TestPServeCommand(unittest.TestCase): def _remove_pid_unlink_exception(self, inst): old_unlink = os.unlink - def fake_unlink(filename): raise OSError('Some OSError - unlink') @@ -109,12 +109,11 @@ class TestPServeCommand(unittest.TestCase): def _remove_pid_unlink_and_write_exceptions(self, inst): old_unlink = os.unlink - old_open = __builtin__.open - def fake_unlink(filename): raise OSError('Some OSError - unlink') run_already = [] + old_open = __builtin__.open def fake_open(*args): if not run_already: run_already.append(True) @@ -139,11 +138,21 @@ class TestPServeCommand(unittest.TestCase): self.assertEqual(self.out_.getvalue(), msg) def _assert_record_pid(self, verbosity, msg): + old_atexit = atexit.register + def fake_atexit(*args): + pass + self.pid_file = tempfile.mktemp() pid = os.getpid() inst = self._makeOne() inst.verbose = verbosity - inst.record_pid(self.pid_file) + + try: + atexit.register = fake_atexit + inst.record_pid(self.pid_file) + finally: + atexit.register = old_atexit + msg = msg % (pid, self.pid_file) if msg else '' self.assertEqual(self.out_.getvalue(), msg) with open(self.pid_file) as f: -- cgit v1.2.3 From 197808638a50dc085efc7511e24c043274e35663 Mon Sep 17 00:00:00 2001 From: Audrey Roy Date: Mon, 12 Mar 2012 17:02:46 -0700 Subject: Clarified which files to open/edit. Highlighted the lines to be edited in models.py. --- docs/tutorials/wiki2/basiclayout.rst | 6 ++++-- docs/tutorials/wiki2/definingmodels.rst | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst index 29e272081..888ae0bf3 100644 --- a/docs/tutorials/wiki2/basiclayout.rst +++ b/docs/tutorials/wiki2/basiclayout.rst @@ -16,8 +16,10 @@ Application Configuration with ``__init__.py`` A directory on disk can be turned into a Python :term:`package` by containing an ``__init__.py`` file. Even if empty, this marks a directory as a Python package. We use ``__init__.py`` both as a marker indicating the directory -it's contained within is a package, and to contain configuration code. Our -``__init__.py`` file will look like this: +it's contained within is a package, and to contain configuration code. + +Open the ``tutorial/tutorial/__init__.py`` file. It should already contain +the following: .. literalinclude:: src/basiclayout/tutorial/__init__.py :linenos: diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst index 66106ffee..ca0493f32 100644 --- a/docs/tutorials/wiki2/definingmodels.rst +++ b/docs/tutorials/wiki2/definingmodels.rst @@ -21,11 +21,15 @@ Making Edits to ``models.py`` (or they may live in a Python subpackage of your application package named ``models``) , but this is only by convention. -Here's what our ``models.py`` file should look like after this step: +Open ``tutorial/tutorial/models.py`` file and edit it to look like the +following: .. literalinclude:: src/models/tutorial/models.py :linenos: :language: py + :emphasize-lines: 19-21,24,26,28 + +(The highlighted lines are the ones that need to be changed.) The first thing we've done is to do is remove the stock ``MyModel`` class from the generated ``models.py`` file. The ``MyModel`` class is only a -- cgit v1.2.3 From d0f5c6885d584493c451d776867d44585c89ec02 Mon Sep 17 00:00:00 2001 From: Audrey Roy Date: Mon, 12 Mar 2012 17:21:08 -0700 Subject: More clarification, telling the user to open specified files. --- docs/tutorials/wiki2/basiclayout.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst index 888ae0bf3..0fca0edf9 100644 --- a/docs/tutorials/wiki2/basiclayout.rst +++ b/docs/tutorials/wiki2/basiclayout.rst @@ -18,7 +18,7 @@ an ``__init__.py`` file. Even if empty, this marks a directory as a Python package. We use ``__init__.py`` both as a marker indicating the directory it's contained within is a package, and to contain configuration code. -Open the ``tutorial/tutorial/__init__.py`` file. It should already contain +Open ``tutorial/tutorial/__init__.py``. It should already contain the following: .. literalinclude:: src/basiclayout/tutorial/__init__.py @@ -128,7 +128,7 @@ pattern matches is done by registering a :term:`view configuration`. Our application uses the :meth:`pyramid.view.view_config` decorator to map view callables to each route, thereby mapping URL patterns to code. -Here is the entirety of code in the ``views.py`` file within our package: +Open ``tutorial/tutorial/views.py``. It should already contain the following: .. literalinclude:: src/basiclayout/tutorial/views.py :linenos: @@ -165,13 +165,13 @@ In a SQLAlchemy-based application, a *model* object is an object composed by querying the SQL database. The ``models.py`` file is where the ``alchemy`` scaffold put the classes that implement our models. -Here is the complete source for ``models.py``: +Open ``tutorial/tutorial/models.py``. It should already contain the following: .. literalinclude:: src/basiclayout/tutorial/models.py :linenos: :language: py -Let's take a look. First, we need some imports to support later code. +Let's examine this in detail. First, we need some imports to support later code: .. literalinclude:: src/basiclayout/tutorial/models.py :end-before: DBSession -- cgit v1.2.3 From 533c89ebe807f02f5eae3240e770864b21f78168 Mon Sep 17 00:00:00 2001 From: Patricio Paez Date: Mon, 12 Mar 2012 17:57:13 -0700 Subject: Clarify two steps in the SQL wiki tutorial - Highlight the added or changed lines --- docs/tutorials/wiki2/authorization.rst | 8 +++++++- docs/tutorials/wiki2/definingviews.rst | 7 +++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst index 900bf0975..5c60640b7 100644 --- a/docs/tutorials/wiki2/authorization.rst +++ b/docs/tutorials/wiki2/authorization.rst @@ -189,7 +189,13 @@ visit ``/login``. We'll need to import some stuff to service the needs of these two functions: the ``pyramid.view.forbidden_view_config`` class, a number of values from the ``pyramid.security`` module, and a value from our newly added -``tutorial.security`` package. +``tutorial.security`` package. Add the following import statements to the +head of the ``views.py`` file: + +.. literalinclude:: src/authorization/tutorial/views.py + :lines: 9-18,24-25 + :linenos: + :language: python Changing Existing Views ----------------------- diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst index 592b183a4..5f7526b24 100644 --- a/docs/tutorials/wiki2/definingviews.rst +++ b/docs/tutorials/wiki2/definingviews.rst @@ -28,15 +28,18 @@ dependency of the original "tutorial" application. The original "tutorial" application was generated by the ``pcreate`` command; it doesn't know about our custom application requirements. We need to add a dependency on the ``docutils`` package to our ``tutorial`` package's ``setup.py`` file by -assigning this dependency to the ``install_requires`` parameter in the +assigning this dependency to the ``requires`` parameter in the ``setup`` function. -Our resulting ``setup.py`` should look like so: +Open ``tutorial/setup.py`` and edit it to look like the following: .. literalinclude:: src/views/setup.py :linenos: + :emphasize-lines: 17 :language: python +(Only the highlighted line needs to be added) + Running ``setup.py develop`` ============================ -- cgit v1.2.3 From c4b64f07487e23427187c08aaf3aec8b89bd267f Mon Sep 17 00:00:00 2001 From: Audrey Roy Date: Mon, 12 Mar 2012 18:07:36 -0700 Subject: The import also has changed and needs to be edited by the user, so I highlighted it. --- docs/tutorials/wiki2/definingmodels.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst index ca0493f32..0810ad1c5 100644 --- a/docs/tutorials/wiki2/definingmodels.rst +++ b/docs/tutorials/wiki2/definingmodels.rst @@ -67,12 +67,15 @@ script. In particular, we'll replace our import of ``MyModel`` with one of ``Page`` and we'll change the very end of the script to create a ``Page`` rather than a ``MyModel`` and add it to our ``DBSession``. -The result of all of our edits to ``populate.py`` will end up looking -something like this: +Open ``tutorial/tutorial/scripts/populate.py`` and edit it to look like the +following: .. literalinclude:: src/models/tutorial/scripts/populate.py :linenos: :language: python + :emphasize-lines: 14,34 + +(Only the highlighted lines need to be changed.) Repopulating the Database ------------------------- -- cgit v1.2.3 From 7fcc3068d4317e81c791adc51d1144e79b654a67 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 12 Mar 2012 21:42:34 -0700 Subject: py3 compat --- pyramid/tests/test_scripts/test_pserve.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py index 6417791f3..1b45a5d90 100644 --- a/pyramid/tests/test_scripts/test_pserve.py +++ b/pyramid/tests/test_scripts/test_pserve.py @@ -1,9 +1,14 @@ import atexit -import __builtin__ import os import tempfile import unittest +from pyramid.compat import PY3 +if PY3: # pragma: no cover + import builtins as __builtin__ +else: + import __builtin__ + class TestPServeCommand(unittest.TestCase): def setUp(self): from pyramid.compat import NativeIO -- cgit v1.2.3