Compare commits
No commits in common. "028c64fc12640387581c877e13705f77e284dbcd" and "d44fe152154b232c8c917288421d6a3691216c19" have entirely different histories.
028c64fc12
...
d44fe15215
|
@ -30,7 +30,7 @@ import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from wuttjamaican.util import (load_entry_points, load_object,
|
from wuttjamaican.util import (load_entry_points, load_object,
|
||||||
make_title, make_uuid, make_true_uuid,
|
make_title, make_uuid,
|
||||||
progress_loop, resource_path)
|
progress_loop, resource_path)
|
||||||
|
|
||||||
|
|
||||||
|
@ -491,44 +491,14 @@ class AppHandler:
|
||||||
"""
|
"""
|
||||||
return make_title(text)
|
return make_title(text)
|
||||||
|
|
||||||
def make_true_uuid(self):
|
|
||||||
"""
|
|
||||||
Generate a new v7 UUID value.
|
|
||||||
|
|
||||||
By default this simply calls
|
|
||||||
:func:`wuttjamaican.util.make_true_uuid()`.
|
|
||||||
|
|
||||||
:returns: :class:`python:uuid.UUID` instance
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
For now, callers should use this method when they want a
|
|
||||||
proper UUID instance, whereas :meth:`make_uuid()` will
|
|
||||||
always return a string.
|
|
||||||
|
|
||||||
However once all dependent logic has been refactored to
|
|
||||||
support proper UUID data type, then ``make_uuid()`` will
|
|
||||||
return those and this method will eventually be removed.
|
|
||||||
"""
|
|
||||||
return make_true_uuid()
|
|
||||||
|
|
||||||
def make_uuid(self):
|
def make_uuid(self):
|
||||||
"""
|
"""
|
||||||
Generate a new v7 UUID value.
|
Generate a new UUID value.
|
||||||
|
|
||||||
By default this simply calls
|
By default this simply calls
|
||||||
:func:`wuttjamaican.util.make_uuid()`.
|
:func:`wuttjamaican.util.make_uuid()`.
|
||||||
|
|
||||||
:returns: UUID value as 32-character string.
|
:returns: UUID value as 32-character string.
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
For now, this method always returns a string.
|
|
||||||
|
|
||||||
However once all dependent logic has been refactored to
|
|
||||||
support proper UUID data type, then this method will return
|
|
||||||
those and the :meth:`make_true_uuid()` method will
|
|
||||||
eventually be removed.
|
|
||||||
"""
|
"""
|
||||||
return make_uuid()
|
return make_uuid()
|
||||||
|
|
||||||
|
|
|
@ -40,5 +40,5 @@ def make_uuid(
|
||||||
"""
|
"""
|
||||||
config = ctx.parent.wutta_config
|
config = ctx.parent.wutta_config
|
||||||
app = config.get_app()
|
app = config.get_app()
|
||||||
uuid = app.make_true_uuid()
|
uuid = app.make_uuid()
|
||||||
sys.stdout.write(f"{uuid}\n")
|
sys.stdout.write(f"{uuid}\n")
|
||||||
|
|
|
@ -9,7 +9,6 @@ from typing import Sequence, Union
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
import wuttjamaican.db.util
|
|
||||||
${imports if imports else ""}
|
${imports if imports else ""}
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
"""convert uuid types
|
|
||||||
|
|
||||||
Revision ID: 6be0ed225f4d
|
|
||||||
Revises: 6bf900765500
|
|
||||||
Create Date: 2024-11-30 17:03:08.930050
|
|
||||||
|
|
||||||
"""
|
|
||||||
import uuid as _uuid
|
|
||||||
from typing import Sequence, Union
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
import wuttjamaican.db.util
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision: str = '6be0ed225f4d'
|
|
||||||
down_revision: Union[str, None] = '6bf900765500'
|
|
||||||
branch_labels: Union[str, Sequence[str], None] = None
|
|
||||||
depends_on: Union[str, Sequence[str], None] = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
|
|
||||||
# upgrade (convert uuid)
|
|
||||||
op.add_column('upgrade', sa.Column('uuid_true', wuttjamaican.db.util.UUID(), nullable=True))
|
|
||||||
upgrade = sa.sql.table('upgrade',
|
|
||||||
sa.sql.column('uuid'),
|
|
||||||
sa.sql.column('uuid_true'))
|
|
||||||
|
|
||||||
engine = op.get_bind()
|
|
||||||
cursor = engine.execute(upgrade.select())
|
|
||||||
for row in cursor.fetchall():
|
|
||||||
if row['uuid']:
|
|
||||||
uuid_true = _uuid.UUID(row['uuid'])
|
|
||||||
engine.execute(upgrade.update()\
|
|
||||||
.where(upgrade.c.uuid == row['uuid'])\
|
|
||||||
.values({'uuid_true': uuid_true}))
|
|
||||||
|
|
||||||
op.drop_constraint('pk_upgrade', 'upgrade', type_='primary')
|
|
||||||
op.drop_column('upgrade', 'uuid')
|
|
||||||
op.alter_column('upgrade', 'uuid_true', new_column_name='uuid')
|
|
||||||
op.create_primary_key('pk_upgrade', 'upgrade', ['uuid'])
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
|
|
||||||
# upgrade (convert uuid)
|
|
||||||
op.add_column('upgrade', sa.Column('uuid_str', sa.VARCHAR(length=32), nullable=True))
|
|
||||||
upgrade = sa.sql.table('upgrade',
|
|
||||||
sa.sql.column('uuid'),
|
|
||||||
sa.sql.column('uuid_str'))
|
|
||||||
|
|
||||||
engine = op.get_bind()
|
|
||||||
cursor = engine.execute(upgrade.select())
|
|
||||||
for row in cursor.fetchall():
|
|
||||||
if row['uuid']:
|
|
||||||
uuid_str = row['uuid'].hex
|
|
||||||
engine.execute(upgrade.update()\
|
|
||||||
.where(upgrade.c.uuid == row['uuid'])\
|
|
||||||
.values({'uuid_str': uuid_str}))
|
|
||||||
|
|
||||||
op.drop_constraint('pk_upgrade', 'upgrade', type_='primary')
|
|
||||||
op.drop_column('upgrade', 'uuid')
|
|
||||||
op.alter_column('upgrade', 'uuid_str', new_column_name='uuid')
|
|
||||||
op.create_primary_key('pk_upgrade', 'upgrade', ['uuid'])
|
|
|
@ -31,8 +31,6 @@ from sqlalchemy import orm
|
||||||
|
|
||||||
from .base import Base, uuid_column, uuid_fk_column
|
from .base import Base, uuid_column, uuid_fk_column
|
||||||
from wuttjamaican.enum import UpgradeStatus
|
from wuttjamaican.enum import UpgradeStatus
|
||||||
from wuttjamaican.db.util import UUID
|
|
||||||
from wuttjamaican.util import make_true_uuid
|
|
||||||
|
|
||||||
|
|
||||||
class Upgrade(Base):
|
class Upgrade(Base):
|
||||||
|
@ -41,7 +39,7 @@ class Upgrade(Base):
|
||||||
"""
|
"""
|
||||||
__tablename__ = 'upgrade'
|
__tablename__ = 'upgrade'
|
||||||
|
|
||||||
uuid = uuid_column(UUID(), default=make_true_uuid)
|
uuid = uuid_column()
|
||||||
|
|
||||||
created = sa.Column(sa.DateTime(timezone=True), nullable=False,
|
created = sa.Column(sa.DateTime(timezone=True), nullable=False,
|
||||||
default=datetime.datetime.now, doc="""
|
default=datetime.datetime.now, doc="""
|
||||||
|
|
|
@ -24,10 +24,7 @@
|
||||||
Database Utilities
|
Database Utilities
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import uuid as _uuid
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy.dialects.postgresql import UUID as PGUUID
|
|
||||||
|
|
||||||
from wuttjamaican.util import make_uuid
|
from wuttjamaican.util import make_uuid
|
||||||
|
|
||||||
|
@ -60,61 +57,14 @@ class ModelBase:
|
||||||
return getattr(self, key)
|
return getattr(self, key)
|
||||||
|
|
||||||
|
|
||||||
class UUID(sa.types.TypeDecorator):
|
|
||||||
"""
|
|
||||||
Platform-independent UUID type.
|
|
||||||
|
|
||||||
Uses PostgreSQL's UUID type, otherwise uses CHAR(32), storing as
|
|
||||||
stringified hex values.
|
|
||||||
|
|
||||||
This type definition is based on example from the `SQLAlchemy
|
|
||||||
documentation
|
|
||||||
<https://docs.sqlalchemy.org/en/14/core/custom_types.html#backend-agnostic-guid-type>`_.
|
|
||||||
"""
|
|
||||||
impl = sa.CHAR
|
|
||||||
cache_ok = True
|
|
||||||
""" """ # nb. suppress sphinx autodoc for cache_ok
|
|
||||||
|
|
||||||
def load_dialect_impl(self, dialect):
|
|
||||||
""" """
|
|
||||||
if dialect.name == "postgresql":
|
|
||||||
return dialect.type_descriptor(PGUUID())
|
|
||||||
else:
|
|
||||||
return dialect.type_descriptor(sa.CHAR(32))
|
|
||||||
|
|
||||||
def process_bind_param(self, value, dialect):
|
|
||||||
""" """
|
|
||||||
if value is None:
|
|
||||||
return value
|
|
||||||
elif dialect.name == "postgresql":
|
|
||||||
return str(value)
|
|
||||||
else:
|
|
||||||
if not isinstance(value, _uuid.UUID):
|
|
||||||
return "%.32x" % _uuid.UUID(value).int
|
|
||||||
else:
|
|
||||||
# hexstring
|
|
||||||
return "%.32x" % value.int
|
|
||||||
|
|
||||||
def process_result_value(self, value, dialect):
|
|
||||||
""" """
|
|
||||||
if value is None:
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
if not isinstance(value, _uuid.UUID):
|
|
||||||
value = _uuid.UUID(value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def uuid_column(*args, **kwargs):
|
def uuid_column(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns a UUID column for use as a table's primary key.
|
Returns a UUID column for use as a table's primary key.
|
||||||
"""
|
"""
|
||||||
if not args:
|
|
||||||
args = (sa.String(length=32),)
|
|
||||||
kwargs.setdefault('primary_key', True)
|
kwargs.setdefault('primary_key', True)
|
||||||
kwargs.setdefault('nullable', False)
|
kwargs.setdefault('nullable', False)
|
||||||
kwargs.setdefault('default', make_uuid)
|
kwargs.setdefault('default', make_uuid)
|
||||||
return sa.Column(*args, **kwargs)
|
return sa.Column(sa.String(length=32), *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def uuid_fk_column(target_column, *args, **kwargs):
|
def uuid_fk_column(target_column, *args, **kwargs):
|
||||||
|
@ -124,6 +74,4 @@ def uuid_fk_column(target_column, *args, **kwargs):
|
||||||
:param target_column: Name of the table column on the remote side,
|
:param target_column: Name of the table column on the remote side,
|
||||||
e.g. ``'user.uuid'``.
|
e.g. ``'user.uuid'``.
|
||||||
"""
|
"""
|
||||||
if not args:
|
return sa.Column(sa.String(length=32), sa.ForeignKey(target_column), *args, **kwargs)
|
||||||
args = (sa.String(length=32), sa.ForeignKey(target_column))
|
|
||||||
return sa.Column(*args, **kwargs)
|
|
||||||
|
|
|
@ -124,7 +124,7 @@ class InstallHandler(GenericHandler):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
paths.insert(0, self.app.resource_path(f'{self.pkg_name}:templates/install'))
|
paths.insert(0, self.app.resource_path(f'{self.pkg_name}:templates/install'))
|
||||||
except (TypeError, ModuleNotFoundError):
|
except ModuleNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.templates = TemplateLookup(directories=paths)
|
self.templates = TemplateLookup(directories=paths)
|
||||||
|
|
|
@ -171,43 +171,13 @@ def make_title(text):
|
||||||
return ' '.join([x.capitalize() for x in words])
|
return ' '.join([x.capitalize() for x in words])
|
||||||
|
|
||||||
|
|
||||||
def make_true_uuid():
|
|
||||||
"""
|
|
||||||
Generate a new v7 UUID value.
|
|
||||||
|
|
||||||
:returns: :class:`python:uuid.UUID` instance
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
For now, callers should use this function when they want a
|
|
||||||
proper UUID instance, whereas :func:`make_uuid()` will always
|
|
||||||
return a string.
|
|
||||||
|
|
||||||
However once all dependent logic has been refactored to support
|
|
||||||
proper UUID data type, then ``make_uuid()`` will return those
|
|
||||||
and this function will eventually be removed.
|
|
||||||
"""
|
|
||||||
return uuid7()
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: deprecate this logic, and reclaim this name
|
|
||||||
# but using the above logic
|
|
||||||
def make_uuid():
|
def make_uuid():
|
||||||
"""
|
"""
|
||||||
Generate a new v7 UUID value.
|
Generate a universally-unique identifier.
|
||||||
|
|
||||||
:returns: A 32-character hex string.
|
:returns: A 32-character hex string.
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
For now, this function always returns a string.
|
|
||||||
|
|
||||||
However once all dependent logic has been refactored to support
|
|
||||||
proper UUID data type, then this function will return those and
|
|
||||||
the :func:`make_true_uuid()` function will eventually be
|
|
||||||
removed.
|
|
||||||
"""
|
"""
|
||||||
return make_true_uuid().hex
|
return uuid7().hex
|
||||||
|
|
||||||
|
|
||||||
def parse_bool(value):
|
def parse_bool(value):
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
# -*- coding: utf-8; -*-
|
# -*- coding: utf-8; -*-
|
||||||
|
|
||||||
import uuid as _uuid
|
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy.dialects.postgresql import UUID as PGUUID
|
|
||||||
from wuttjamaican.db import util as mod
|
from wuttjamaican.db import util as mod
|
||||||
from wuttjamaican.db.model.base import Setting
|
from wuttjamaican.db.model.base import Setting
|
||||||
from wuttjamaican.util import make_true_uuid
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
@ -26,88 +22,6 @@ else:
|
||||||
self.assertEqual(setting['name'], 'foo')
|
self.assertEqual(setting['name'], 'foo')
|
||||||
|
|
||||||
|
|
||||||
class TestUUID(TestCase):
|
|
||||||
|
|
||||||
def test_load_dialect_impl(self):
|
|
||||||
typ = mod.UUID()
|
|
||||||
dialect = MagicMock()
|
|
||||||
|
|
||||||
# TODO: this doesn't really test anything, but gives us
|
|
||||||
# coverage at least..
|
|
||||||
|
|
||||||
# postgres
|
|
||||||
dialect.name = 'postgresql'
|
|
||||||
dialect.type_descriptor.return_value = 42
|
|
||||||
result = typ.load_dialect_impl(dialect)
|
|
||||||
self.assertTrue(dialect.type_descriptor.called)
|
|
||||||
self.assertEqual(result, 42)
|
|
||||||
|
|
||||||
# other
|
|
||||||
dialect.name = 'mysql'
|
|
||||||
dialect.type_descriptor.return_value = 43
|
|
||||||
dialect.type_descriptor.reset_mock()
|
|
||||||
result = typ.load_dialect_impl(dialect)
|
|
||||||
self.assertTrue(dialect.type_descriptor.called)
|
|
||||||
self.assertEqual(result, 43)
|
|
||||||
|
|
||||||
def test_process_bind_param_postgres(self):
|
|
||||||
typ = mod.UUID()
|
|
||||||
dialect = MagicMock()
|
|
||||||
dialect.name = 'postgresql'
|
|
||||||
|
|
||||||
# null
|
|
||||||
result = typ.process_bind_param(None, dialect)
|
|
||||||
self.assertIsNone(result)
|
|
||||||
|
|
||||||
# string
|
|
||||||
uuid_str = make_true_uuid().hex
|
|
||||||
result = typ.process_bind_param(uuid_str, dialect)
|
|
||||||
self.assertEqual(result, uuid_str)
|
|
||||||
|
|
||||||
# uuid
|
|
||||||
uuid_true = make_true_uuid()
|
|
||||||
result = typ.process_bind_param(uuid_true, dialect)
|
|
||||||
self.assertEqual(result, str(uuid_true))
|
|
||||||
|
|
||||||
def test_process_bind_param_other(self):
|
|
||||||
typ = mod.UUID()
|
|
||||||
dialect = MagicMock()
|
|
||||||
dialect.name = 'mysql'
|
|
||||||
|
|
||||||
# null
|
|
||||||
result = typ.process_bind_param(None, dialect)
|
|
||||||
self.assertIsNone(result)
|
|
||||||
|
|
||||||
# string
|
|
||||||
uuid_str = make_true_uuid().hex
|
|
||||||
result = typ.process_bind_param(uuid_str, dialect)
|
|
||||||
self.assertEqual(result, uuid_str)
|
|
||||||
|
|
||||||
# uuid
|
|
||||||
uuid_true = make_true_uuid()
|
|
||||||
result = typ.process_bind_param(uuid_true, dialect)
|
|
||||||
self.assertEqual(result, uuid_true.hex)
|
|
||||||
|
|
||||||
def test_process_result_value(self):
|
|
||||||
typ = mod.UUID()
|
|
||||||
dialect = MagicMock()
|
|
||||||
|
|
||||||
# null
|
|
||||||
result = typ.process_result_value(None, dialect)
|
|
||||||
self.assertIsNone(result)
|
|
||||||
|
|
||||||
# string
|
|
||||||
uuid_str = make_true_uuid().hex
|
|
||||||
result = typ.process_result_value(uuid_str, dialect)
|
|
||||||
self.assertIsInstance(result, _uuid.UUID)
|
|
||||||
self.assertEqual(result.hex, uuid_str)
|
|
||||||
|
|
||||||
# uuid
|
|
||||||
uuid_true = make_true_uuid()
|
|
||||||
result = typ.process_result_value(uuid_true, dialect)
|
|
||||||
self.assertIs(result, uuid_true)
|
|
||||||
|
|
||||||
|
|
||||||
class TestUUIDColumn(TestCase):
|
class TestUUIDColumn(TestCase):
|
||||||
|
|
||||||
def test_basic(self):
|
def test_basic(self):
|
||||||
|
|
|
@ -5,7 +5,6 @@ import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import warnings
|
import warnings
|
||||||
import uuid as _uuid
|
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
@ -386,10 +385,6 @@ app_title = WuttaTest
|
||||||
uuid = self.app.make_uuid()
|
uuid = self.app.make_uuid()
|
||||||
self.assertEqual(len(uuid), 32)
|
self.assertEqual(len(uuid), 32)
|
||||||
|
|
||||||
def test_make_true_uuid(self):
|
|
||||||
uuid = self.app.make_true_uuid()
|
|
||||||
self.assertIsInstance(uuid, _uuid.UUID)
|
|
||||||
|
|
||||||
def test_progress_loop(self):
|
def test_progress_loop(self):
|
||||||
|
|
||||||
def act(obj, i):
|
def act(obj, i):
|
||||||
|
|
|
@ -74,15 +74,23 @@ class TestInstallHandler(ConfigTestCase):
|
||||||
'dburl': f'sqlite:///{self.tempdir}/poser.sqlite',
|
'dburl': f'sqlite:///{self.tempdir}/poser.sqlite',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
orig_import = __import__
|
||||||
|
mock_prompt = MagicMock()
|
||||||
|
|
||||||
|
def mock_import(name, globals=None, locals=None, fromlist=(), level=0):
|
||||||
|
if name == 'prompt_toolkit':
|
||||||
|
if fromlist == ('prompt',):
|
||||||
|
return MagicMock(prompt=mock_prompt)
|
||||||
|
return orig_import(name, globals, locals, fromlist, level)
|
||||||
|
|
||||||
|
with patch('builtins.__import__', side_effect=mock_import):
|
||||||
with patch.object(handler, 'get_dbinfo', return_value=dbinfo):
|
with patch.object(handler, 'get_dbinfo', return_value=dbinfo):
|
||||||
with patch.object(handler, 'make_appdir') as make_appdir:
|
|
||||||
with patch.object(handler, 'install_db_schema') as install_db_schema:
|
with patch.object(handler, 'install_db_schema') as install_db_schema:
|
||||||
|
|
||||||
# nb. just for sanity/coverage
|
# nb. just for sanity/coverage
|
||||||
install_db_schema.return_value = True
|
install_db_schema.return_value = True
|
||||||
self.assertFalse(hasattr(handler, 'schema_installed'))
|
self.assertFalse(hasattr(handler, 'schema_installed'))
|
||||||
handler.do_install_steps()
|
handler.do_install_steps()
|
||||||
self.assertTrue(make_appdir.called)
|
|
||||||
self.assertTrue(handler.schema_installed)
|
self.assertTrue(handler.schema_installed)
|
||||||
install_db_schema.assert_called_once_with(dbinfo['dburl'])
|
install_db_schema.assert_called_once_with(dbinfo['dburl'])
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue