Ptah

Ptah Walkthrough

You should have a virtualenv with ptah installed. Let’s create an add-on:

~/venv/src$ ../bin/pcreate -t ptah_starter mypkg
~/venv/src$ cd mypkg
~/venv/src/mypkg$ ../../bin/python setup.py develop

Now let’s start the system. –reload will start the file watcher in paster and will restart the process any time a .py file is changed:

~/venv/src/mypkg$ ../../bin/pserve settings.ini --reload

Go to http://localhost:6543/ and click around. Things to look out for:

  • There is a simple webpage based on the bootstrap CSS library.
  • On the right hand side there is a tab that says DT and has a pyramid. This is the pyramid_debugtoolbar which provides all sorts of useful feedback during development.
  • There is a button “Goto Ptah Manage UI”. This Management UI has a lot of features for you to explore.

What you see on screen when you go to http://localhost:6543/ is a View registered with the / route under the mypkg folder in a file, views.py:

@view_config(renderer='mypkg:templates/homepage.pt',
             route_name='home')

class HomepageView(object):

    def __init__(self, request):
        self.request = request
..

There is no regular expression which matches /, but a name of a route, ‘home’. Let’s open up app.py in the same folder to see where the home route is defined. If you scan through the main() function you will see:

config.add_route('home', '/')

That is it. Let’s take this opportunity to review the main() function. All comments have been removed:

def main(global_config, **settings):

    config = Configurator(settings=settings,
                          session_factory = session_factory,
                          authentication_policy = auth_policy)
    config.include('ptah')

    config.ptah_init_sql()

    config.ptah_init_settings()

    config.ptah_init_manage(managers=('*',))

    Base = ptah.get_base()
    Base.metadata.create_all()

    config.add_route('home', '/')
    config.add_static_view('_mypkg', 'mypkg:static')
    config.scan()

    return config.make_wsgi_app()

Stepping through the code

  1. Instantiate a Pyramid Configurator.
  2. Notify Pyramid to run the ‘ptah’ extension.
  3. Set up the RDBMS. (See the “settings.ini” file for the connection string.)
  4. Activate ptah settings management with config.ptah_init_settings() which initializes additional ptah.settings and sends ptah.events.SettingsInitializing and ptah.events.SettingsInitialized.
  5. config.ptah_init_manage() enables the Ptah Manage Interface and manager=(‘*’,) allows anyone access to it.
  6. Set up the SQLAlchemy ORM and create tables if necessary.
  7. config.add_route(‘home’, ‘/’) registers / to the HomepageView
  8. config.add_static_view(‘_mypkg’, ‘mypkg:static’) allows you to call http://localhost:6543/_mypkg/app.css which you can see on filesystem, mypkg/static/app.css
  9. config.scan() imports all python modules in your application and performs registration. You will note there is no import .views inside the app.py module, because the scan makes that unnecessary.
  10. return config.make_wsgi_app() is Pyramid returning a configured WSGI application.

In summary, you put your application configuration inside of the function which will return a WSGI application. Any Pyramid extension, such as Ptah, is included via config.include(‘package_name’). We initialize Ptah. Then add your application views and routes using Pyramid syntax. We run a scan and then enable the Manager Interface. Lastly, return the configured WSGI application.

views.py

Now that we know how the application gets configured and we know how / calls the HomepageView, let’s look at how the static resources get included on the homepage. We will examine views.py and the template homepage.pt.

Let’s look at views.py:

import ptah

class HomepageView(object):

    def __init__(self, request):
        self.request = request
        ptah.include(request, 'bootstrap')
        ptah.include(request, 'bootstrap-js')

    def __call__(self):
        request = self.request
        self.rendered_includes = ptah.render_includes(request)
        self.rendered_messages = ptah.render_messages(request)
        return {}

Every time the view gets created it annotates the request object with its requirements, in this case “bootstrap” and “bootstrap-js”. Subsequenty, when Pyramid __calls__ the view, passing the view and the return value to the template, 2 additional functions are called: render_includes and render_messages. Both take the request object as a parameter.

render_includes

You specified what ptah.library you needed by using ptah.include in the constructor. Now we need to convert those into HTML for the <head> tag; we call ptah.render_includes which will return an HTML string ready to be included in the <head>. ptah.library supports dependencies and render_includes() will compute that dependency correctly.

render_messages

User performed actions such as submitting forms, logging in, or providing a user feedback notification is done with messages. These have been called “flash messages” in other web frameworks. Any messages your application has generated must be consumed (i.e. rendered) by calling render_messages().

Even though we do not create messages in the homepage.pt template, we still want to pump any previously generated messages. For instance, you might experiment with the Ptah Manage interface and somehow be redirected to the Homepage – you would want to see any messages created in previous requests immediately. This is why messages are usually handled in master (layout) templates.

homepage.pt

Now let’s go and look at the template which renders the HTML. It can be found in mkpkg/templates/homepage.pt and there are only a few lines of interest in the <head>:

