Thomas Waldmann avatar Thomas Waldmann committed a1406ec

added some convenience functions to create backend mappings to storage package

create_simple_mapping('stores:fs:/some/path') creates a ns_mapping

added .from_uri(uri) class methods to build backends and stores from URIs

added dynamic loading of backend and store modules

Comments (0)

Files changed (11)

storage/__init__.py

                                    over key/value pairs
 """
 
+
+CONTENT, USERPROFILES, TRASH = 'content', 'userprofiles', 'trash'
+
+BACKENDS_PACKAGE = 'storage.backends'
+
+
+def backend_from_uri(uri):
+    """
+    create a backend instance for uri
+    """
+    backend_name_uri = uri.split(':', 1)
+    if len(backend_name_uri) != 2:
+        raise ValueError("malformed backend uri: %s" % backend_uri)
+    backend_name, backend_uri = backend_name_uri
+    module = __import__(BACKENDS_PACKAGE + '.' + backend_name, globals(), locals(), ['Backend', ])
+    return module.Backend.from_uri(backend_uri)
+
+
+def create_mapping(uri, mounts_acls):
+    namespace_mapping = [(mounts_acls[nsname][0],
+                          backend_from_uri(uri % dict(nsname=nsname)),
+                          mounts_acls[nsname][1])
+                         for nsname in mounts_acls]
+    # we need the longest mountpoints first, shortest last (-> '' is very last)
+    return sorted(namespace_mapping, key=lambda x: len(x[0]), reverse=True)
+
+
+def create_simple_mapping(uri='stores:fs:instance',
+                          content_acl=None, user_profile_acl=None, trash_acl=None):
+    """
+    When configuring storage, the admin needs to provide a namespace_mapping.
+    To ease creation of such a mapping, this function provides sane defaults
+    for different types of stores.
+    The admin can just call this function, pass a hint on what type of stores
+    he wants to use and a proper mapping is returned.
+
+    :params uri: '<backend_name>:<backend_uri>' (general form)
+                 backend_name must be a backend module name (e.g. stores)
+                 the backend_uri must have a %(nsname)s placeholder, it gets replaced
+                 by the CONTENT, USERPROFILES, TRASH strings and result is given to
+                 to that backend's constructor
+
+                 for the 'stores' backend, backend_uri looks like '<store_name>:<store_uri>'
+                 store_name must be a store module name (e.g. fs)
+                 the store_uri must have a %(kind)s placeholder, it gets replaced
+                 by 'meta' or 'data' and the result is given to that store's constructor
+
+                 e.g.:
+                 'stores:fs:/path/to/store/%(nsname)s/%(kind)s' will create a mapping
+                 using the 'stores' backend with 'fs' stores and everything will be stored
+                 to below /path/to/store/.
+    """
+    # if no acls are given, use something mostly harmless:
+    if not content_acl:
+        content_acl = dict(before=u'', default=u'All:read,write,create', after=u'', hierarchic=False)
+    if not user_profile_acl:
+        user_profile_acl = dict(before=u'All:', default=u'', after=u'', hierarchic=False)
+    if not trash_acl:
+        trash_acl = content_acl
+    mounts_acls = {
+        CONTENT: ('', content_acl),
+        TRASH: ('Trash', trash_acl),
+        USERPROFILES: ('UserProfile', user_profile_acl),
+    }
+    return create_mapping(uri, mounts_acls)
+

storage/backends/__init__.py

     """
     __metaclass__ = ABCMeta
 
+    @classmethod
+    @abstractmethod
+    def from_uri(cls, uri):
+        """
+        create an instance using the data given in uri
+        """
+
     @abstractmethod
     def open(self):
         """

storage/backends/fileserver.py

     """
     exposes part of the filesystem (read-only)
     """
+    @classmethod
+    def from_uri(cls, uri):
+        return cls(uri)
+
     def __init__(self, path):
         """
         :param path: base directory (all files/dirs below will be exposed)

storage/backends/stores.py

 except ImportError:
     import simplejson as json
 
+STORES_PACKAGE = 'storage.stores'
+
 
 class Backend(BackendBase):
     """
     ties together a store for metadata and a store for data, readonly
     """
+    @classmethod
+    def from_uri(cls, uri):
+        store_name_uri = uri.split(':', 1)
+        if len(store_name_uri) != 2:
+            raise ValueError("malformed store uri: %s" % uri)
+        store_name, store_uri = store_name_uri
+        module = __import__(STORES_PACKAGE + '.' + store_name, globals(), locals(), ['BytesStore', 'FileStore', ])
+        meta_store_uri = store_uri % dict(kind='meta')
+        data_store_uri = store_uri % dict(kind='data')
+        return cls(module.BytesStore(meta_store_uri), module.FileStore(data_store_uri))
+
     def __init__(self, meta_store, data_store):
         """
         :param meta_store: a ByteStore for metadata

storage/stores/__init__.py

     """
     A simple read-only key/value store.
     """
+    @classmethod
+    @abstractmethod
+    def from_uri(cls, uri):
+        """
+        return an instance constructed from the given uri
+        """
+
     def __init__(self, **kw):
         """
         lazy stuff - just remember pathes, urls, database name, ... -

storage/stores/fs.py

 
     keys are required to be valid filenames.
     """
+    @classmethod
+    def from_uri(cls, uri):
+        return cls(uri)
+
     def __init__(self, path):
         """
         :param path: base directory used for this store

storage/stores/kc.py

     """
     Kyoto cabinet based store.
     """
+    @classmethod
+    def from_uri(cls, uri):
+        return cls(uri)
+
     def __init__(self, path, mode=DB.OWRITER|DB.OAUTOTRAN, db_opts=DB.GCONCURRENT):
         """
         Store params for .open(). Please refer to kyotocabinet-python-legacy docs for more information.

storage/stores/kt.py

     """
     Kyoto tycoon based store.
     """
+    @classmethod
+    def from_uri(cls, uri):
+        return cls(uri)
+
     def __init__(self, host='127.0.0.1', port=1978, timeout=30):
         """
         Store params for .open().

storage/stores/memcached.py

     """
     A simple dict-based in-memory store. No persistence!
     """
+    @classmethod
+    def from_uri(cls, uri):
+        return cls([uri])
+
     def __init__(self, servers=['localhost:11211'], debug=0):
         """
         :param servers: list of memcached servers (default: ['localhost:11211'])

storage/stores/memory.py

     """
     A simple dict-based in-memory store. No persistence!
     """
+    @classmethod
+    def from_uri(cls, uri):
+        return cls()
+
     def __init__(self):
         self._st = None
 

storage/stores/sqlite.py

     """
     A simple sqlite3 based store.
     """
-    def __init__(self, db_name, table_name, compression_level=0):
+    @classmethod
+    def from_uri(cls, uri):
+        return cls(uri)
+
+    def __init__(self, db_name, table_name='store', compression_level=0):
         """
         Just store the params.
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.