Skip to content

Commit

Permalink
Reorganize demo
Browse files Browse the repository at this point in the history
  • Loading branch information
maldoinc committed Aug 24, 2024
1 parent d806b50 commit 25d25aa
Show file tree
Hide file tree
Showing 23 changed files with 301 additions and 284 deletions.
4 changes: 2 additions & 2 deletions config/parameters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ app:
from_address: "[email protected]"
admin_name: "John Doe"
admin_address: "[email protected]"
dsn: !ENV ${MAILER_DSN}
db_connection_url: !ENV ${DB_CONNECTION_URL}
dsn: "smtp://invalid"
db_connection_url: "sqlite:///var/blog.db"
17 changes: 5 additions & 12 deletions demoapp/app.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import yaml
from flask import Flask
from pyaml_env import parse_config
from wireup import container, initialize_container
from wireup.integration.flask_integration import wireup_init_flask_integration

from demoapp import services
from demoapp.blueprints.post import bp as post_blueprint
from demoapp.blueprint.post import bp as post_blueprint
from demoapp.config import get_config


def create_app() -> Flask:
flask_app = Flask(__name__)

flask_app.register_blueprint(post_blueprint)

# Load all configuration from yaml into a dict then register them in the container.
# Services asking for parameter can reference them by name.
# Note that types don't have to be just scalar values.
# notification_mailer is a dataclass that will get injected as a parameter.
all_config = parse_config("config/parameters.yaml", loader=yaml.Loader)
container.params.update(all_config["app"])
initialize_container(container, service_modules=[services])
flask_app.config.update(get_config())
wireup_init_flask_integration(flask_app, service_modules=[services])

return flask_app

Expand Down
File renamed without changes.
18 changes: 6 additions & 12 deletions demoapp/blueprints/post.py → demoapp/blueprint/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

import flask
from flask import Blueprint, Response, abort, jsonify
from wireup import Inject, container
from wireup import Inject

from demoapp.models.api import PostCreateModel
from demoapp.services.post_repository import PostRepository
from demoapp.services.post_service import PostService
from demoapp.util import ApiEndpoint, ApiResponse

bp = Blueprint("post", __name__, url_prefix="/posts")

Expand All @@ -16,31 +15,26 @@
# Inject services by decorating methods with `@container.autowire`.
# Order can be important with decorators and this needs to be listed closer
# to the method than the flask one so that it is executed before it.
@container.autowire
# Injection is automatically performed in views via the Flask integration.
# Making @container.autowire here redundant.
def get_posts(post_repository: PostRepository) -> Response:
return jsonify(post_repository.find_all())
return jsonify([post.model_dump() for post in post_repository.find_all()])


@bp.post("/")
@container.autowire
# Dependencies will get injected based on their type.
def create_post(post_service: PostService) -> Response:
new_post = post_service.create_post(PostCreateModel(**flask.request.json))

return ApiResponse.created(
data=new_post,
location=ApiEndpoint("post.get_post", post_id=new_post.id),
)
return jsonify(new_post.model_dump())


@bp.get("/<int:post_id>")
@container.autowire
# Alternatively, if you want to be explicit about what will get injected
# you can annotate injected services with `Inject()`.
# If the container does not know about a type which is being explicitly asked
# to inject, it will raise an error.
def get_post(post_id: int, post_repository: Annotated[PostRepository, Inject()]) -> Response:
if post := post_repository.find_one_by_id(post_id):
return ApiResponse.ok(post)
return jsonify(post.model_dump())

return abort(404)
9 changes: 9 additions & 0 deletions demoapp/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from wireup import container, initialize_container

from demoapp import services
from demoapp.commands import cli
from demoapp.config import get_config

if __name__ == "__main__":
initialize_container(container, parameters=get_config(), service_modules=[services])
cli()
13 changes: 13 additions & 0 deletions demoapp/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import click

from demoapp.commands.create_database_command import create_db
from demoapp.commands.create_post_command import create_post


@click.group()
def cli() -> None:
pass


cli.add_command(create_post)
cli.add_command(create_db)
19 changes: 19 additions & 0 deletions demoapp/commands/create_database_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import sys
from pathlib import Path

import click
from wireup import container

from demoapp.models.db import DbBaseModel
from demoapp.services.database_connection import DatabaseConnection


@click.command()
@container.autowire
def create_db(db: DatabaseConnection) -> None:
path = Path(sys.argv[0]).parent.parent / "var"