<head>
  <meta charset="utf-8">
  <title>mypkg, made with Ptah!</title>
  ${structure: view.rendered_includes}
  <link type="text/css" rel="stylesheet"
        href="${request.static_url('empty:static/app.css')}" />
  <link rel="shortcut icon"
        href="${request.static_url('empty:static/ico/favicon.ico')}" />
</head>

This line:

${structure: view.rendered_includes}

...generates the HTML:

<link type="text/css" rel="stylesheet" href="http://localhost:6543/_ptah/static/bootstrap/bootstrap-1.4.0.min.css" />
<script src="http://localhost:6543/_ptah/static/jquery/jquery-1.7.min.js"> </script>
<script src="http://localhost:6543/_ptah/static/bootstrap/bootstrap-2.0.1.min.js"> </script>

Lastly to reference static assets this line:

<link rel="shortcut icon"
      href="${request.static_url('mypkg:static/ico/favicon.ico')}" />

...generates:

<link type="text/css" rel="stylesheet"
      href="http://localhost:6543/_mypkg/app.css" />

Conclusion

This demonstrates most of the view functionality. In the examples repository you can look at ptah_models for an example of using ptah.library. It ships with a colourpicker widget which requires a javascript library.

More Examples

There is a separate repository for examples. You can read the Examples documentation on-line at http://ptah-examples.readthedocs.org.

Database Structure

If you add Ptah to your project there are some database schema requirements. You name your tables whatever you like.

ptah_blobs

This table provides the ability to support large binary objects. It is used by the ptah.cms.blob.Blob model.

Name Type Null Default Comments
id int False ‘’ PK, FK ptah_nodes.id
mimetype varchar True ‘’  
filename varchar True ‘’  
size int True 0  
data blob True    

ptah_content

The ptah_content table provides a definition for base content model. It is used by the ptah.cms.Content model.

Name Type Null Default Comments
id int False ‘’ PK, FK ptah_nodes.id
path varchar True    
name varchar True   Maxlength 255
title varchar True    
description varchar True    
created datetime True    
modified datetime True    
effective datetime True    
expires datetime True    
lang varchar True    

ptah_nodes

The ptah_nodes table provides the base model for all data elements in the system. This table is used by the ptah.cms.Node model.

Name Type Null Default Comments
id int False ‘’ Primary key
type varchar True ‘’  
uri varchar False   Maxlength=255
parent varchar True ‘’ FK: ptah_nodes.uri
owner varchar True ‘’ Principal URI
roles text True ‘{}’ JSON
acls text True ‘[]’ JSON
annotations varchar True ‘{}’ JSON

ptah_settings

The ptah_settings table provides key, value for internal ptah settings machinery, in particular the ptah.settings.SettingRecord model.

Name Value Null Default Comments
name varchar False   Primary key
value varchar True ‘’  

ptah_tokens

The ptah_tokens table provides a space for transient tokens which are generated by application, such as password-reset tokens. You use the token service API but this table is used by ptah.token.Token model table.

Name Value Null Default Comments
id int False   Primary key
token varchar True   MaxLegnth 48
valid datetime True    
data varchar True    
type varchar True   MaxLength 48

ptah_db_versions

The ptah_db_versions table contains migration revisions information.

Name Value Null Default Comments
package str False   Primary key
version_num varchar True   MaxLegnth 32

Manage Interface

The Ptah Management UI is a dashboard into your application. The Manage Interface is simple, extensible, and has quite a few features out of the box.

By default the Manage Interface is disabled.

managers sequence are login attributes from the ptah.auth_service:

>> from ptah import auth_service
>> print auth_service.get_current_principal().login
>> runyaga@gmail.com

Configuring

The Manage Interface is configured through Ptah Settings. You will do this inside of your WSGI entry point where you return make_wsgi_app(). config is the Pyramid configurator.:

config.ptah_init_manage(
    managers = ['*'],
    disable_modules = ['rest', 'introspect', 'apps', 'permissions', 'settings'])

Enable

Inside of Ptah Settings you can set the managers argument the userid’s you want to allow access. * means everyone. By default it is empty and no one is allowed access. Granting everyone:

managers = ['*']

Granting a few people:

managers = ['bob@dobbs.com', 'runyaga@gmail.com']

Disable

By default the Manage Interface is disabled. If Manage Interface is enabled but you want to prevent users from able to access it add the following to your .ini file:

ptah.manage = ""

Out-of-the-box Modules

The listing of modules you see when you open up http://localhost:6543/ptah-manage interface are all modules which your account has permission to view. Below are the out-of-the-box modules and a description.

REST

This module provides a interactive javascript REST introspector for the Ptah application. If you want to see this in action see the ptah_minicms in examples repository.

Introspect

A comprehensive view into all registrations in your application. It provides mechanisms to query URI’s, see events registered, subscribers, and you can jump directly to the source code where registration takes place.

Permissions

A list of permission sets which are used by all applications running in the system.

Settings

Listing of all settings for Pyramid and Ptah. Ptah has extra settings features. This settings module will show more variables than the .ini file that you used to start Pyramid. These extra settings are from Ptah such as ptah.formatter strings.

SQLAlchemy

Uses SQLAlchemy reflection capabilities to display all tables & rows that are accessible in the database. If a table is polymorphic it is not editable.

