---
# System prepended metadata

title: 17 - CLI
tags: [web, day2]

---

---
tags: web,day2
robots: noindex, nofollow
lang: pt-br
---

# 17 - CLI

Command Line Interface

### Posts

O próximo passo é criarmos funções que servirão de **controllers** para nossos dados, essas funções serão úteis para abstrair o acesso ao MongoDB e serão usadas tanto pelas views quanto pela CLI.


`exemplos/day2/flask/blog/posts.py`
```python
from __future__ import annotations
from datetime import datetime
from blog.database import mongo


def get_all_posts(published=True):
    posts = mongo.db.posts.find({"published": published})
    return posts.sort("date")


def get_post_by_slug(slug: str) -> dict:
    post = mongo.db.posts.find_one({"slug": slug})
    return post


def update_post_by_slug(slug: str, data: dict) -> dict:
    return mongo.db.posts.find_one_and_update({"slug": slug}, {"$set": data})


def new_post(title: str, content: str, published: bool = True) -> str:
    slug = title.replace("_", "-").replace(" ", "-").lower()
    # TODO: Evitar a entrada de posts com slug duplicado?
    new = mongo.db.posts.insert_one(
        {
            "title": title,
            "content": content,
            "published": published,
            "slug": slug,
            "date": datetime.now(),
        }
    )
    return slug
```

Nesse ponto já podemos abrir o `shell` e testar se está tudo funcionando:


```python
In [1]: from blog.posts import get_all_posts, get_post_by_slug, update_post_by_slug, new_post

# Criar um novo post
In [2]: new_post(title="Flask é legal", content="Eu gosto de flask")
Out[2]: 'flask-é-legal'

# Buscar o post mais recente
In [3]: list(get_all_posts())[-1]
Out[3]: 
{'_id': ObjectId('62ba02d2ba946f4d625c87a7'),
 'title': 'Flask é legal',
 'content': 'Eu gosto de flask',
 'published': True,
 'slug': 'flask-é-legal',
 'date': datetime.datetime(2022, 6, 27, 20, 19, 46, 776000)}

# Buscar pelo slug
In [4]: get_post_by_slug("flask-é-legal")
Out[4]: 
{'_id': ObjectId('62ba02d2ba946f4d625c87a7'),
 'title': 'Flask é legal',
 'content': 'Eu gosto de flask',
 'published': True,
 'slug': 'flask-é-legal',
 'date': datetime.datetime(2022, 6, 27, 20, 19, 46, 776000)}

# Alterar um post
In [5]: update_post_by_slug("flask-é-legal", {"content": "Novo contéudo alterado."})
Out[5]: 
{'_id': ObjectId('62ba02d2ba946f4d625c87a7'),
 'title': 'Flask é legal',
 'content': 'Eu gosto de flask',
 'published': True,
 'slug': 'flask-é-legal',
 'date': datetime.datetime(2022, 6, 27, 20, 19, 46, 776000)}

# Confirmar a alteração
In [6]: get_post_by_slug("flask-é-legal")
Out[6]: 
{'_id': ObjectId('62ba02d2ba946f4d625c87a7'),
 'title': 'Flask é legal',
 'content': 'Novo contéudo alterado.',
 'published': True,
 'slug': 'flask-é-legal',
 'date': datetime.datetime(2022, 6, 27, 20, 19, 46, 776000)}
```

### CLI

O Flask oferece a possibilidade de extendermos a sua `CLI` e para fazer isso vamos criar no arquivo `commands.py` novos comandos:

- `post new`
- `post list`
- `post get`
- `post update`

`exemplos/day2/flask/blog/commands.py`
```python
import click

from blog.posts import (
    get_all_posts,
    get_post_by_slug,
    new_post,
    update_post_by_slug
)


@click.group()
def post():
    """Manage posts"""


@post.command()
@click.option("--title")
@click.option("--content")
def new(title, content):
    """Creates a new post"""
    new = new_post(title=title, content=content)
    click.echo(f"New post {new} created!")


@post.command("list")
def _list():
    """Lists all posts"""
    for post in get_all_posts():
        click.echo(post)


@post.command()
@click.argument("slug")
def get(slug):
    """Get post by slug"""
    post = get_post_by_slug(slug)
    click.echo(post or "post not found")


@post.command()
@click.argument("slug")
@click.option("--content", default=None, type=str)
@click.option("--published", default=None, type=str)
def update(slug, content, published):
    """Update post by slug"""
    data = {}
    if content is not None:
        data["content"] = content
    if published is not None:
        data["published"] = published.lower() == "true"
    update_post_by_slug(slug, data)
    click.echo("Post updated")


def configure(app):
    app.cli.add_command(post)
```

Agora inicializamos o módulo `commands no arquivo de settings`

`settings.toml`
```toml
[default]
mongo_uri = "mongodb://localhost:27017/blog"
extensions = [
    "blog.database:configure",
    "blog.commands:configure"  # NEW
]
```

E podemos testar no terminal.

```
export FLASK_APP=blog.app:create_app 
export FLASK_ENV=development
```


```bash
flask post --help    

Usage: flask post [OPTIONS] COMMAND [ARGS]...

  Manage posts

Options:
  --help  Show this message and exit.

Commands:
  get     Get post by slug
  list    Lists all posts
  new     Creates a new post
  update  Update post by slug
```

```bash
flask post get flask-é-legal

{'_id': ObjectId('62ba02d2ba946f4d625c87a7'), 'title': 'Flask é legal', 'content': 'Novo contéudo alterado.', 'published': True, 'slug': 'flask-é-legal', 'date': datetime.datetime(2022, 6, 27, 20, 19, 46, 776000)}

```

```bash
flask post list    

{'_id': ObjectId('62b613a07e4b3c31107abd2b'), 'title': 'Meu primeiro post', 'content': 'Este é meu primeiro blog post', 'published': True, 'slug': 'meu-primeiro-post'}
{'_id': ObjectId('62ba00a02a93f608c1b6978f'), 'title': 'Another post', 'content': 'this is even another', 'published': True, 'slug': 'another-post', 'date': datetime.datetime(2022, 6, 27, 20, 10, 24, 914000)}
{'_id': ObjectId('62ba02d2ba946f4d625c87a7'), 'title': 'Flask é legal', 'content': 'Novo contéudo alterado.', 'published': True, 'slug': 'flask-é-legal', 'date': datetime.datetime(2022, 6, 27, 20, 19, 46, 776000)}
```

```bash
flask post new --title "Flask na linha de 
comando" --content "É muito legal o CLI do Flask"

New post 62ba0a2528fe9264f931f142 created!
```

```bash
flask post update flask-é-legal --published "false"                                     

Post updated
```


Na próxima aula continuaremos adicionando views, templates e uma interface administrativa a nossa aplicação :)