if not path.exists():
Path.mkdir(path)

DbBaseModel.metadata.create_all(db.engine)
19 changes: 19 additions & 0 deletions demoapp/commands/create_post_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from datetime import datetime, timezone

import click
from wireup import container

from demoapp.models.api import PostCreateModel
from demoapp.services.post_service import PostService


@click.command()
@click.argument("title")
@click.argument("contents")
@container.autowire
def create_post(title: str, contents: str, post_service: PostService) -> None:
post = post_service.create_post(
PostCreateModel(title=title, content=contents, created_at=datetime.now(tz=timezone.utc))
)

click.echo(f"Created post with id: {post.id}")
17 changes: 17 additions & 0 deletions demoapp/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from pathlib import Path
from typing import Any

import yaml


def get_config() -> dict[str, Any]:
# Load all configuration from yaml into a dict then register them in the container.
# Services asking for parameter can reference them by name.
# Note that types don't have to be just scalar values.
# notification_mailer is a dataclass that will get injected as a parameter.
app_dir = Path(__file__).parent.parent

with Path.open(Path(f"{app_dir}/config/parameters.yaml")) as f:
all_config = yaml.unsafe_load(f)

return all_config["app"]
29 changes: 17 additions & 12 deletions demoapp/models/db.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
from datetime import datetime, timezone

from sqlalchemy import Column, DateTime, ForeignKey, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy import DateTime, ForeignKey, Integer, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship

DbBaseModel = declarative_base()

class DbBaseModel(DeclarativeBase):
pass


class Post(DbBaseModel):
__tablename__ = "posts"

id: Column[int] = Column(Integer, primary_key=True)
title: Column[str] = Column(String(255), nullable=False)
content: Column[str] = Column(String, nullable=False)
created_at: Column[datetime] = Column(DateTime, default=lambda: datetime.now(tz=timezone.utc))
id: Mapped[int] = mapped_column(Integer, primary_key=True)
title: Mapped[str] = mapped_column(String(255), nullable=False)
content: Mapped[str] = mapped_column(String, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime, default=lambda: datetime.now(tz=timezone.utc)
)

comments = relationship("Comment", backref="post", lazy=True)


class Comment(DbBaseModel):
__tablename__ = "comments"

id: Column[int] = Column(Integer, primary_key=True)
content: Column[str] = Column(String, nullable=False)
created_at: Column[datetime] = Column(DateTime, default=lambda: datetime.now(tz=timezone.utc))
id: Mapped[int] = mapped_column(Integer, primary_key=True)
content: Mapped[str] = mapped_column(String, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime, default=lambda: datetime.now(tz=timezone.utc)
)

post_id: Column[int] = Column(Integer, ForeignKey("posts.id"), nullable=False)
post_id: Mapped[int] = mapped_column(Integer, ForeignKey("posts.id"), nullable=False)
5 changes: 2 additions & 3 deletions demoapp/services/mailer_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ def make_email_from_post(self, post: Post) -> EmailMessage:
)

def send_email(self, message: EmailMessage) -> None:
msg = (
f'Cannot send message with subject "{message.subject}" '
print(
f'Sending email: "{message.subject}",\n\n{message.body}.\n'
f"from dsn {self.notifier_config.dsn}"
)
raise NotImplementedError(msg)
11 changes: 8 additions & 3 deletions demoapp/services/post_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from wireup import service

from demoapp.models.api import PostCreateModel
from demoapp.models.api import PostCreateModel, PostGetModel
from demoapp.models.db import Post
from demoapp.services.mailer_service import MailerService
from demoapp.services.post_repository import PostRepository
Expand All @@ -24,10 +24,15 @@ class PostService:
repository: PostRepository
mailer: MailerService

def create_post(self, post: PostCreateModel) -> Post:
def create_post(self, post: PostCreateModel) -> PostGetModel:
new_post = Post(**post.model_dump())

self.repository.save(new_post)
self.mailer.notify_admin_for_post(new_post)

return new_post
return PostGetModel(
id=new_post.id,
title=new_post.title,
content=new_post.content,
created_at=new_post.created_at,
)
3 changes: 0 additions & 3 deletions demoapp/util/__init__.py

This file was deleted.

62 changes: 0 additions & 62 deletions demoapp/util/api_response.py

This file was deleted.

Loading

0 comments on commit 25d25aa

Please sign in to comment.