Models

CRUD (CReate Update and Delete) interface for models. Displays a list of registered models, allows you to modify the records.

Applications

A list of all Ptah applications registered in the system.

Field types

A preview of most registered form Fields in the system. If a field does not provide a preview it will now show up. You can see how each field will be rendered.

Extending

The simplest module example to look at is in ptah/manage/rest.py which registers a template.

Module

Create a class which subclasses ptah.manage.PtahModule. Decorate the class with ptah.manage.module() decorate. The label you register using the manage.module decorator is the internal key for that module. If you wanted to disable it you would use this name in the ptah_settings[‘disable_modules’] registration.

An example:

import ptah

@ptah.manage.module('rest')
class RestModule(ptah.manage.PtahModule):
    """
    REST Introspector
    """
    title = 'REST Introspector'

View

The module views for the Manage Interface use traversal. It is important to note that you do not have to use ptah.View but you will need to use wrapper so your template will look like the rest of the Manage Interface. Here is an example, again, from the REST module:

from pyramid.view import view_config

@view_config(
    context=RestModule,
    wrapper=ptah.wrap_layout(),
    renderer='ptah.manage:templates/rest.pt')

class RestModuleView(ptah.View):
    def update(self):
        self.url = self.request.params.get('url','')

Nothing special. Just a Pyramid view with wrapper=ptah.wrap_layout() and you can do whatever you like in that view.

Ptah Forms

ptah.form is an optional form library package. It provides some benefits when using it with the integrated environment such as autogeneration of forms for models, validation using the sqlalchemy constraints, and JSON representations for the REST api.

Form

Supports HTML generation, CSRF protection, additional field validation.

Form Validation

The default form validation uses CSRF prtoection to validate data input.

Fieldset

A ordered dictionary of Field instances. Fieldset supports validation and extraction.

Fieldset Validation

Is used when you have validation dependencies between Fields. For instance if Field Y depends

Fieldset Extraction

Internal implementation details and only needed for expert usage. Possibly needs renaming or refactoring.

Field

Fields are important in Ptah not only because its how forms are used in the HTML interface but they are also used in the REST interface. For instance when you send update via HTTP (html POST or json POST) the same field implementation is used for deserialization and validation.

Field Serialization

  • serialize converts the field and data python structure into string representation. e.g. dattime.date(2011, 12, 12) into ‘2011-12-12’.
  • deserailize converts a string representation into the internal representation. e.g. 2011-12-12 into datetime.date(2011, 12, 12)

Field Modes

Display mode and input mode.

Field Validation

Specific validation rules or whether a field is required is validated through the field validation.

1
2
3
4
5
6
7
 TELEPHONE_REGEX = u'^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$'
 class Telephone(form.Regex):
     """ Telephone number validator """
     def __init__(self, msg=None):
         if msg is None:
             msg = "Invalid telephone number"
         super(Telephone, self).__init__(TELEPHONE_REGEX, msg=msg)

A ptah.form.Field accepts a validator in its constructor. The fields’ validator will be called by the form with both field and value as parameters.

1
2
3
4
5
 form.TextField(
     'phone',
     title = u'Telephone number',
     description=u'Please provide telephone number',
     validator = Telephone()),

Field Extraction

Extracts the value from request.

Field Factory

Expert level usage. This is how Ptah’s internals work.

Examples

There are 2 form examples which can be found in ptah_models package in the examples repository. You can find both examples in ptah_models/views.py.

Manual Form & Fieldset

The contact-us form in ptah_models.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
 contactform = form.Form(context, request)
 contactform.fields = form.Fieldset(
     form.TextField(
         'fullname',
         title = u'First & Last Name'),

     form.TextField(
         'phone',
         title = u'Telephone number',
         description=u'Please provide telephone number',
         validator = Telephone()),

     form.TextField(
         'email',
         title = u'Your email',
         description = u'Please provide email address.',
         validator = form.Email()),

     form.TextAreaField(
         'subject',
         title = u'How can we help?',
         missing = u''),
     )

 # form actions
 def cancelAction(form):
     return HTTPFound(location='/')

 def updateAction(form):
     data, errors = form.extract()

     if errors:
         form.message(errors, 'form-error')
         return

     # form.context is ...
     form.context.fullname = data['fullname']
     form.context.phone = data['phone']
     form.context.email = data['email']
     form.context.subject = data['subject']

     # You would add any logic/database updates/insert here.
     # You would probably also redirect.

     log.info('The form was updated successfully')
     form.message('The form was updated successfully')

 contactform.label = u'Contact us'
 contactform.buttons.add_action('Update', action=updateAction)
 contactform.buttons.add_action('Cancel', action=cancelAction)

 # form default values
 contactform.content = {}

 # compute the form
 result = contactform.update()
 if isinstance(result, HTTPFound):
     return result

 # generate HTML from form
 html = contactform.render()

Manual Form & Auto-Fieldset

In the ptah_models package there is a content model, Link. This model can be found in ptah_models/models.py. This code-snippet is found in the ptah_models/views.py.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 linkform = form.Form(context,request)
 linkform.fields = models.Link.__type__.fieldset

 def cancelAction(form):
     return HTTPFound(location='/')

 def updateAction(form):
     data, errors = form.extract()
     if errors:
         form.message(errors, 'form-error')
         return

     link = models.Link(title = data['title'],
                        href = data['href'],
                        color = data['color'])
     ptah.get_session().add(link)

     form.message('Link has been created.')
     return HTTPFound(location='/')

 linkform.label = u'Add link'
 linkform.buttons.add_action('Add', action=updateAction)
 linkform.buttons.add_action('Cancel', action=cancelAction)

 result = linkform.update() # prepare form for rendering
 if isinstance(result, HTTPFound):
     return result

 rendered_form = linkform.render()

Everything Manual

Using form without context and request.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 from ptah import form
 from ptah_models.models import Link

 def action1(form):
     print ('action1', form)

 def action2(form):
     print ('action2', form)

 eform = form.Form(None, None)
 eform.params = {}
 eform.method = 'params'
 eform.fields = Link.__type__.fieldset

 eform.buttons.add_action('Test submit', name='ac1', action=action1)
 eform.buttons.add_action('Test action2', name='ac2', action=action2)

 print "==== execute action1 ===="
 eform.params = {'%sbuttons.ac1'%eform.prefix: 'value'}
 eform.update()

 print
 print "==== extract data ====="
 data, errors = eform.extract()

 print
 print "DATA:"
 pprint(data)

 print
 print "ERRORS:"
 pprint(errors)

Class-based Form

Example of subclassing ptah.form.Form.

Authentication Service

Need to integrate user logins to new authentication service? e.g. using LDAP, OAuth, Openid, Mongo, or ZODB to source user credentials.

There are 4 facilities of which 2 are optional:

  • User provider, required
  • User resolver, required
  • Password changer, optional
  • User searcher, optional

Example

You can find an “in-memory” user provider in examples/auth_provider.py

User provider

A Principal (User) must have at least 3 attributes:

  • user.uri, which must be resolvable to the User model
  • user.login, which is identifier such as email
  • user.name, human readable user name

The Provider class provides 2 methods:

  • authenticate, which takes a mapping {‘login’:’user’, ‘password’:’pw’}
  • get_principal_bylogin, which takes a login string and returns User

You register a Provider by calling ptah.register_auth_provider and provide a uri scheme and instance.

User resolver

Resolvers in Ptah are the way we indirect lookup between components. For instance, instead of storing the primary key of the user for say, the ptah.cms.node.Owner field; we make that a string and store a URI. URIs can be resolved into a object.

This code registeres a function which returns a object given a URI:

@ptah.register_uri_resolver('user+crowd', 'Crowd principal resolver')
def getPrincipal(uri):
    return User.get(uri)

Any uri prefixed with ‘user+crowd’ will be sent to this function, getPrincipal. For instance, uri.resolve(‘user+crowd:bob’) would be sent to getPrincipal to return a Principal with that uri.

Password changer

A function which is responsible for changing a user’s password. An example:

ptah.pwd_tool.register_password_changer('user+crowd', change_pw)

Password changer is optional.

Principal searcher

A function which accepts a string and returns a iterator. This is registered with URI scheme and function:

import ptah
ptah.register_principal_searcher('user+crowd', search)

Superuser

There is another authentication service, ptah+auth which provides a sole superuser Principal. The name is superuser. It is a special Principal. You can not login with this Prinipcal. It is useful for integration tests.

Command-line utilities

Your ptah application can be controlled and inspected using a variety of command-line utilities. These utilities are documented in this chapter.

Application Settings

You can use the ptah-settings command in a terminal window to print a summary of settings to your application registered with ptah.register_settings() api. Much like the any pyramid command, the ptah-settings command accepts one argument with the format config_file#section_name. The config_file is the path to your application’s .ini file, and section_name is the app section name inside the .ini file which points to your application. By default, the section_name is main and can be omitted.

For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[fafhrd@... MyProject]$ ../bin/ptah-settings development.ini

* Ptah settings (ptah)

  - ptah.secret: Authentication policy secret (TextField: secret-
    ptah!)
       The secret (a string) used for auth_tkt cookie signing

  - ptah.manage: Ptah manage id (TextField: ptah-manage)

...

By default ptah-settings shows all sections. It possible to show only certain settings section like “ptah” or “format”.

It is possible to print all settings in .ini format.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[fafhrd@... MyProject]$ ../bin/ptah-settings development.ini -p
[DEFAULT]
format.date_full = "%%A, %%B %%d, %%Y"
format.date_long = "%%B %%d, %%Y"
format.date_medium = "%%b %%d, %%Y"
format.date_short = "%%m/%%d/%%y"
format.time_full = "%%I:%%M:%%S %%p %%Z"
format.time_long = "%%I:%%M %%p %%z"
format.time_medium = "%%I:%%M %%p"
format.time_short = "%%I:%%M %%p"
format.timezone = "us/central"
ptah.disable_models = []
ptah.disable_modules = []
...
ptah.pwd_letters_digits = false
ptah.pwd_letters_mixed_case = false
ptah.pwd_manager = "plain"
ptah.pwd_min_length = 5

