1. angri
  2. sqlamp

Commits

angri  committed fe63391

[wip] let's allow users to easily change path field length. WARNING: declarative setup is broken!

  • Participants
  • Parent commits f491830
  • Branches default

Comments (0)

Files changed (1)

File sqlamp/__init__.py

View file
 
 ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 PATH_FIELD_LENGTH = 255
+STEP_LENGTH = 3
 
 
 if hasattr(sqlalchemy.exc, 'DontWrapMixin'):
                  path_field='mp_path',
                  depth_field='mp_depth',
                  tree_id_field='mp_tree_id',
-                 steplen=3):
+                 steplen=None,
+                 pathlen=None):
 
         self.table = table
 
-        self.steplen = steplen
-
-        self.max_children = len(ALPHABET) ** steplen
-        self.max_depth = (PATH_FIELD_LENGTH // steplen) + 1
+        if steplen is not None:
+            self.steplen = steplen
+        else:
+            self.steplen = STEP_LENGTH
+        # pathlen is set later, after creating a column object
 
         assert len(table.primary_key.columns) == 1, \
                "Composite primary keys not supported"
             assert parent_id_field.table is table
             self.parent_id_field = parent_id_field
 
-        def _check_field(name, field, type_):
+        def _check_field(name, field, type_, params=None):
             # Check field argument (one of `path_field`, `depth_field` and
             # `tree_id_field`), convert it from field name to `Column` object
             # if needed, create the column object if needed and check the
                 assert field.table is table
             elif field in table.columns:
                 field = table.columns[field]
+                if params:
+                    # User wants to use custom-made column object,
+                    # but at the same time provides a list of parameters
+                    # for column's type engine as if they want us
+                    # to create it. It doesn't make any sense, because
+                    # type is already created and most likely the column
+                    # with parameters provided there already exists
+                    # in the database. Anyway, if values are consistent,
+                    # we won't complain.
+                    actual_params = dict((k, getattr(field.type, k))
+                                         for k in params)
+                    assert actual_params == params, \
+                           "Can not change parameters from %r to %r " \
+                           "on field %s" % (actual_params, params, name)
             else:
-                field = sqlalchemy.Column(field, type_(), nullable=False)
+                field = sqlalchemy.Column(field, type_(**(params or {})),
+                                          nullable=False)
                 table.append_column(field)
                 return field
             assert isinstance(field.type, type_), \
                    "The %s field should not be nullable" % name
             return field
 
-        self.path_field = _check_field('path', path_field, PathField)
+        # If path length was not provided, we omit passing it to checker
+        # function and let :class:`PathField` set the default length.
+        path_params = {}
+        if pathlen is not None:
+            path_params = {'length': pathlen}
+
+        self.path_field = _check_field('path', path_field,
+                                       PathField, path_params)
         self.depth_field = _check_field('depth', depth_field, DepthField)
         self.tree_id_field = _check_field('tree_id', tree_id_field, TreeIdField)
         self.fields = (self.path_field, self.depth_field, self.tree_id_field)
 
+        # Getting path length from the actual column length, no matter if
+        # we're dealing with custom path field object, or just created one.
+        self.pathlen = self.path_field.type.length
+        self.max_children = len(ALPHABET) ** self.steplen
+        self.max_depth = (self.pathlen // self.steplen) + 1
+
         self.indices = [
             sqlalchemy.Index(
                 '__'.join((table.name, self.tree_id_field.name,
                     # transform exception `PathOverflowError`, raised by
                     # `inc_path()` to more convenient `TooManyChildrenError`.
                     raise TooManyChildrenError()
-            if len(path) > PATH_FIELD_LENGTH:
+            if len(path) > opts.pathlen:
                 raise PathTooDeepError()
             self._query_result = dict(
                 path=path, depth=query['depth'], tree_id=query['tree_id']
 class PathField(sqlalchemy.types.TypeDecorator):
     "Varchar field subtype representing node's path."
     impl = sqlalchemy.String
-    def __init__(self):
-        super(PathField, self).__init__(PATH_FIELD_LENGTH)
+    def __init__(self, length=None):
+        if length is None:
+            length = PATH_FIELD_LENGTH
+        super(PathField, self).__init__(length)
     def process_bind_param(self, value, dialect):
         if not isinstance(value, _InsertionsParamsSelector):
             return value
         the same as for :attr:`path_field`, except that the type of this
         column should be :class:`TreeIdField`.
 
+    :param pathlen=255:
+        an integer, the length for path field.
+
     :param steplen=3:
         an integer, the number of characters in each part of the path.
-        This value allows to fine-tune the limits for max tree depth
-        (equal to `(255 / steplen) + 1`) and max children in each node
-        (`36 ** steplen`). Default `3` value sets the following limits:
-        max depth is `86` and max children number is `46656`.
+        This value along with `pathlen` allow to fine-tune the limits for
+        max tree depth (equal to `(pathlen / steplen) + 1`) and max children
+        in each node (`36 ** steplen`). Default values of `3` and `255`
+        set the following limits: max depth is `86` and max children number
+        is `46656`.
 
     :param instance_manager_key='_mp_instance_manager':
         name for node instance's attribute to cache node's instance