Pylons

Apr 16, 2009 10:30:25 PM

Sharing beaker sessions with perl's catalyst

This module provides a namespace manager that will interact with how catalyst (http://www.catalystframework.org/) stores sessions in the database. It is totally transparent to catalyst. All you have to do is use the same cookie name, database tables, and serializer/deserializer. I don't use cPickle here because perl's Perl::Serialiser::Pickle doesn't seem to work anymore.

Here is an example how to install this namespace manager in your backend: (If you are using pylons, you can put this in your environment.py in the load_environment() function. Put this code at the top of the function.)

from beaker.exceptions import InvalidCacheBackendError
try:
  import beaker.cache
  import beaker.ext.catalyst as catalyst # Or wherever you put this module in your backend
  beaker.cache.clsmap['ext:catalystdatabase'] = catalyst.CatalystDatabaseNamespaceManager
except (InvalidCacheBackendError, SyntaxError), e:
  beaker.cache.clsmap['ext:catalystdatabase'] = e

Now in your beaker configuration, just use the type of 'ext:catalystdatabase'. Here is my configuration for beaker with pylons:

beaker.session.key = session
beaker.session.cookie_expires = True
beaker.session.timeout = 680
beaker.session.auto = True
beaker.session.type = ext:catalystdatabase
beaker.session.url = mysql://user:pass@localhost:3306/DATABASE
beaker.session.table_name = sessions

Warning

perl does not care whether things are integers or strings. So all of the integers in the serialized json will be strings. This means that you either need to make a json deserializer to convert strings to integers (probably slow), or just use int() anytime you use a value that is suppose to be an integer.

And the module...

# Catalyst compatability for beaker.
# Author: Kevin Darlington (no@binds.net)
# Creation date: 03/29/2009
# License: Public domain (http://creativecommons.org/licenses/publicdomain/)
import logging
import time
from datetime import datetime

from beaker.container import OpenResourceNamespaceManager, Container
from beaker.exceptions import InvalidCacheBackendError, MissingCacheParameter
from beaker.synchronization import file_synchronizer, null_synchronizer
from beaker.util import verify_directory, SyncDict

sa_version = None

log = logging.getLogger(__name__)

try:
  import sqlalchemy as sa
  import sqlalchemy.pool as pool
  from sqlalchemy import types
  sa_version = '0.3'
except ImportError:
  raise InvalidCacheBackendError("Database cache backend requires the 'sqlalchemy' library")

if not hasattr(sa, 'BoundMetaData'):
  sa_version = '0.4'

#-----------------------------------
class LoadsError(Exception):
  """An error that occurs when deserializing.
  """
  pass

#-----------------------------------
class DumpsError(Exception):
  """An error that occurs when serializing.
  """
  pass

#===================================
# FIXME: Is there a way to get the beaker session without
# querying the framework for it?
def get_timeout():
  from pylons import config
  return int(config['beaker.session.timeout'])

#===================================
# Modify the code in here for your own deserializer.
def loads(s):
  """Tries to deserialize a string. If it errors, it will
  raise a LoadsError.
  """
  from tpacemr.lib.util import json
  try:
    return json.loads(s)
  # If you change the deserializer, make it catch whatever exceptions it
  # may throw here.
  except ValueError, e:
    raise LoadsError(e)

#===================================
# Modify the code in here for your own serializer.
def dumps(obj):
  """Tries to serialize an object. If it errors, it will
  raise a DumpsError.
  """
  from tpacemr.lib.util import json
  try:
    return json.dumps(obj)
  # If you change the serializer, make it catch whatever exceptions it
  # may throw here.
  except TypeError, e:
    raise DumpsError(e)

#-----------------------------------
class CatalystDatabaseNamespaceManager(OpenResourceNamespaceManager):
  metadatas = SyncDict()
  tables = SyncDict()

  #===================================
  def __init__(self, namespace, url=None, sa_opts=None, optimistic=False,
               table_name='beaker_cache', data_dir=None, lock_dir=None,
               **params):
    """Creates a catalyst database namespace manager
    """
    OpenResourceNamespaceManager.__init__(self, namespace)

    if sa_opts is None:
      sa_opts = params

    if lock_dir:
      self.lock_dir = lock_dir
    elif data_dir:
      self.lock_dir = data_dir + "/container_db_lock"
    if self.lock_dir:
      verify_directory(self.lock_dir)

    # Check to see if the table's been created before
    url = url or sa_opts['sa.url']
    table_key = url + table_name
    def make_cache():
      # Check to see if we have a connection pool open already
      meta_key = url + table_name
      def make_meta():
        if sa_version == '0.3':
          if url.startswith('mysql') and not sa_opts:
            sa_opts['poolclass'] = pool.QueuePool
          engine = sa.create_engine(url, **sa_opts)
          meta = sa.BoundMetaData(engine)
        else:
          # SQLAlchemy pops the url, this ensures it sticks around
          # later
          sa_opts['sa.url'] = url
          engine = sa.engine_from_config(sa_opts, 'sa.')
          meta = sa.MetaData()
          meta.bind = engine
        return meta
      meta = CatalystDatabaseNamespaceManager.metadatas.get(meta_key, make_meta)
      # Create the table object and cache it now
      cache = sa.Table(table_name, meta,
                       sa.Column('id', types.String(72), primary_key=True),
                       sa.Column('session_data', types.Text()),
                       sa.Column('expires', types.Integer)
      )
      cache.create(checkfirst=True)
      return cache
    self.hash = {}
    self._is_new = False
    self.loaded = False
    self.cache = CatalystDatabaseNamespaceManager.tables.get(table_key, make_cache)

    self.id = 'session:%s' % self.namespace

  #===================================
  def get_access_lock(self):
    return null_synchronizer()

  #===================================
  def get_creation_lock(self, key):
    return file_synchronizer(
      identifier ="databasecontainer/funclock/%s" % self.namespace,
      lock_dir = self.lock_dir)

  #===================================
  def do_open(self, flags):
    # If we already loaded the data, don't bother loading it again
    if self.loaded:
      self.flags = flags
      return

    get_timeout()

    cache = self.cache
    result = sa.select([cache.c.session_data, cache.c.expires], cache.c.id==self.id).execute().fetchone()
    if not result:
      self._is_new = True
      self.hash = {}
    else:
      self._is_new = False

      try:
        data = loads(str(result['session_data']))
        data['_creation_time'] = data['__created']
        data['_accessed_time'] = result['expires'] - get_timeout()
        self.hash = {'session': data}
      except (IOError, OSError, EOFError, LoadsError):
        log.debug("Couln't load json data, creating new storage")
        self.hash = {}
        self._is_new = True

    self.flags = flags
    self.loaded = True

  #===================================
  def do_close(self):
    if self.flags is not None and (self.flags == 'c' or self.flags == 'w'):
      cache = self.cache

      if not self.hash:
        self.do_remove()
        return

      time_now = int(time.time())

      tmp_hash = self.hash['session']
      # This is the default __user_realm for the catalyst I use. You may
      # need to change it if you have something different.
      tmp_hash.setdefault('__user_realm', 'dbic')

      if '_creation_time' in tmp_hash:
        tmp_hash['__created'] = int(tmp_hash['_creation_time'])
        del tmp_hash['_creation_time']
      else:
        tmp_hash['__created'] = time_now
      tmp_hash['__updated'] = time_now

      if '_accessed_time' in tmp_hash:
        expires = int(tmp_hash['_accessed_time']) + get_timeout()
        del tmp_hash['_accessed_time']
      else:
        expires = time_now + get_timeout()

      data = dumps(tmp_hash)

      if self._is_new:
        cache.insert().execute(id=self.id, session_data=data, expires=expires)
        self._is_new = False
      else:
        cache.update(cache.c.id==self.id).execute(session_data=data, expires=expires)
    self.flags = None

  #===================================
  def do_remove(self):
    cache = self.cache
    cache.delete(cache.c.id==self.id).execute()
    self.hash = {}

    # We can retain the fact that we did a load attempt, but since the
    # file is gone this will be a new namespace should it be saved.
    self._is_new = True

  #===================================
  def __getitem__(self, key):
    return self.hash[key]

  #===================================
  def __contains__(self, key):
    return self.hash.has_key(key)

  #===================================
  def __setitem__(self, key, value):
    self.hash[key] = value

  #===================================
  def __delitem__(self, key):
    del self.hash[key]

  #===================================
  def keys(self):
    return self.hash.keys()

#-----------------------------------
class CatalystDatabaseContainer(Container):
  namespace_manager = CatalystDatabaseNamespaceManager

Comments (44)

xdyputixin
Jan 12, 2012 11:12:47 AM

The breville juice fountain elite 800jexl is popularly known as the Cadillac of juicers but does it really live up to its claims?

The breville 800jexl has an Italian made electronic motor that increases power when it’s under heavy use in order to maintain filter revolutions. This breville 800jexl juice fountain elite is also “smart” because it has the technological ability to regulate the power needed depending on what’s being juiced.

Andrew Miller
5 hours and 49 minutes ago

I wanted to say that it’s nice arts degree to know that someone else also mentioned legal studies school this as I had trouble finding the same Online Natural sciences degree info elsewhere management degree. This was the first place that told me the answer Computer Science school.

You must login before you can comment.

Powered by Pylons - Contact Administrators