Application Information

You can use the ptah-manage command in a terminal window to print a summary of different information of your application. The ptah-manage command accepts one argument with the format config_file#section_name.

List management modules

Use --list-modules argument to the ptah-manage to list all registered ptah management modules.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[fafhrd@... MyProject]$ ../bin/ptah-manage development.ini --list-modules

* apps: Applications (disabled: False)
    A listing of all registered Ptah Applications.

* crowd: User management (disabled: False)
    Default user management. Create, edit, and activate users.

* fields: Field types (disabled: False)
    A preview and listing of all form fields in the application. This
    is useful to see what fields are available. You may also interact
    with the field to see how it works in display mode.

...

List db models

Use --list-models argument to the ptah-manage to list all registered ptah models.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[fafhrd@... MyProject]$ ../bin/ptah-manage development.ini --list-models

* cms-type:app: Application (disabled: False)
    Default ptah application

    class: ApplicationRoot
    module: MyProject.root
    file:  .../root.pyc

...

Data population

You can use the ptah-populate command in a terminal window to execute a populate steps registered with ptah.populate() api. Much like the any pyramid command, the ptah-populate command accepts one argument with the format config_file#section_name.

Use -l argument to list all registered steps.

1
2
3
4
5
6
7
[fafhrd@... MyProject]$ ../bin/ptah-populate development.ini -l

* ptah-db-schema: Create db schema (active)

* ptah-crowd-admin: Create admin user (active)

...

It shows step name, then step title, and activity state. If step is active it is beeing executed automatically with -a argument.

Use -a argument to execute all active steps.

1
2
3
4
5
6
7
8
[fafhrd@... MyProject]$ ../bin/ptah-populate development.ini -a
2012-01-03 12:43:46,796 INFO  [ptah][MainThread] Executing populate step: ptah-db-schema
2012-01-03 12:43:46,797 INFO  [ptah][MainThread] Creating db table `ptah_crowd`.
2012-01-03 12:43:46,931 INFO  [ptah][MainThread] Creating db table `ptah_blobs`
...
2012-01-03 12:43:48,087 INFO  [ptah][MainThread] Executing populate step: ptah-crowd-admin
2012-01-03 12:43:48,092 INFO  [ptah_crowd][MainThread] Creating admin user `admin` Ptah admin
...

Its possible to execute inactive steps or specific step with all required steps. Specify step names in command line after your ini file.

1
[fafhrd@... MyProject]$ ../bin/ptah-populate development.ini ptah-db-schema ptah-crowd-admin

Data migration

You can use the ptah-migrate command in a terminal window to execute a migration steps registered with ptah.register_migration() api. Much like the any pyramid command, the ptah-migrate command accepts first argument with the format config_file#section_name.

Use list argument to list all registered migrations.

1
2
3
4
5
6
7
[fafhrd@... MyProject]$ ../bin/ptah-migrate development.ini list

* ptah: Ptah database migration
    ptah:migrations
    /...src/ptah/ptah/migrations

...

Use revision argument to create new revision for registered migration.

1
2
3
[fafhrd@... MyProject]$ ../bin/ptah-migrate development.ini revision ptah
  Generating /../src/ptah/ptah/migrations/3fd73c8b8727.py...done
...

Additional arguments:

-r specify custom revision id. revision id has to contain only letters and numbers.

-m specify revision message

Full command can look like:

1
2
[fafhrd@... MyProject]$ ../bin/ptah-migrate development.ini revision ptah -r 001 -m "Add new column X"
  ...

Use upgrade argument to upgrade package to specific revision. You can specify one or more packages after upgrade argument.

1
2
3
[fafhrd@... MyProject]$ ../bin/ptah-migrate development.ini upgrade ptah
2012-01-11 17:00:44,506 INFO  [ptah.alembic] ptah: running upgrade None -> 0301
...

To specify specific revision number use : and revision number. For example ptah:0301

Use current argument to check current package revision. You can specify any number of package after current argument. If package is not specified script shows information for all packages.

1
2
[fafhrd@... MyProject]$ ../bin/ptah-migrate development.ini current ptah
Package 'ptah' rev: 0301(head) Migration to ptah 0.3

Use history argument to check migrations history.

1
2
3
4
5
[fafhrd@... MyProject]$ ../bin/ptah-migrate development.ini history ptah

ptah
====
0301: Ptah 0.3.0 changes

Data population

Data population process consists of populate steps. Each step can be marked as active or inactive. Active steps are executed by ptah_populate pyramid directive and ptah-populate -a `` command-line script. also it is possible to specify ``requires for each step. requires is a list of steps that should be executed before step.

Define step

Interface of populate step is very simple. It is function that accepts one argument pyramid registry ptah.interfaces.populate_step.

import ptah

@ptah.populate('custom-populate-step')
def populate_step(registry):
    ...

Check ptah.populate for detailed description of this directive.

Populate data during start up

Use ptah_populate() pyramid directive for populate system data during startup.

import ptah
from pyramid.config import Configurator

