diff options
| author | Michael Merickel <michael@merickel.org> | 2024-02-04 13:51:57 -0700 |
|---|---|---|
| committer | Michael Merickel <michael@merickel.org> | 2024-02-04 14:02:25 -0700 |
| commit | 28afd5d1e79d618ed0522029e926fd9219d31c0d (patch) | |
| tree | 26af9a7cb9f414b9aef15963605f0fa3e8175bca /docs | |
| parent | 3a1d24397616a47f75b63b6367a78975dc546526 (diff) | |
| download | pyramid-28afd5d1e79d618ed0522029e926fd9219d31c0d.tar.gz pyramid-28afd5d1e79d618ed0522029e926fd9219d31c0d.tar.bz2 pyramid-28afd5d1e79d618ed0522029e926fd9219d31c0d.zip | |
upgrade definingmodels chapter
Diffstat (limited to 'docs')
19 files changed, 213 insertions, 213 deletions
diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst index e419fdf0a..b9b02ed12 100644 --- a/docs/tutorials/wiki2/definingmodels.rst +++ b/docs/tutorials/wiki2/definingmodels.rst @@ -17,22 +17,21 @@ be to define a wiki page :term:`domain model`. but this is only a convention and not a requirement. -Declaring dependencies in our ``setup.py`` file -=============================================== +Declaring dependencies in our ``pyproject.toml`` file +===================================================== The models code in our application will depend on a package which is not a dependency of the original "tutorial" application. The original "tutorial" application was generated by the cookiecutter; it doesn't know about our custom application requirements. -We need to add a dependency, the `bcrypt <https://pypi.org/project/bcrypt/>`_ package, to our ``tutorial`` -package's ``setup.py`` file by assigning this dependency to the ``requires`` -parameter in the ``setup()`` function. +We need to add a dependency, the `bcrypt <https://pypi.org/project/bcrypt/>`_ package, to our ``tutorial`` package's ``pyproject.toml`` file. +Dependencies are defined via the ``dependencies`` key in the ``[project]`` section. -Open ``tutorial/setup.py`` and edit it to look like the following by adding ``bcrypt`` and sorting the packages: +Open ``tutorial/pyproject.toml`` and edit it to look like the following by adding ``bcrypt`` and sorting the packages: -.. literalinclude:: src/models/setup.py - :lines: 11-24 +.. literalinclude:: src/models/pyproject.toml + :lines: 20-33 :linenos: :lineno-match: :emphasize-lines: 3 @@ -56,7 +55,7 @@ Since a new software dependency was added, you will need to run ``pip install the newly added dependency distribution. Make sure your current working directory is the root of the project (the -directory in which ``setup.py`` lives) and execute the following command. +directory in which ``pyproject.toml`` lives) and execute the following command. On Unix: @@ -75,7 +74,7 @@ like the following. .. code-block:: text - Successfully installed bcrypt-3.2.0 cffi-1.14.4 pycparser-2.20 tutorial + Successfully installed bcrypt-4.1.2 tutorial-0.0 Remove ``mymodel.py`` @@ -96,19 +95,24 @@ Create a new file ``tutorial/models/user.py`` with the following contents: This is a very basic model for a user who can authenticate with our wiki. -We discussed briefly in the previous chapter that our models will inherit from -an SQLAlchemy :func:`sqlalchemy.ext.declarative.declarative_base`. This will -attach the model to our schema. +We discussed briefly in the previous chapter that our models will inherit from an SQLAlchemy :func:`sqlalchemy.orm.DeclarativeBase`. +This will attach the model to our schema. -As you can see, our ``User`` class has a class-level attribute -``__tablename__`` which equals the string ``users``. Our ``User`` class will -also have class-level attributes named ``id``, ``name``, ``password_hash``, -and ``role`` (all instances of :class:`sqlalchemy.schema.Column`). These will -map to columns in the ``users`` table. The ``id`` attribute will be the primary -key in the table. The ``name`` attribute will be a text column, each value of -which needs to be unique within the column. The ``password_hash`` is a nullable -text attribute that will contain a securely hashed password. Finally, the -``role`` text attribute will hold the role of the user. +As you can see, our ``User`` class has a class-level attribute ``__tablename__`` which equals the string ``users``. +Our ``User`` class will also have class-level attributes named ``id``, ``name``, ``password_hash``, and ``role``. +These attributes will map to columns in the ``users`` table. +The ``id`` attribute will be the primary key in the table. +The ``name`` attribute will be a text column, each value of which needs to be unique within the column. +The ``password_hash`` is a nullable text attribute that will contain a securely hashed password. +Finally, the ``role`` text attribute will hold the role of the user. + +.. note:: + + Read more about how SQLAlchemy defines columns in the ORM at :ref:`sqla:orm_declarative_table_config_toplevel`. + Every column has a base schema inferred from the ``Mapped[...]`` type annotation. + This can define the Python type, for which SQLAlchemy maintains default database type mappings. + It also defines whether the attribute is nullable. + Any further schema attributes like primary keys, unique keys, or overrides to the database types, can be done by also defining the ``mapped_column``. There are two helper methods that will help us later when using the user objects. The first is ``set_password`` which will take a raw password and @@ -118,10 +122,11 @@ the hashed value of the submitted password against the hashed value of the password stored in the user's record in the database. If the two hashed values match, then the submitted password is valid, and we can authenticate the user. -We hash passwords so that it is impossible to decrypt them and use them to -authenticate in the application. If we stored passwords foolishly in clear -text, then anyone with access to the database could retrieve any password to -authenticate as any user. +We hash passwords so that it is impossible to decipher them and use them to authenticate in the application. +If we stored passwords foolishly in clear text and we lost control of the database, all of our users could be compromised. + +Notice that we configured a ``created_pages`` relationship, which will make more sense after you create the ``Page`` object in the next section. +This relationship is the reverse side of the ``creator`` one-to-many relationship on the ``Page`` and allows a user object to access a list of all pages in which the user is the creator. Add ``page.py`` @@ -141,7 +146,7 @@ here is the ``creator_id`` column, which is a foreign key referencing the want to relate ``User`` objects with ``Page`` objects, we also define a ``creator`` attribute as an ORM-level mapping between the two tables. SQLAlchemy will automatically populate this value using the foreign key -referencing the user. Since the foreign key has ``nullable=False``, we are +referencing the user. Since ``creator`` attribute / foreign key is not marked as optional, we are guaranteed that an instance of ``page`` will have a corresponding ``page.creator``, which will be a ``User`` instance. @@ -190,20 +195,19 @@ Success executing these commands will generate output similar to the following. .. code-block:: text - 2021-01-07 08:00:14,550 INFO [alembic.runtime.migration:155][MainThread] Context impl SQLiteImpl. - 2021-01-07 08:00:14,551 INFO [alembic.runtime.migration:158][MainThread] Will assume non-transactional DDL. - 2021-01-07 08:00:14,553 INFO [alembic.autogenerate.compare:134][MainThread] Detected added table 'users' - 2021-01-07 08:00:14,553 INFO [alembic.autogenerate.compare:134][MainThread] Detected added table 'pages' - 2021-01-07 08:00:14,558 INFO [alembic.autogenerate.compare:622][MainThread] Detected removed index 'my_index' on 'models' - 2021-01-07 08:00:14,558 INFO [alembic.autogenerate.compare:176][MainThread] Detected removed table 'models' - Generating <somepath>/tutorial/tutorial/alembic/versions/20210107_bc9a3dead43a.py ... done + 2024-02-04 13:29:23,664 INFO [alembic.runtime.migration:216][MainThread] Context impl SQLiteImpl. + 2024-02-04 13:29:23,665 INFO [alembic.runtime.migration:219][MainThread] Will assume non-transactional DDL. + 2024-02-04 13:29:23,667 INFO [alembic.autogenerate.compare:189][MainThread] Detected added table 'users' + 2024-02-04 13:29:23,667 INFO [alembic.autogenerate.compare:189][MainThread] Detected added table 'pages' + 2024-02-04 13:29:23,672 INFO [alembic.autogenerate.compare:672][MainThread] Detected removed index 'my_index' on 'models' + 2024-02-04 13:29:23,672 INFO [alembic.autogenerate.compare:230][MainThread] Detected removed table 'models' + Generating <somepath>/tutorial/alembic/versions/20240204_07f9d6b626b2.py ... done .. code-block:: text - 2021-01-07 08:00:21,318 INFO [alembic.runtime.migration:155][MainThread] Context impl SQLiteImpl. - 2021-01-07 08:00:21,318 INFO [alembic.runtime.migration:158][MainThread] Will assume non-transactional DDL. - 2021-01-07 08:00:21,320 INFO [alembic.runtime.migration:517][MainThread] Running upgrade 90658c4a9673 -> bc9a3dead43a, use new models Page and User - + 2024-02-04 13:32:48,735 INFO [alembic.runtime.migration:216][MainThread] Context impl SQLiteImpl. + 2024-02-04 13:32:48,735 INFO [alembic.runtime.migration:219][MainThread] Will assume non-transactional DDL. + 2024-02-04 13:32:48,737 INFO [alembic.runtime.migration:622][MainThread] Running upgrade 4b6614165904 -> 07f9d6b626b2, use new models Page and User .. _wiki2_alembic_overview: @@ -235,7 +239,7 @@ command, as we did in the installation step of this tutorial. .. note:: - The command is named ``initialize_tutorial_db`` because of the mapping defined in the ``[console_scripts]`` entry point of our project's ``setup.py`` file. + The command is named ``initialize_tutorial_db`` because of the mapping defined in the ``[project.scripts]`` section of our project's ``pyproject.toml`` file. Since we've changed our model, we need to make changes to our ``initialize_db.py`` script. In particular, we'll replace our import of diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index 707a6a109..072625f80 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -218,7 +218,7 @@ The output to your console should be something like this: 2024-02-04 12:02:28,828 INFO [alembic.runtime.migration:219][MainThread] Will assume non-transactional DDL. 2024-02-04 12:02:28,832 INFO [alembic.autogenerate.compare:189][MainThread] Detected added table 'models' 2024-02-04 12:02:28,832 INFO [alembic.autogenerate.compare:633][MainThread] Detected added index ''my_index'' on '('name',)' - Generating /Users/michael/work/oss/pyramid/tutorial/tutorial/alembic/versions/20240204_4b6614165904.py ... done + Generating <somepath>/tutorial/tutorial/alembic/versions/20240204_4b6614165904.py ... done Upgrade to that revision. @@ -334,7 +334,7 @@ If successful, you will see output something like this: ====================================== test session starts ====================================== platform darwin -- Python 3.11.7, pytest-8.0.0, pluggy-1.4.0 - rootdir: /Users/michael/work/oss/pyramid/tutorial + rootdir: <somepath>/tutorial configfile: pyproject.toml testpaths: tutorial, tests plugins: cov-4.1.0 diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/mymodel.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/mymodel.py index 947c7b65f..2b9c7da11 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/mymodel.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/mymodel.py @@ -1,4 +1,4 @@ -from sqlalchemy import Index, Integer, Text +from sqlalchemy import Index from sqlalchemy.orm import Mapped, mapped_column from typing import Optional diff --git a/docs/tutorials/wiki2/src/installation/tutorial/models/mymodel.py b/docs/tutorials/wiki2/src/installation/tutorial/models/mymodel.py index 947c7b65f..2b9c7da11 100644 --- a/docs/tutorials/wiki2/src/installation/tutorial/models/mymodel.py +++ b/docs/tutorials/wiki2/src/installation/tutorial/models/mymodel.py @@ -1,4 +1,4 @@ -from sqlalchemy import Index, Integer, Text +from sqlalchemy import Index from sqlalchemy.orm import Mapped, mapped_column from typing import Optional diff --git a/docs/tutorials/wiki2/src/models/.coveragerc b/docs/tutorials/wiki2/src/models/.coveragerc deleted file mode 100644 index 5db0e79cf..000000000 --- a/docs/tutorials/wiki2/src/models/.coveragerc +++ /dev/null @@ -1,2 +0,0 @@ -[run] -source = tutorial diff --git a/docs/tutorials/wiki2/src/models/CHANGES.txt b/docs/tutorials/wiki2/src/models/CHANGES.txt deleted file mode 100644 index 14b902fd1..000000000 --- a/docs/tutorials/wiki2/src/models/CHANGES.txt +++ /dev/null @@ -1,4 +0,0 @@ -0.0 ---- - -- Initial version. diff --git a/docs/tutorials/wiki2/src/models/MANIFEST.in b/docs/tutorials/wiki2/src/models/MANIFEST.in index b4624fd1c..201692c1b 100644 --- a/docs/tutorials/wiki2/src/models/MANIFEST.in +++ b/docs/tutorials/wiki2/src/models/MANIFEST.in @@ -1,4 +1,4 @@ -include *.txt *.ini *.cfg *.rst +include *.txt *.ini *.cfg *.rst *.toml recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 recursive-include tests * recursive-exclude * __pycache__ diff --git a/docs/tutorials/wiki2/src/models/README.md b/docs/tutorials/wiki2/src/models/README.md new file mode 100644 index 000000000..5ac1209d0 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/README.md @@ -0,0 +1,60 @@ +# myproj + +## Getting Started + +- Change directory into your newly created project if not already there. Your + current directory should be the same as this `README.md` file and `pyproject.toml`. + + ``` + cd tutorial + ``` + +- Create a Python virtual environment, if not already created. + + ``` + python3 -m venv env + ``` + +- Upgrade packaging tools, if necessary. + + ``` + env/bin/pip install --upgrade pip + ``` + +- Install the project in editable mode with its testing requirements. + + ``` + env/bin/pip install -e ".[testing]" + ``` + +- Initialize and upgrade the database using Alembic. + + - Generate your first revision. + + ``` + env/bin/alembic -c development.ini revision --autogenerate -m "init" + ``` + + - Upgrade to that revision. + + ``` + env/bin/alembic -c development.ini upgrade head + ``` + +- Load default data into the database using a script. + + ``` + env/bin/initialize_tutorial_db development.ini + ``` + +- Run your project's tests. + + ``` + env/bin/pytest + ``` + +- Run your project. + + ``` + env/bin/pserve development.ini + ``` diff --git a/docs/tutorials/wiki2/src/models/README.txt b/docs/tutorials/wiki2/src/models/README.txt deleted file mode 100644 index ed6b88b49..000000000 --- a/docs/tutorials/wiki2/src/models/README.txt +++ /dev/null @@ -1,44 +0,0 @@ -myproj -====== - -Getting Started ---------------- - -- Change directory into your newly created project if not already there. Your - current directory should be the same as this README.txt file and setup.py. - - cd tutorial - -- Create a Python virtual environment, if not already created. - - python3 -m venv env - -- Upgrade packaging tools, if necessary. - - env/bin/pip install --upgrade pip setuptools - -- Install the project in editable mode with its testing requirements. - - env/bin/pip install -e ".[testing]" - -- Initialize and upgrade the database using Alembic. - - - Generate your first revision. - - env/bin/alembic -c development.ini revision --autogenerate -m "init" - - - Upgrade to that revision. - - env/bin/alembic -c development.ini upgrade head - -- Load default data into the database using a script. - - env/bin/initialize_tutorial_db development.ini - -- Run your project's tests. - - env/bin/pytest - -- Run your project. - - env/bin/pserve development.ini diff --git a/docs/tutorials/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini index f02c4b1b6..e7baeed37 100644 --- a/docs/tutorials/wiki2/src/models/development.ini +++ b/docs/tutorials/wiki2/src/models/development.ini @@ -25,16 +25,16 @@ retry.attempts = 3 [pshell] setup = tutorial.pshell.setup -### -# wsgi server configuration -### - [alembic] # path to migration scripts script_location = tutorial/alembic file_template = %%(year)d%%(month).2d%%(day).2d_%%(rev)s # file_template = %%(rev)s_%%(slug)s +### +# wsgi server configuration +### + [server:main] use = egg:waitress#main listen = localhost:6543 diff --git a/docs/tutorials/wiki2/src/models/production.ini b/docs/tutorials/wiki2/src/models/production.ini index f8e83f21f..f636aaba3 100644 --- a/docs/tutorials/wiki2/src/models/production.ini +++ b/docs/tutorials/wiki2/src/models/production.ini @@ -19,16 +19,16 @@ retry.attempts = 3 [pshell] setup = tutorial.pshell.setup -### -# wsgi server configuration -### - [alembic] # path to migration scripts script_location = tutorial/alembic file_template = %%(year)d%%(month).2d%%(day).2d_%%(rev)s # file_template = %%(rev)s_%%(slug)s +### +# wsgi server configuration +### + [server:main] use = egg:waitress#main listen = *:6543 diff --git a/docs/tutorials/wiki2/src/models/pyproject.toml b/docs/tutorials/wiki2/src/models/pyproject.toml new file mode 100644 index 000000000..973944f98 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/pyproject.toml @@ -0,0 +1,59 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +version = "0.0" +name = "tutorial" +authors = [] +description = "myproj" +readme = "README.md" +keywords = ["web", "pyramid", "pylons"] +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Framework :: Pyramid", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", +] +requires-python = ">=3.8" +dependencies = [ + "alembic", + "bcrypt", + "plaster_pastedeploy", + "pyramid", + "pyramid_debugtoolbar", + "pyramid_jinja2", + "pyramid_retry", + "pyramid_tm", + "SQLAlchemy", + "transaction", + "waitress", + "zope.sqlalchemy", +] + +[project.optional-dependencies] +testing = [ + "WebTest", + "pytest", + "pytest-cov", +] + +[project.scripts] +initialize_tutorial_db = "tutorial.scripts.initialize_db:main" + +[project.entry-points."paste.app_factory"] +main = "tutorial:main" + +[tool.setuptools.packages.find] +exclude = ["tests"] + +[tool.coverage.run] +source = "tutorial" + +[tool.pytest.ini_options] +addopts = "--strict-markers" +testpaths = [ + "tutorial", + "tests", +] diff --git a/docs/tutorials/wiki2/src/models/pytest.ini b/docs/tutorials/wiki2/src/models/pytest.ini deleted file mode 100644 index 3df78fe9d..000000000 --- a/docs/tutorials/wiki2/src/models/pytest.ini +++ /dev/null @@ -1,6 +0,0 @@ -[pytest] -addopts = --strict-markers - -testpaths = - tutorial - tests diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py deleted file mode 100644 index fbd848136..000000000 --- a/docs/tutorials/wiki2/src/models/setup.py +++ /dev/null @@ -1,62 +0,0 @@ -import os - -from setuptools import setup, find_packages - -here = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(here, 'README.txt')) as f: - README = f.read() -with open(os.path.join(here, 'CHANGES.txt')) as f: - CHANGES = f.read() - -requires = [ - 'alembic', - 'bcrypt', - 'plaster_pastedeploy', - 'pyramid', - 'pyramid_debugtoolbar', - 'pyramid_jinja2', - 'pyramid_retry', - 'pyramid_tm', - 'SQLAlchemy', - 'transaction', - 'waitress', - 'zope.sqlalchemy', -] - -tests_require = [ - 'WebTest', - 'pytest', - 'pytest-cov', -] - -setup( - name='tutorial', - version='0.0', - description='myproj', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - 'Programming Language :: Python', - 'Framework :: Pyramid', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', - ], - author='', - author_email='', - url='', - keywords='web pyramid pylons', - packages=find_packages(exclude=['tests']), - include_package_data=True, - zip_safe=False, - extras_require={ - 'testing': tests_require, - }, - install_requires=requires, - entry_points={ - 'paste.app_factory': [ - 'main = tutorial:main', - ], - 'console_scripts': [ - 'initialize_tutorial_db=tutorial.scripts.initialize_db:main', - ], - }, -) diff --git a/docs/tutorials/wiki2/src/models/testing.ini b/docs/tutorials/wiki2/src/models/testing.ini index 5caa1a8dc..503cf3018 100644 --- a/docs/tutorials/wiki2/src/models/testing.ini +++ b/docs/tutorials/wiki2/src/models/testing.ini @@ -19,16 +19,16 @@ retry.attempts = 3 [pshell] setup = tutorial.pshell.setup -### -# wsgi server configuration -### - [alembic] # path to migration scripts script_location = tutorial/alembic file_template = %%(year)d%%(month).2d%%(day).2d_%%(rev)s # file_template = %%(rev)s_%%(slug)s +### +# wsgi server configuration +### + [server:main] use = egg:waitress#main listen = localhost:6543 diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/meta.py b/docs/tutorials/wiki2/src/models/tutorial/models/meta.py index d659c7857..38712bfd2 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models/meta.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models/meta.py @@ -1,16 +1,16 @@ -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.schema import MetaData +from sqlalchemy import MetaData +from sqlalchemy.orm import DeclarativeBase -# Recommended naming convention used by Alembic, as various different database -# providers will autogenerate vastly different names making migrations more -# difficult. See: https://alembic.sqlalchemy.org/en/latest/naming.html -NAMING_CONVENTION = { - "ix": "ix_%(column_0_label)s", - "uq": "uq_%(table_name)s_%(column_0_name)s", - "ck": "ck_%(table_name)s_%(constraint_name)s", - "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", - "pk": "pk_%(table_name)s" -} -metadata = MetaData(naming_convention=NAMING_CONVENTION) -Base = declarative_base(metadata=metadata) +class Base(DeclarativeBase): + # Recommended naming convention used by Alembic, as various different + # database providers will autogenerate vastly different names making + # migrations more difficult. + # See: https://alembic.sqlalchemy.org/en/latest/naming.html + metadata = MetaData(naming_convention={ + "ix": "ix_%(column_0_label)s", + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_`%(constraint_name)s`", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s" + }) diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/page.py b/docs/tutorials/wiki2/src/models/tutorial/models/page.py index 74ff1faf8..5a1c885c9 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models/page.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models/page.py @@ -1,10 +1,6 @@ -from sqlalchemy import ( - Column, - ForeignKey, - Integer, - Text, -) -from sqlalchemy.orm import relationship +import bcrypt +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship from .meta import Base @@ -12,9 +8,9 @@ from .meta import Base class Page(Base): """ The SQLAlchemy declarative model class for a Page object. """ __tablename__ = 'pages' - id = Column(Integer, primary_key=True) - name = Column(Text, nullable=False, unique=True) - data = Column(Text, nullable=False) + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(unique=True) + data: Mapped[str] - creator_id = Column(ForeignKey('users.id'), nullable=False) - creator = relationship('User', backref='created_pages') + creator_id: Mapped[int] = mapped_column(ForeignKey('users.id')) + creator: Mapped['User'] = relationship('User', back_populates='created_pages') diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/user.py b/docs/tutorials/wiki2/src/models/tutorial/models/user.py index 9228b48f7..d85d890c6 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models/user.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models/user.py @@ -1,9 +1,6 @@ import bcrypt -from sqlalchemy import ( - Column, - Integer, - Text, -) +from sqlalchemy.orm import Mapped, mapped_column, relationship +from typing import Optional from .meta import Base @@ -11,11 +8,11 @@ from .meta import Base class User(Base): """ The SQLAlchemy declarative model class for a User object. """ __tablename__ = 'users' - id = Column(Integer, primary_key=True) - name = Column(Text, nullable=False, unique=True) - role = Column(Text, nullable=False) + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(unique=True) + role: Mapped[str] - password_hash = Column(Text) + password_hash: Mapped[Optional[str]] def set_password(self, pw): pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt()) @@ -26,3 +23,5 @@ class User(Base): expected_hash = self.password_hash.encode('utf8') return bcrypt.checkpw(pw.encode('utf8'), expected_hash) return False + + created_pages: Mapped['Page'] = relationship('Page', back_populates='creator') diff --git a/docs/tutorials/wiki2/src/models/tutorial/views/default.py b/docs/tutorials/wiki2/src/models/tutorial/views/default.py index a0f654d38..3eeb7f26d 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/models/tutorial/views/default.py @@ -1,6 +1,6 @@ from pyramid.view import view_config from pyramid.response import Response -from sqlalchemy.exc import SQLAlchemyError +import sqlalchemy as sa from .. import models @@ -8,9 +8,9 @@ from .. import models @view_config(route_name='home', renderer='tutorial:templates/mytemplate.jinja2') def my_view(request): try: - query = request.dbsession.query(models.MyModel) - one = query.filter(models.MyModel.name == 'one').one() - except SQLAlchemyError: + query = sa.select(models.MyModel).where(models.MyModel.name == 'one') + one = request.dbsession.execute(query).scalar_one() + except sa.exc.SQLAlchemyError: return Response(db_err_msg, content_type='text/plain', status=500) return {'one': one, 'project': 'myproj'} |
