abhijan 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- abhijan-0.1.0/PKG-INFO +20 -0
- abhijan-0.1.0/README.md +12 -0
- abhijan-0.1.0/abhijan/__init__.py +3 -0
- abhijan-0.1.0/abhijan/cli.py +35 -0
- abhijan-0.1.0/abhijan/commands/__init__.py +0 -0
- abhijan-0.1.0/abhijan/commands/init.py +37 -0
- abhijan-0.1.0/abhijan/templates/alembic.ini +38 -0
- abhijan-0.1.0/abhijan/templates/alembic_async_env.py +52 -0
- abhijan-0.1.0/abhijan/templates/alembic_sync_env.py +42 -0
- abhijan-0.1.0/abhijan/templates/database_async.py +22 -0
- abhijan-0.1.0/abhijan/templates/database_sync.py +13 -0
- abhijan-0.1.0/abhijan/templates/default_async.env +1 -0
- abhijan-0.1.0/abhijan/templates/default_sync.env +1 -0
- abhijan-0.1.0/abhijan/templates/project_pyproject.toml +8 -0
- abhijan-0.1.0/abhijan/templates/settings.py +10 -0
- abhijan-0.1.0/abhijan.egg-info/PKG-INFO +20 -0
- abhijan-0.1.0/abhijan.egg-info/SOURCES.txt +21 -0
- abhijan-0.1.0/abhijan.egg-info/dependency_links.txt +1 -0
- abhijan-0.1.0/abhijan.egg-info/entry_points.txt +2 -0
- abhijan-0.1.0/abhijan.egg-info/top_level.txt +1 -0
- abhijan-0.1.0/pyproject.toml +24 -0
- abhijan-0.1.0/setup.cfg +4 -0
- abhijan-0.1.0/tests/test_init.py +30 -0
abhijan-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: abhijan
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Project bootstrapper for SQLAlchemy + Alembic
|
|
5
|
+
Author: abhijan
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# abhijan
|
|
10
|
+
|
|
11
|
+
Bootstrap a SQLAlchemy + Alembic project scaffold.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install abhijan
|
|
17
|
+
abhijan init --async
|
|
18
|
+
# or
|
|
19
|
+
abhijan init --sync
|
|
20
|
+
```
|
abhijan-0.1.0/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .commands.init import init_project
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
10
|
+
parser = argparse.ArgumentParser(prog="abhijan")
|
|
11
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
12
|
+
|
|
13
|
+
init_parser = subparsers.add_parser("init", help="Initialize project scaffold")
|
|
14
|
+
mode_group = init_parser.add_mutually_exclusive_group(required=True)
|
|
15
|
+
mode_group.add_argument("--async", dest="is_async", action="store_true", help="Generate async SQLAlchemy setup")
|
|
16
|
+
mode_group.add_argument("--sync", dest="is_sync", action="store_true", help="Generate sync SQLAlchemy setup")
|
|
17
|
+
init_parser.add_argument("--path", type=Path, default=Path.cwd(), help="Target project directory")
|
|
18
|
+
|
|
19
|
+
return parser
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main(argv: list[str] | None = None) -> int:
|
|
23
|
+
parser = build_parser()
|
|
24
|
+
args = parser.parse_args(argv)
|
|
25
|
+
|
|
26
|
+
if args.command == "init":
|
|
27
|
+
mode = "async" if args.is_async else "sync"
|
|
28
|
+
init_project(args.path, mode)
|
|
29
|
+
print(f"Initialized project at {args.path} ({mode})")
|
|
30
|
+
|
|
31
|
+
return 0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == "__main__":
|
|
35
|
+
raise SystemExit(main())
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from importlib import resources
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _render_template(name: str) -> str:
|
|
8
|
+
return resources.files("abhijan.templates").joinpath(name).read_text(encoding="utf-8")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _write_file(path: Path, content: str) -> None:
|
|
12
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
13
|
+
path.write_text(content, encoding="utf-8")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def init_project(project_root: Path, mode: str) -> None:
|
|
17
|
+
if mode not in {"sync", "async"}:
|
|
18
|
+
raise ValueError("mode must be 'sync' or 'async'")
|
|
19
|
+
|
|
20
|
+
app_dir = project_root / "app"
|
|
21
|
+
alembic_dir = project_root / "alembic"
|
|
22
|
+
versions_dir = alembic_dir / "versions"
|
|
23
|
+
|
|
24
|
+
app_dir.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
versions_dir.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
|
|
27
|
+
_write_file(app_dir / "__init__.py", "")
|
|
28
|
+
_write_file(app_dir / "settings.py", _render_template("settings.py"))
|
|
29
|
+
_write_file(app_dir / "database.py", _render_template(f"database_{mode}.py"))
|
|
30
|
+
_write_file(project_root / ".env", _render_template(f"default_{mode}.env"))
|
|
31
|
+
|
|
32
|
+
_write_file(project_root / "alembic.ini", _render_template("alembic.ini"))
|
|
33
|
+
_write_file(alembic_dir / "env.py", _render_template(f"alembic_{mode}_env.py"))
|
|
34
|
+
|
|
35
|
+
pyproject = project_root / "pyproject.toml"
|
|
36
|
+
if not pyproject.exists():
|
|
37
|
+
_write_file(pyproject, _render_template("project_pyproject.toml"))
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[alembic]
|
|
2
|
+
script_location = alembic
|
|
3
|
+
prepend_sys_path = .
|
|
4
|
+
|
|
5
|
+
[loggers]
|
|
6
|
+
keys = root,sqlalchemy,alembic
|
|
7
|
+
|
|
8
|
+
[handlers]
|
|
9
|
+
keys = console
|
|
10
|
+
|
|
11
|
+
[formatters]
|
|
12
|
+
keys = generic
|
|
13
|
+
|
|
14
|
+
[logger_root]
|
|
15
|
+
level = WARN
|
|
16
|
+
handlers = console
|
|
17
|
+
|
|
18
|
+
[logger_sqlalchemy]
|
|
19
|
+
level = WARN
|
|
20
|
+
handlers =
|
|
21
|
+
qualname = sqlalchemy.engine
|
|
22
|
+
|
|
23
|
+
[logger_alembic]
|
|
24
|
+
level = INFO
|
|
25
|
+
handlers =
|
|
26
|
+
qualname = alembic
|
|
27
|
+
|
|
28
|
+
[handler_console]
|
|
29
|
+
class = StreamHandler
|
|
30
|
+
args = (sys.stderr,)
|
|
31
|
+
level = NOTSET
|
|
32
|
+
formatter = generic
|
|
33
|
+
|
|
34
|
+
[formatter_generic]
|
|
35
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
36
|
+
|
|
37
|
+
auto_generate = true
|
|
38
|
+
sqlalchemy.url = sqlite:///./app.db
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from logging.config import fileConfig
|
|
2
|
+
|
|
3
|
+
from alembic import context
|
|
4
|
+
from sqlalchemy import pool
|
|
5
|
+
from sqlalchemy.engine import Connection
|
|
6
|
+
from sqlalchemy.ext.asyncio import async_engine_from_config
|
|
7
|
+
|
|
8
|
+
config = context.config
|
|
9
|
+
|
|
10
|
+
if config.config_file_name is not None:
|
|
11
|
+
fileConfig(config.config_file_name)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run_migrations_offline() -> None:
|
|
15
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
16
|
+
context.configure(
|
|
17
|
+
url=url,
|
|
18
|
+
target_metadata=None,
|
|
19
|
+
literal_binds=True,
|
|
20
|
+
dialect_opts={"paramstyle": "named"},
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
with context.begin_transaction():
|
|
24
|
+
context.run_migrations()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def do_run_migrations(connection: Connection) -> None:
|
|
28
|
+
context.configure(connection=connection, target_metadata=None)
|
|
29
|
+
|
|
30
|
+
with context.begin_transaction():
|
|
31
|
+
context.run_migrations()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def run_migrations_online() -> None:
|
|
35
|
+
connectable = async_engine_from_config(
|
|
36
|
+
config.get_section(config.config_ini_section, {}),
|
|
37
|
+
prefix="sqlalchemy.",
|
|
38
|
+
poolclass=pool.NullPool,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
async with connectable.connect() as connection:
|
|
42
|
+
await connection.run_sync(do_run_migrations)
|
|
43
|
+
|
|
44
|
+
await connectable.dispose()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if context.is_offline_mode():
|
|
48
|
+
run_migrations_offline()
|
|
49
|
+
else:
|
|
50
|
+
import asyncio
|
|
51
|
+
|
|
52
|
+
asyncio.run(run_migrations_online())
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from logging.config import fileConfig
|
|
2
|
+
|
|
3
|
+
from alembic import context
|
|
4
|
+
from sqlalchemy import engine_from_config, pool
|
|
5
|
+
|
|
6
|
+
config = context.config
|
|
7
|
+
|
|
8
|
+
if config.config_file_name is not None:
|
|
9
|
+
fileConfig(config.config_file_name)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run_migrations_offline() -> None:
|
|
13
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
14
|
+
context.configure(
|
|
15
|
+
url=url,
|
|
16
|
+
target_metadata=None,
|
|
17
|
+
literal_binds=True,
|
|
18
|
+
dialect_opts={"paramstyle": "named"},
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
with context.begin_transaction():
|
|
22
|
+
context.run_migrations()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run_migrations_online() -> None:
|
|
26
|
+
connectable = engine_from_config(
|
|
27
|
+
config.get_section(config.config_ini_section, {}),
|
|
28
|
+
prefix="sqlalchemy.",
|
|
29
|
+
poolclass=pool.NullPool,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
with connectable.connect() as connection:
|
|
33
|
+
context.configure(connection=connection, target_metadata=None)
|
|
34
|
+
|
|
35
|
+
with context.begin_transaction():
|
|
36
|
+
context.run_migrations()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if context.is_offline_mode():
|
|
40
|
+
run_migrations_offline()
|
|
41
|
+
else:
|
|
42
|
+
run_migrations_online()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from sqlalchemy.ext.asyncio import (
|
|
2
|
+
AsyncSession,
|
|
3
|
+
async_sessionmaker,
|
|
4
|
+
create_async_engine,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
from .settings import settings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
engine = create_async_engine(
|
|
11
|
+
settings.DATABASE_URL,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
async_session_maker = async_sessionmaker(
|
|
15
|
+
engine,
|
|
16
|
+
expire_on_commit=False,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def get_db():
|
|
21
|
+
async with async_session_maker() as session:
|
|
22
|
+
yield session
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from sqlalchemy import create_engine
|
|
2
|
+
from sqlalchemy.orm import sessionmaker
|
|
3
|
+
|
|
4
|
+
from .settings import settings
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
engine = create_engine(settings.DATABASE_URL)
|
|
8
|
+
|
|
9
|
+
SessionLocal = sessionmaker(
|
|
10
|
+
bind=engine,
|
|
11
|
+
autoflush=False,
|
|
12
|
+
autocommit=False,
|
|
13
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DATABASE_URL=sqlite+aiosqlite:///./app.db
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DATABASE_URL=sqlite:///./app.db
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: abhijan
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Project bootstrapper for SQLAlchemy + Alembic
|
|
5
|
+
Author: abhijan
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# abhijan
|
|
10
|
+
|
|
11
|
+
Bootstrap a SQLAlchemy + Alembic project scaffold.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install abhijan
|
|
17
|
+
abhijan init --async
|
|
18
|
+
# or
|
|
19
|
+
abhijan init --sync
|
|
20
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
abhijan/__init__.py
|
|
4
|
+
abhijan/cli.py
|
|
5
|
+
abhijan.egg-info/PKG-INFO
|
|
6
|
+
abhijan.egg-info/SOURCES.txt
|
|
7
|
+
abhijan.egg-info/dependency_links.txt
|
|
8
|
+
abhijan.egg-info/entry_points.txt
|
|
9
|
+
abhijan.egg-info/top_level.txt
|
|
10
|
+
abhijan/commands/__init__.py
|
|
11
|
+
abhijan/commands/init.py
|
|
12
|
+
abhijan/templates/alembic.ini
|
|
13
|
+
abhijan/templates/alembic_async_env.py
|
|
14
|
+
abhijan/templates/alembic_sync_env.py
|
|
15
|
+
abhijan/templates/database_async.py
|
|
16
|
+
abhijan/templates/database_sync.py
|
|
17
|
+
abhijan/templates/default_async.env
|
|
18
|
+
abhijan/templates/default_sync.env
|
|
19
|
+
abhijan/templates/project_pyproject.toml
|
|
20
|
+
abhijan/templates/settings.py
|
|
21
|
+
tests/test_init.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
abhijan
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "abhijan"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Project bootstrapper for SQLAlchemy + Alembic"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [{ name = "abhijan" }]
|
|
12
|
+
dependencies = []
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
abhijan = "abhijan.cli:main"
|
|
16
|
+
|
|
17
|
+
[tool.setuptools]
|
|
18
|
+
include-package-data = true
|
|
19
|
+
|
|
20
|
+
[tool.setuptools.packages.find]
|
|
21
|
+
include = ["abhijan*"]
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.package-data]
|
|
24
|
+
abhijan = ["templates/*.py", "templates/*.ini", "templates/*.toml", "templates/*.env"]
|
abhijan-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from abhijan.cli import main
|
|
6
|
+
from abhijan.commands.init import init_project
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_init_async_generates_expected_files(tmp_path: Path) -> None:
|
|
10
|
+
root = tmp_path / "project"
|
|
11
|
+
main(["init", "--async", "--path", str(root)])
|
|
12
|
+
|
|
13
|
+
assert (root / "app" / "settings.py").exists()
|
|
14
|
+
assert (root / "app" / "database.py").read_text(encoding="utf-8").find("create_async_engine") != -1
|
|
15
|
+
assert (root / "alembic" / "env.py").read_text(encoding="utf-8").find("async_engine_from_config") != -1
|
|
16
|
+
assert (root / ".env").read_text(encoding="utf-8").strip() == "DATABASE_URL=sqlite+aiosqlite:///./app.db"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_init_sync_generates_expected_files(tmp_path: Path) -> None:
|
|
20
|
+
root = tmp_path / "project"
|
|
21
|
+
main(["init", "--sync", "--path", str(root)])
|
|
22
|
+
|
|
23
|
+
assert (root / "app" / "database.py").read_text(encoding="utf-8").find("create_engine") != -1
|
|
24
|
+
assert (root / "alembic" / "env.py").read_text(encoding="utf-8").find("engine_from_config") != -1
|
|
25
|
+
assert (root / ".env").read_text(encoding="utf-8").strip() == "DATABASE_URL=sqlite:///./app.db"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_init_rejects_invalid_mode(tmp_path: Path) -> None:
|
|
29
|
+
with pytest.raises(ValueError):
|
|
30
|
+
init_project(tmp_path, "invalid")
|