def main(global_settings, **settings):

    config = Configurator(settings=settings)
    config.include('ptah')

    ...

    config.ptah_populate()

    ...

    return config.make_wsgi_app()

Populate steps are executed after configration commited.

Command line script

Ptah providers ptah-populate command-line script for data population.

[fafhrd@... MyProject]$ ../bin/ptah-populate development.ini -a
...

Check Data population for detailed description of this script.

Data migration

Ptah migration based on alembic package. Ptah adds per package migration. Migration is not required alembic environment.

Create package migration

You can use alembic operations for ddl manipulation. Here are the steps for ptah migrations generation.

  • Create directory in your package that will contain migration steps.

For example:

1
2
$ cd ptah_minicms
$ mkdir migrations

So directory listing should look like this:

1
2
3
4
5
6
$ ls -l
drwxrwxr-x 2 fafhrd fafhrd 4096 2012-01-11 15:58 migrations
drwxrwxr-x 2 fafhrd fafhrd 4096 2011-12-16 11:33 static
drwxrwxr-x 2 fafhrd fafhrd 4096 2011-12-29 14:50 templates
-rw-rw-r-- 1 fafhrd fafhrd 1457 2011-12-29 14:50 actions.py
...
  • Register package migrations with ptah.register_migration() api.
1
2
3
4
5
import ptah

ptah.register_migration(
    'ptah_minicms', 'ptah_minicms:migrations',
    'Ptah minicms example migration')

First parameter is package name, second parameter is asset style path and third parameter migration title.

  • To create new revision you should use ptah-migrate script.
1
2
$ /bin/ptah-migrate settings.ini revision ptah_minicms -r 001 -m "Add column"
Generating /path-to-virtualenv/ptah_minicms/migrations/001.py...done

Generated script contains empty update() and downgrade() function. Add code that does migration from previous revision to this function.

  • Now you can use ptah-migrate script to execute migration steps.
1
2
$ bin/ptah-migrate settings.ini upgrade ptah_minicms
2012-01-11 16:14:42,657 INFO  [ptah.alembic] ptah_minicms: running upgrade None -> 001

Check Data migration chapter for ptah-migrate script detailed description.

Migration data during start up

Use ptah_migrate() pyramid directive for migration data schema during startup.

import ptah
from pyramid.config import Configurator

def main(global_settings, **settings):

    config = Configurator(settings=settings)
    config.include('ptah')

    ...

    config.ptah_migrate()

    ...

    return config.make_wsgi_app()

Migration steps are executed after configration commited.

Notes

  • Ptah stores package revision numbers in ptah_db_versions table. During data population process ptah checks if ptah_db_versions table contains version info, if it doesnt contain version information ptah just set latest revision without running migration steps. It assumes if there is no version information then database schema is latest.
  • ptah-migrate script executes POPULATE_DB_SCHEMA populate step before running any upgrade steps.

Ptah Q & A

What is scope of Ptah?

Ptah aims to provide a framework which makes low level choices for developers so the programmer can get on with solving their problems on a unrealistic deadline. ptah.cms is an API it is not an applictaion. The API does not have advanced CMS functionality such as staging, workflow or versioning. That is someone elses job.

Ptah is a framework, an implementation and set of opinions around the Pyramid web framework.

Ptah, like Pyramid, supports both URL dispatch, traversal. Unlike Pyramid it provides a data model, content heirarchy, form library, and high level security primitives (permissions, roles, and principals). Any of this is additional to Pyramid and augements your application.

Where does Pyramid and Ptah differ?

Ptah attempts to provide a “full stack” on top of Pyramid whereas Pyramid urges you to find and use individual libraries.

Why does Ptah not use deform?

Ptah does not use deform; but you can. Ptah ships with a form subsystem, ptah.form which you should give a shot.

Why does Ptah use a Folder paradigm?

Ptah does not require a Folder paradigm or containment. examples/ptah_minicms demonstrates the features of ptah.cms and one of those features are content hierarchies. Thus the Page/Folder experience in ptah_minicms.

Why does Ptah use sqlite?

Ptah uses SQLAlchemy which supports many different database drivers. sqlite ships with Python obviating the need to install a separate database daemon. ptah.cms will not depend on database specific features to gain performance or scalability.

SQLAlchemy is complex and scary

SQLAlchemy is a comprehensive library and an effect of that is it can feel overwhelming when reviewing the documentation. SQLAlchemy is the best thing we have in Python.

Traversal, wtf?

Traversal is not required. It is optional. It is a feature which can you use if you like. Traversal works quite well & if you have used Apache - you have used traversal - instead of a database Apache uses a filesystem.

Layout vs. Macros/Inheritance

Layouts are 100% optional. They provide an alternative for template inheritance. Layouts are renderer independent. This means you can use Jinja, Chameleon, & Mako with the Layout subsystem.

Several reasons exist for Layouts:

  • A layout is a view with a template. This provides a encapsulated template and view. Meaning a template which is used as a layout has its own view (methods, data, etc).
  • The contract between layouts is a string. Layouts can only be passed a content string which is the result of rendering the “inner” block.
  • Since layout’s work from the “inside-out” there is a desirable side-effect, by the time the <HEAD> layout is rendered all static assets (CSS, JS) that are requirements for form elements or for your custom view will have been computed. This is not possible to do generically with the ZPT/MACRO or the Jinja inheritance system.
  • Layouts can be context dependent. This is a advanced usage. It is unclear if this is possible with other template composition mechanisms. This is a feature you will most likely need or use or understand why it is needed.

Getting a pkg_resources.DistributionNotFound: myapp Exception

This means that you did not run python setup.py develop on your package. This is Python and you need to add your package to the python path/virtual environment. e.g.:

$ bin/pcreate -t template mypackage
$ cd mypackage
$ ../bin/python setup.py develop
$ ../bin/pserve settings.ini

Where did Paster Go?

ptah 0.1 used Pyramid 1.2 and Paster. Pyramid 1.3 removed the dependency on Paster and rolled the functionality directly into the pyramid framework. pcreate and pserver are scripts which are now generated by Pyramid.

(OperationalError) no such table:

Previously to ptah 0.3; all of the tables were created automatically without intervention. As of 0.3 you explicit must bootstrap your schema using ptah-populate script:

$ virtualenv/bin/ptah-populate settings.ini

The ptah-populate script takes the .INI settings file as a parameter. Once this completes you will be able to start your application. For details check Data population chapter.

API

ptah

URI

Layout

Settings

ACL

ACLs

ACLs dictionary contains all registered acl maps in the system.

Roles

Everyone
Authenticated
Owner

Permissions

DEFAULT_ACL
NOT_ALLOWED
NO_PERMISSION_REQUIRED

Security

auth_service

Instance of ptah.authentication.Authentication class.

SUPERUSER_URI

System user uri. Permission check always passes for user user. It is possible to use it as effective user:

ptah.auth_service.set_effective_user(ptah.SUPERUSER_URI)

This allow to pass security checks for any user.

Password utils

pwd_tool

Instance of ptah.password.PasswordTool class

Utilities

tldata

Status messages

UI Actions

Data population

POPULATE_DB_SCHEMA

Id for database schema creation step. Use it as requires dependency to make sure that db schema is cerated before execute any other steps.

Data migration

Events

Settings events

Content events

Principal events

Populate db schema

ptah.cms

Content classes

Content loading

Type system

Application Root/Factory/Policy

Blob api

blobStorage
class IBlob
class IBlobStorage

Content schema

class ContentSchema
class ContentNameSchema

Permissions

View
AddContent
DeleteContent
ModifyContent
ShareContent
NOT_ALLOWED
ALL_PERMISSIONS

ptah.form

Form

Field

Button

AC_DEFAULT
AC_PRIMARY
AC_DANGER
AC_SUCCESS
AC_INFO

Vocabulary

Validators

Predefined fields

Any field can be create with two different way. Using field class:

field = ptah.form.TextField(
    'field',
    title='Text',
    description='Field description')

Or using field factory:

field = ptah.form.FieldFactory(
    'text', 'field',
    title='Text',
    description='Field description')

Pyramid directives

Pyramid Configuration directive from Ptah. An example:

auth_policy = AuthTktAuthenticationPolicy('secret')
session_factory = UnencryptedCookieSessionFactoryConfig('secret')

def main(global_config, **settings):
    """ This is your application startup."""

    config = Configurator(settings=settings,
                          session_factory = session_factory,
                          authentication_policy = auth_policy)
    config.include('ptah')

    config.ptah_init_settings()
    config.ptah_init_sql()

ptah_init_sql(prefix=’sqlalchemy.’)

This directive creates new SQLAlchemy engine and bind session and declarative base.

param prefix:INI settings prefix, default is sqlalchemy.

Example:

[app:ptah]
sqlalchemy.url = sqlite:///%(here)s/var/db.sqlite

ptah_init_settings(settings=None)

Initialize settings management system and load settings from system settings. Also it sends ptah.events.SettingsInitializing and then ptah.events.SettingsInitialized. By default it reads info from config.registry.settings. Its possible to pass custom settings as first parameter.

param settings:Custom settings
config = Configurator()
config.include('ptah')

# initialize ptah setting management system
config.ptah_initialize_settings()
..
config.ptah_initialize_settings({'ptah.managers': '["*"]'})

ptah_init_manage()

Initialize and enable ptah management subsystem.

param name:Management ui prefix. Default is ptah-manage.
param access_manager:
 Set custom access manager.
param managers:List of user logins with access rights to ptah management ui.
param manager_role:
 Specific role with access rights to ptah management ui.
param disable_modules:
 List of modules names to hide in manage ui
param enable_modules:
 List of modules names to enable in manage ui

Also it possible to enable and configure management subsystem with settings in ini file:

[app:ptah]

ptah.manage = "ptah-manage"
ptah.managers = ["*"]
ptah.manager_role = ...
ptah.disable_modules = ...

ptah_init_mailer(mailer)

Set mailer object. Mailer interface is compatible with repoze.sendmail and pyramid_mailer. By default stub mailer is beeing used.

param mailer:Mailer object

ptah_auth_checker(checker)

Register auth checker. Checker function interface ptah.interfaces.auth_checker

