Poetry is a tool for dependency management and packaging in Python. It allows you to declare the libraries your project depends on and it will manage (install/update) them for you. Poetry offers a lockfile to ensure repeatable installs, and can build your project for distribution.
Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary. It’s the “Command Line Interface Creation Kit”. It’s highly configurable but comes with sensible defaults out of the box.
It aims to make the process of writing command line tools quick and fun while also preventing any frustration caused by the inability to implement an intended CLI API.
Protection of sensitive information (passwords/tokens)
Multiple file formatstoml|yaml|json|ini|py and also customizable loaders.
Full support for environment variables to override existing settings (dotenv support included).
Optional layered system for multi environments[default, development, testing, production] (also called multi profiles)
Built-in support for Hashicorp Vault and Redis as settings and secrets storage.
Built-in extensions for Django and Flask web frameworks.
CLI for common operations such as init, list, write, validate, export.
SQLAlchemy
SQLAlchemy is the Python SQL toolkit and Object Relational Mapper that gives application developers the full power and flexibility of SQL.
It provides a full suite of well known enterprise-level persistence patterns, designed for efficient and high-performing database access, adapted into a simple and Pythonic domain language.
Pydantic is the most widely used data validation library for Python.
Fast and extensible, Pydantic plays nicely with your linters/IDE/brain. Define how data should be in pure, canonical Python 3.8+; validate it with Pydantic.
Uvicorn is an ASGI web server implementation for Python.
Until recently Python has lacked a minimal low-level server/application interface for async frameworks. The ASGI specification fills this gap, and means we’re now able to start building a common set of tooling usable across all async frameworks.
Uvicorn currently supports HTTP/1.1 and WebSockets.
Alembic
Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database Toolkit for Python.
Technically, you can also create Python packages without an __init__.py file, but those are called namespace packages and considered an advanced topic (not covered in this tutorial). If you are only getting started with Python packaging, it is recommended to stick with regular packages and __init__.py (even if the file is empty).
Explain[tool.poetry] name = "example_blog" version = "0.1.0" description = "This is example blog system." authors = ["huagang517 <huagang517@126.com>"] readme = "README.md"
# PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec
# pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version
# pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/
### Windows template # Windows thumbnail cache files Thumbs.db Thumbs.db:encryptable ehthumbs.db ehthumbs_vista.db
# Dump file *.stackdump
# Folder config file [Dd]esktop.ini
# Recycle Bin used on file shares $RECYCLE.BIN/
# Windows Installer files *.cab *.msi *.msix *.msm *.msp
# Windows shortcuts *.lnk
### Linux template *~
# temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden*
# KDE directory preferences .directory
# Linux trash folder which might appear on any partition or disk .Trash-*
# .nfs files are created when an open file is removed but is still being accessed .nfs*
### macOS template # General .DS_Store .AppleDouble .LSOverride
# Icon must end with two \r Icon
# Thumbnails ._*
# Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk
.vscode .idea
安装开发包
1
poetry install
初始 Git 提交
1 2 3 4 5
git init git config user.name example git config user.email example@example.com git add . git commit -m "feat: First commit!"
from sqlalchemy.engine import create_engine from sqlalchemy.engine.base import Engine from sqlalchemy.engine.url import URL from sqlalchemy.orm import scoped_session, sessionmaker
# BaseDAO类的具体实现,用于操作文章模型 classArticleDAO(BaseDAO[Article, CreateArticleSchema, UpdateArticleSchema]): # 设置 model 属性为 Article,指定了该 DAO 类要操作的模型类型 model = Article
from example_blog.dao import ArticleDAO, BaseDAO from example_blog.models import Article from example_blog.schemas import CreateSchema, ModelType, UpdateSchema
from fastapi import APIRouter, Depends from sqlalchemy.orm import Session
from example_blog.dependencies import CommonQueryParams, get_db from example_blog.schemas import (ArticleSchema, CreateArticleSchema, UpdateArticleSchema) from example_blog.services import ArticleService
git add . git commit -m "feat: Add server cmdline."
启动 Server
将本项目以可编辑方式安装到当前 Python 环境:
1
pip install -e .
命令行运行:
1
example_blog server
可以看到如下输出:
1 2 3 4 5 6 7 8
INFO: Started server process [21687] 2020-12-28 18:11:56,341 INFO uvicorn.error 21687 139772921304768 Started server process [21687] INFO: Waiting for application startup. 2020-12-28 18:11:56,341 INFO uvicorn.error 21687 139772921304768 Waiting for application startup. INFO: Application startup complete. 2020-12-28 18:11:56,341 INFO uvicorn.error 21687 139772921304768 Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) 2020-12-28 18:11:56,341 INFO uvicorn.error 21687 139772921304768 Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
# template used to generate migration files # file_template = %%(rev)s_%%(slug)s
# timezone to use when rendering the date # within the migration file as well as the filename. # string value is passed to dateutil.tz.gettz() # leave blank for localtime # timezone =
# max length of characters to apply to the # "slug" field # truncate_slug_length = 40
# set to 'true' to run the environment during # the 'revision' command, regardless of autogenerate # revision_environment = false
# set to 'true' to allow .pyc and .pyo files without # a source .py file to be detected as revisions in the # versions/ directory # sourceless = false
# version location specification; this defaults # to src/example_blog/migration/versions. When using multiple version # directories, initial revisions must be specified with --version-path # version_locations = %(here)s/bar %(here)s/bat src/example_blog/migration/versions
# the output encoding used when revision files # are written from script.py.mako # output_encoding = utf-8
[post_write_hooks] # post_write_hooks defines scripts or Python functions that are run # on newly generated revision scripts. See the documentation for further # detail and examples
# format using "black" - use the console_scripts runner, against the "black" entrypoint # hooks=black # black.type=console_scripts # black.entrypoint=black # black.options=-l 79
from alembic import context from sqlalchemy import engine_from_config, pool
from example_blog import db from example_blog.models import BaseModel
# this is the Alembic Config object, which provides # access to the values within the .ini file in use. config = context.config
# Interpret the config file for Python logging. # This line sets up loggers basically. fileConfig(config.config_file_name)
# add your model's MetaData object here # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata # target_metadata = None
target_metadata = BaseModel.metadata
# other values from the config, defined by the needs of env.py, # can be acquired: # my_important_option = config.get_main_option("my_important_option") # ... etc.
defrun_migrations_offline(): """Run migrations in 'offline' mode. This configures the context with just a URL and not an Engine, though an Engine is acceptable here as well. By skipping the Engine creation we don't even need a DBAPI to be available. Calls to context.execute() here emit the given string to the script output. """ context.configure( url=db.url, target_metadata=target_metadata, literal_binds=True, dialect_opts={"paramstyle": "named"}, )
with context.begin_transaction(): context.run_migrations()
defrun_migrations_online(): """Run migrations in 'online' mode. In this scenario we need to create an Engine and associate a connection with the context. """ configuration = config.get_section(config.config_ini_section) configuration['sqlalchemy.url'] = str(db.url) connectable = engine_from_config( configuration, prefix="sqlalchemy.", poolclass=pool.NullPool, )
with connectable.connect() as connection: context.configure( connection=connection, target_metadata=target_metadata )
with context.begin_transaction(): context.run_migrations()
if context.is_offline_mode(): run_migrations_offline() else: run_migrations_online()
编写 src/example_blog/cmdline.py ,创建迁移命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
from pathlib import Path
from alembic import config from click import Context
from example_blog import migration from example_blog.config import settings from example_blog.db import SessionFactory from example_blog.models import Article
@pytest.fixture() defmigrate(): """Re-init database when run a test.""" os.chdir(Path(migration.__file__).parent) alembic_config = config.Config('./alembic.ini') alembic_config.set_main_option('script_location', os.getcwd()) print('\n----- RUN ALEMBIC MIGRATION: -----\n') command.downgrade(alembic_config, 'base') command.upgrade(alembic_config, 'head') try: yield finally: command.downgrade(alembic_config, 'base') db_name = settings.DATABASE.get('NAME') if settings.DATABASE.DRIVER == 'sqlite'and os.path.isfile(db_name): try: os.remove(db_name) except FileNotFoundError: pass
@pytest.fixture() definit_article(session): """Init article""" a_1 = Article(title='Hello world', body='Hello world, can you see me?') a_2 = Article(title='Love baby', body='I love you everyday, and i want with you.') a_3 = Article(title='Tomorrow', body='When the sun rises, this day is fine day, cheer up.') session.add_all([a_1, a_2, a_3]) session.commit()
from example_blog.dao import ArticleDAO from example_blog.models import Article from example_blog.schemas import CreateArticleSchema, UpdateArticleSchema
[tool.poetry] name = "example_blog" version = "0.1.0" description = "This is example blog system." authors = ["huagang <huagang517@126.com>"] readme = "README.md"