diff --git a/CHANGELOG.md b/CHANGELOG.md index feb3e0f..c8b4a03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,16 +5,6 @@ All notable changes to WuttJamaican will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## v0.7.0 (2024-07-14) - -### Feat - -- add basic "auth" data models: user/role/perm - -### Fix - -- always use 'wutta' prefix for provider entry points - ## v0.6.1 (2024-07-12) ### Fix diff --git a/docs/api/wuttjamaican/auth.rst b/docs/api/wuttjamaican/auth.rst deleted file mode 100644 index b2e7382..0000000 --- a/docs/api/wuttjamaican/auth.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``wuttjamaican.auth`` -===================== - -.. automodule:: wuttjamaican.auth - :members: diff --git a/docs/api/wuttjamaican/db.model.auth.rst b/docs/api/wuttjamaican/db.model.auth.rst deleted file mode 100644 index fdb1da9..0000000 --- a/docs/api/wuttjamaican/db.model.auth.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``wuttjamaican.db.model.auth`` -============================== - -.. automodule:: wuttjamaican.db.model.auth - :members: diff --git a/docs/api/wuttjamaican/index.rst b/docs/api/wuttjamaican/index.rst index ddc050c..452a183 100644 --- a/docs/api/wuttjamaican/index.rst +++ b/docs/api/wuttjamaican/index.rst @@ -8,12 +8,10 @@ :maxdepth: 1 app - auth conf db db.conf db.model - db.model.auth db.model.base db.sess exc diff --git a/docs/glossary.rst b/docs/glossary.rst index 2c9863a..6845af9 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -55,12 +55,6 @@ Glossary See also the code-friendly :term:`app name`. - auth handler - A :term:`handler` responsible for user authentication and - authorization (login, permissions) and related things. - - See also :class:`~wuttjamaican.auth.AuthHandler`. - command A top-level command line interface for the app. Note that top-level commands don't usually "do" anything per se, and are @@ -77,9 +71,6 @@ Glossary happens is, a config object is created and then extended by each of the registered config extensions. - The intention is that all config extensions will have been - applied before the :term:`app handler` is created. - config file A file which contains :term:`config settings<config setting>`. See also :doc:`narr/config/files`. diff --git a/pyproject.toml b/pyproject.toml index 8809b13..79b3ff1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "WuttJamaican" -version = "0.7.0" +version = "0.6.1" description = "Base package for Wutta Framework" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] @@ -38,9 +38,9 @@ tests = ["pytest-cov", "tox"] [project.urls] -Homepage = "https://wuttaproject.org/" -Repository = "https://forgejo.wuttaproject.org/wutta/wuttjamaican" -Changelog = "https://forgejo.wuttaproject.org/wutta/wuttjamaican/src/branch/master/CHANGELOG.md" +Homepage = "https://rattailproject.org/" +Repository = "https://kallithea.rattailproject.org/rattail-project/wuttjamaican" +Changelog = "https://kallithea.rattailproject.org/rattail-project/wuttjamaican/files/master/CHANGELOG.md" [tool.commitizen] diff --git a/src/wuttjamaican/app.py b/src/wuttjamaican/app.py index f551be9..86a7b4c 100644 --- a/src/wuttjamaican/app.py +++ b/src/wuttjamaican/app.py @@ -105,6 +105,9 @@ class AppHandler: self.providers = self.get_all_providers() return self.providers + # if 'providers' not in self.__dict__: + # self.__dict__['providers'] = self.get_all_providers() + for provider in self.providers.values(): if hasattr(provider, name): return getattr(provider, name) @@ -118,22 +121,10 @@ class AppHandler: Note that you do not need to call this directly; instead just use :attr:`providers`. - The discovery logic is based on :term:`entry points<entry - point>` using the ``wutta.app.providers`` group. For instance - here is a sample entry point used by WuttaWeb (in its - ``pyproject.toml``): - - .. code-block:: toml - - [project.entry-points."wutta.app.providers"] - wuttaweb = "wuttaweb.app:WebAppProvider" - :returns: Dictionary keyed by entry point name; values are - :class:`AppProvider` instances. + :class:`AppProvider` *instances*. """ - # nb. must use 'wutta' and not self.appname prefix here, or - # else we can't find all providers with custom appname - providers = load_entry_points('wutta.app.providers') + providers = load_entry_points(f'{self.appname}.providers') for key in list(providers): providers[key] = providers[key](self.config) return providers @@ -287,7 +278,8 @@ class AppProvider: These can add arbitrary extra functionality to the main :term:`app handler`. See also :doc:`/narr/providers/app`. - :param config: The app :term:`config object`. + :param config: Config object for the app. This should be an + instance of :class:`~wuttjamaican.conf.WuttaConfig`. Instances have the following attributes: @@ -309,16 +301,7 @@ class AppProvider: config = config.config self.config = config - self.app = self.config.get_app() - - @property - def appname(self): - """ - The :term:`app name` for the current app. - - See also :attr:`AppHandler.appname`. - """ - return self.app.appname + self.app = config.get_app() class GenericHandler: @@ -335,12 +318,3 @@ class GenericHandler: def __init__(self, config, **kwargs): self.config = config self.app = self.config.get_app() - - @property - def appname(self): - """ - The :term:`app name` for the current app. - - See also :attr:`AppHandler.appname`. - """ - return self.app.appname diff --git a/src/wuttjamaican/auth.py b/src/wuttjamaican/auth.py deleted file mode 100644 index 90a314f..0000000 --- a/src/wuttjamaican/auth.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# WuttJamaican -- Base package for Wutta Framework -# Copyright © 2023-2024 Lance Edgar -# -# This file is part of Wutta Framework. -# -# Wutta Framework is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# Wutta Framework is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# Wutta Framework. If not, see <http://www.gnu.org/licenses/>. -# -################################################################################ -""" -Auth Handler - -This defines the default :term:`auth handler`. -""" - -from wuttjamaican.app import GenericHandler - - -class AuthHandler(GenericHandler): - """ - Base class and default implementation for the :term:`auth - handler`. - - This is responsible for "authentication and authorization" - for - instance: - - * create new users, roles - * grant/revoke role permissions - * determine which permissions a user has - * identify user from login credentials - """ diff --git a/src/wuttjamaican/db/model/__init__.py b/src/wuttjamaican/db/model/__init__.py index 66b36e5..aa6877d 100644 --- a/src/wuttjamaican/db/model/__init__.py +++ b/src/wuttjamaican/db/model/__init__.py @@ -21,21 +21,14 @@ # ################################################################################ """ -Data Models +WuttJamaican - database model -This is the default :term:`app model` module. +For convenience, from this ``wuttjamaican.db.model`` namespace you can +access the following: -The ``wuttjamaican.db.model`` namespace contains the following: - -* :func:`~wuttjamaican.db.model.base.uuid_column()` -* :func:`~wuttjamaican.db.model.base.uuid_fk_column()` * :class:`~wuttjamaican.db.model.base.Base` * :class:`~wuttjamaican.db.model.base.Setting` -* :class:`~wuttjamaican.db.model.auth.Role` -* :class:`~wuttjamaican.db.model.auth.Permission` -* :class:`~wuttjamaican.db.model.auth.User` -* :class:`~wuttjamaican.db.model.auth.UserRole` +* :func:`~wuttjamaican.db.model.base.uuid_column()` """ -from .base import Base, Setting, uuid_column, uuid_fk_column -from .auth import Role, Permission, User, UserRole +from .base import Base, uuid_column, Setting diff --git a/src/wuttjamaican/db/model/auth.py b/src/wuttjamaican/db/model/auth.py deleted file mode 100644 index 297e891..0000000 --- a/src/wuttjamaican/db/model/auth.py +++ /dev/null @@ -1,229 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# WuttJamaican -- Base package for Wutta Framework -# Copyright © 2023-2024 Lance Edgar -# -# This file is part of Wutta Framework. -# -# Wutta Framework is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# Wutta Framework is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# Wutta Framework. If not, see <http://www.gnu.org/licenses/>. -# -################################################################################ -""" -Auth Models - -The :term:`auth handler` is primarily responsible for managing the -data for these models. - -Basic design/structure is as follows: - -* :class:`User` may be assigned to multiple roles -* :class:`Role` may contain multiple users (cf. :class:`UserRole`) -* :class:`Role` may be granted multiple permissions -* :class:`Permission` is a permission granted to a role -* roles are not nested/grouped; each is independent -* a few roles are built-in, e.g. Administrators - -So a user's permissions are "inherited" from the role(s) to which they -belong. -""" - -import sqlalchemy as sa -from sqlalchemy import orm -from sqlalchemy.ext.associationproxy import association_proxy - -from .base import Base, uuid_column, uuid_fk_column - - -class Role(Base): - """ - Represents an authentication role within the system; used for - permission management. - - .. attribute:: permissions - - List of keys (string names) for permissions granted to this - role. - - See also :attr:`permission_refs`. - - .. attribute:: users - - List of :class:`User` instances belonging to this role. - - See also :attr:`user_refs`. - """ - __tablename__ = 'role' - __table_args__ = ( - sa.UniqueConstraint('name', - # TODO - # name='role_uq_name', - ), - ) - - uuid = uuid_column() - - name = sa.Column(sa.String(length=100), nullable=False, doc=""" - Name for the role. Each role must have a name, which must be - unique. - """) - - notes = sa.Column(sa.Text(), nullable=True, doc=""" - Arbitrary notes for the role. - """) - - permission_refs = orm.relationship( - 'Permission', - back_populates='role', - doc=""" - List of :class:`Permission` references for the role. - - See also :attr:`permissions`. - """) - - permissions = association_proxy( - 'permission_refs', 'permission', - creator=lambda p: Permission(permission=p), - # TODO - # getset_factory=getset_factory, - ) - - user_refs = orm.relationship( - 'UserRole', - # TODO - # cascade='all, delete-orphan', - # cascade_backrefs=False, - back_populates='role', - doc=""" - List of :class:`UserRole` instances belonging to the role. - - See also :attr:`users`. - """) - - users = association_proxy( - 'user_refs', 'user', - creator=lambda u: UserRole(user=u), - # TODO - # getset_factory=getset_factory, - ) - - def __str__(self): - return self.name or "" - - -class Permission(Base): - """ - Represents a permission granted to a role. - """ - __tablename__ = 'permission' - __table_args__ = ( - sa.ForeignKeyConstraint(['role_uuid'], ['role.uuid'], - # TODO - # name='permission_fk_role', - ), - ) - - role_uuid = uuid_fk_column(primary_key=True, nullable=False) - role = orm.relationship( - Role, - back_populates='permission_refs', - doc=""" - Reference to the :class:`Role` for which the permission is - granted. - """) - - permission = sa.Column(sa.String(length=254), primary_key=True, doc=""" - Key (name) of the permission which is granted. - """) - - def __str__(self): - return self.permission or "" - - -class User(Base): - """ - Represents a user of the system. - - This may or may not correspond to a real person, i.e. some users - may exist solely for automated tasks. - """ - __tablename__ = 'user' - __table_args__ = ( - sa.UniqueConstraint('username', - # TODO - # name='user_uq_username', - ), - ) - - uuid = uuid_column() - - username = sa.Column(sa.String(length=25), nullable=False, doc=""" - Account username. This is required and must be unique. - """) - - password = sa.Column(sa.String(length=60), nullable=True, doc=""" - Hashed password for login. (The raw password is not stored.) - """) - - active = sa.Column(sa.Boolean(), nullable=False, default=True, doc=""" - Flag indicating whether the user account is "active" - it is - ``True`` by default. - - The default auth logic will prevent login for "inactive" user accounts. - """) - - role_refs = orm.relationship( - 'UserRole', - back_populates='user', - doc=""" - List of :class:`UserRole` records. - """) - - def __str__(self): - return self.username or "" - - -class UserRole(Base): - """ - Represents the association between a user and a role. - """ - __tablename__ = 'user_x_role' - __table_args__ = ( - sa.ForeignKeyConstraint(['user_uuid'], ['user.uuid'], - # TODO - # name='user_x_role_fk_user', - ), - sa.ForeignKeyConstraint(['role_uuid'], ['role.uuid'], - # TODO - # name='user_x_role_fk_role', - ), - ) - - uuid = uuid_column() - - user_uuid = uuid_fk_column(nullable=False) - user = orm.relationship( - User, - back_populates='role_refs', - doc=""" - Reference to the :class:`User` involved. - """) - - role_uuid = uuid_fk_column(nullable=False) - role = orm.relationship( - Role, - back_populates='user_refs', - doc=""" - Reference to the :class:`Role` involved. - """) diff --git a/src/wuttjamaican/db/model/base.py b/src/wuttjamaican/db/model/base.py index 5de65ca..45a7eee 100644 --- a/src/wuttjamaican/db/model/base.py +++ b/src/wuttjamaican/db/model/base.py @@ -21,7 +21,7 @@ # ################################################################################ """ -Base Models +WuttJamaican - base models .. class:: Base @@ -47,13 +47,6 @@ def uuid_column(*args, **kwargs): return sa.Column(sa.String(length=32), *args, **kwargs) -def uuid_fk_column(*args, **kwargs): - """ - Returns a UUID column for use as a foreign key to another table. - """ - return sa.Column(sa.String(length=32), *args, **kwargs) - - class Setting(Base): """ Represents a :term:`config setting`. diff --git a/tasks.py b/tasks.py index 9f2850b..a3ce31e 100644 --- a/tasks.py +++ b/tasks.py @@ -30,7 +30,7 @@ def release(c, skip_tests=False): Release a new version of WuttJamaican """ if not skip_tests: - c.run('pytest') + c.run('tox') # rebuild local tar.gz file for distribution if os.path.exists('WuttJamaican.egg-info'): diff --git a/tests/db/model/test_auth.py b/tests/db/model/test_auth.py deleted file mode 100644 index 29ba802..0000000 --- a/tests/db/model/test_auth.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8; -*- - -from unittest import TestCase - -try: - import sqlalchemy as sa - from wuttjamaican.db.model import auth as model -except ImportError: - pass -else: - - class TestRole(TestCase): - - def test_basic(self): - role = model.Role() - self.assertEqual(str(role), "") - role.name = "Managers" - self.assertEqual(str(role), "Managers") - - - class TestPermission(TestCase): - - def test_basic(self): - perm = model.Permission() - self.assertEqual(str(perm), "") - perm.permission = 'users.create' - self.assertEqual(str(perm), "users.create") - - - class TestUser(TestCase): - - def test_basic(self): - user = model.User() - self.assertEqual(str(user), "") - user.username = 'barney' - self.assertEqual(str(user), "barney") diff --git a/tests/db/model/test_base.py b/tests/db/model/test_base.py index aa27702..646e330 100644 --- a/tests/db/model/test_base.py +++ b/tests/db/model/test_base.py @@ -14,16 +14,7 @@ else: def test_basic(self): column = model.uuid_column() self.assertIsInstance(column, sa.Column) - self.assertIsInstance(column.type, sa.String) - self.assertEqual(column.type.length, 32) - class TestUUIDFKColumn(TestCase): - - def test_basic(self): - column = model.uuid_column() - self.assertIsInstance(column, sa.Column) - self.assertIsInstance(column.type, sa.String) - self.assertEqual(column.type.length, 32) class TestSetting(TestCase): diff --git a/tests/test_app.py b/tests/test_app.py index ea1d4e4..30adf72 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -134,7 +134,6 @@ class TestAppProvider(TestCase): provider = app.AppProvider(self.config) self.assertIs(provider.config, self.config) self.assertIs(provider.app, self.app) - self.assertEqual(provider.appname, 'wuttatest') # but can pass app handler instead with warnings.catch_warnings(): @@ -156,7 +155,7 @@ class TestAppProvider(TestCase): # sanity check, we get *instances* back from this providers = self.app.get_all_providers() - load_entry_points.assert_called_once_with('wutta.app.providers') + load_entry_points.assert_called_once_with('wuttatest.providers') self.assertEqual(len(providers), 1) self.assertIn('fake', providers) self.assertIsInstance(providers['fake'], FakeProvider) @@ -213,4 +212,3 @@ class TestGenericHandler(TestCase): handler = app.GenericHandler(self.config) self.assertIs(handler.config, self.config) self.assertIs(handler.app, self.app) - self.assertEqual(handler.appname, 'wuttatest') diff --git a/tests/test_auth.py b/tests/test_auth.py deleted file mode 100644 index 6b51b66..0000000 --- a/tests/test_auth.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8; -*- - -from unittest import TestCase - -from wuttjamaican import auth as mod -from wuttjamaican.conf import WuttaConfig - - -class TestAuthHandler(TestCase): - - def setUp(self): - self.config = WuttaConfig() - self.app = self.config.get_app() - - def test_basic(self): - handler = mod.AuthHandler(self.config) - self.assertIs(handler.app, self.app)