param checker:Checker function
config = Configurator()
config.include('ptah')

def my_checker(info):
    ...

config.ptah_auth_checker(my_checker)

ptah_auth_provider(name, provider)

Register auth provider. Authentication provider interface ptah.interfaces.AuthProvider

ptah_principal_searcher(name, searcher)

Register principal searcher function. Principal searcher function interface ptah.interfaces.principal_searcher()

ptah_uri_resolver(schema, resolver)

Register resolver for given schema. Resolver function interface ptah.interfaces.resolver()

param schema:uri schema
param resolver:Callable object that accept one parameter.
config = Configurator()
config.include('ptah')

def my_resolver(uri):
    ....

config.ptah_uri_resolver('custom-schema', my_resolver)

ptah_password_changer(schema, changer)

Register password changer function for specific user uri schema. Password changer interface ptah.intefaces.password_changer()

param schema:Principal uri schema.
param changer:Function
config = Configurator()
config.include('ptah')

config.ptah_password_changer('custom-schema', custom_changer)

ptah_layout(...)

Registers a layout.

param name:Layout name
param context:Specific context for this layout.
param root:Root object
param parent:A parent layout. None means no parent layout.
param renderer:A pyramid renderer
param route_name:
 A pyramid route_name. Apply layout only for specific route
param use_global_views:
 Apply layout to all routes. even is route doesnt use use_global_views.
param view:Layout implementation (same as for pyramid view)
config = Configurator()
config.include('ptah')

config.ptah_layout(
    'page', parent='page',
    renderer='ptah:template/page.pt')

config.add_view('
    index.html',
    wrapper=ptah.wrap_layout(),
    renderer = '...')

ptah_populate()

Execute active populate steps.

config = Configurator()
config.include('ptah')

config.ptah_populate()

ptah_populate_step()

Register populate step. Step interface ptah.interfaces.populate_step.

param name:Step name
param factory:Step callable factory
param title:Snippet context
param active:View implementation
param requires:List of step names that should be executed before this step.
config = Configurator()
config.include('ptah')

def create_db_schemas(registry):
    ...

config.ptah_populate_step('ptah-create-db-schemas',
    factory=create_db_schemas,
    title='Create db scehams', active=True, requires=())

ptah_migrate()

Execute all registered database migration scripts. Check Data migration chapter for detailed description.

config = Configurator()
config.include('ptah')

config.ptah_migrate()

Interfaces

Ptah interfaces

Form interfaces

Ptah settings

All .INI settings set in file are in JSON format. All examples in this document are given in JSON format. An example entry in .ini file:

[app:ptah]
ptah.managers = ["*"]
ptah.pwd_manager = "ssha"

All values passed inside of Pyramid configurator are in Python format:

config.ptah_init_manage(
    managers = ['*'],
    disable_modules = ['rest', 'introspect', 'apps', 'permissions'])

ptah.disable_modules

Hide Modules in Management UI. List of modules names to hide in manage ui. e.g.:

ptah.disable_modules = ["rest", "apps"]

ptah.enable_modules

Enable Modules in Management UI. List of modules names to enable in manage ui. e.g.:

ptah.enable_modules = ["rest", "apps"]

ptah.disable_models

Provides a mechanism to hide models in the Model Management UI. A list of models to hide in model manage ui. e.g.:

ptah.disable_models = ["link"]

ptah.email_from_name

Site admin name. Default is Site administrator. e.g.:

ptah.email_from_name = "Site Administrator"

ptah.email_from_address

Site admin email address. e.g.:

ptah.email_from_address = "no-reply@myapplication.com"

ptah.manage

Ptah manage id. Default value is ptah-manage. Also this value is being used for ptah management url http://localhost:6543/ptah-manage/... e.g.:

ptah.manage = "manage"

The Ptah Manage UI would then be available at http://localhost:6543/manage

ptah.manager_role

Specific role with access rights to ptah management ui.

ptah.managers

List of user logins with access rights to ptah management ui. Default value is empty string, ‘’ which means no one logins allowed. “*” allows all principals. Must be a list of strings, e.g.:

ptah.managers = ["userid"]

ptah.pwd_manager

Password manager (plain, ssha, ..)

ptah.pwd_min_length

Minimum length for password.

ptah.pwd_letters_digits

Use letters and digits in password. Boolean value.

ptah.pwd_letters_mixed_case

Use letters in mixed case. Boolean value.

ptah.secret

Authentication policy secret. The secret (a string) used for auth_tkt cookie signing. e.g.:

ptah.secret = "s3cr3t"

ptah.db_skip_tables

Do not create listed tables during data population process. e.g.:

ptah.db_skip_tables = ["ptah_nodes", "ptah_content"]

ptah.default_roles

List of default principal roles:

ptah.default_roles = ["role:Editor"]

License

This software is licensed under a Simplified BSD License. Read more http://en.wikipedia.org/wiki/BSD_licenses

Copyright (c) 2011, RUNYAGA, LLC. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY RUNYAGA, LLC. ‘’AS IS’’ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of RUNYAGA, LLC..

Subprojects

Indices and tables