waiross 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.
- waiross-0.1.0/.env.example +35 -0
- waiross-0.1.0/.github/workflows/release.yml +49 -0
- waiross-0.1.0/.gitignore +20 -0
- waiross-0.1.0/Dockerfile +17 -0
- waiross-0.1.0/LICENSE +21 -0
- waiross-0.1.0/Makefile +46 -0
- waiross-0.1.0/PKG-INFO +403 -0
- waiross-0.1.0/PUBLISHING.md +84 -0
- waiross-0.1.0/README.md +377 -0
- waiross-0.1.0/alembic.ini +44 -0
- waiross-0.1.0/app/__init__.py +0 -0
- waiross-0.1.0/app/application/__init__.py +0 -0
- waiross-0.1.0/app/application/dto/__init__.py +0 -0
- waiross-0.1.0/app/application/dto/user/__init__.py +0 -0
- waiross-0.1.0/app/application/dto/user/create_user_input.py +10 -0
- waiross-0.1.0/app/application/dto/user/delete_user_input.py +9 -0
- waiross-0.1.0/app/application/dto/user/get_user_input.py +9 -0
- waiross-0.1.0/app/application/dto/user/list_users_input.py +9 -0
- waiross-0.1.0/app/application/dto/user/login_input.py +9 -0
- waiross-0.1.0/app/application/dto/user/token_output.py +15 -0
- waiross-0.1.0/app/application/dto/user/update_user_input.py +11 -0
- waiross-0.1.0/app/application/dto/user/user_output.py +47 -0
- waiross-0.1.0/app/application/services/__init__.py +0 -0
- waiross-0.1.0/app/application/services/user/__init__.py +0 -0
- waiross-0.1.0/app/application/use_cases/__init__.py +0 -0
- waiross-0.1.0/app/application/use_cases/user/__init__.py +0 -0
- waiross-0.1.0/app/application/use_cases/user/create_user_use_case.py +41 -0
- waiross-0.1.0/app/application/use_cases/user/delete_user_use_case.py +19 -0
- waiross-0.1.0/app/application/use_cases/user/get_user_use_case.py +19 -0
- waiross-0.1.0/app/application/use_cases/user/list_users_use_case.py +16 -0
- waiross-0.1.0/app/application/use_cases/user/login_use_case.py +44 -0
- waiross-0.1.0/app/application/use_cases/user/update_user_use_case.py +29 -0
- waiross-0.1.0/app/config/__init__.py +0 -0
- waiross-0.1.0/app/config/logging.py +23 -0
- waiross-0.1.0/app/config/settings.py +70 -0
- waiross-0.1.0/app/di/__init__.py +0 -0
- waiross-0.1.0/app/di/autodiscovery.py +94 -0
- waiross-0.1.0/app/di/bootstrap.py +43 -0
- waiross-0.1.0/app/di/container.py +114 -0
- waiross-0.1.0/app/domain/__init__.py +0 -0
- waiross-0.1.0/app/domain/entities/__init__.py +0 -0
- waiross-0.1.0/app/domain/entities/user_entity.py +65 -0
- waiross-0.1.0/app/domain/enums/__init__.py +0 -0
- waiross-0.1.0/app/domain/enums/user_status.py +9 -0
- waiross-0.1.0/app/domain/events/__init__.py +0 -0
- waiross-0.1.0/app/domain/exceptions/__init__.py +35 -0
- waiross-0.1.0/app/domain/exceptions/already_exists_exception.py +5 -0
- waiross-0.1.0/app/domain/exceptions/app_exception.py +12 -0
- waiross-0.1.0/app/domain/exceptions/not_found_exception.py +5 -0
- waiross-0.1.0/app/domain/exceptions/unauthorized_exception.py +5 -0
- waiross-0.1.0/app/domain/exceptions/user/__init__.py +0 -0
- waiross-0.1.0/app/domain/exceptions/user/already_exists_error.py +5 -0
- waiross-0.1.0/app/domain/exceptions/user/inactive_user_error.py +5 -0
- waiross-0.1.0/app/domain/exceptions/user/invalid_credentials_error.py +5 -0
- waiross-0.1.0/app/domain/exceptions/user/invalid_email_error.py +5 -0
- waiross-0.1.0/app/domain/exceptions/user/not_found_error.py +5 -0
- waiross-0.1.0/app/domain/exceptions/user/weak_password_error.py +5 -0
- waiross-0.1.0/app/domain/exceptions/validation_exception.py +5 -0
- waiross-0.1.0/app/domain/repositories/__init__.py +0 -0
- waiross-0.1.0/app/domain/repositories/user_repository.py +41 -0
- waiross-0.1.0/app/domain/services/__init__.py +0 -0
- waiross-0.1.0/app/domain/value_objects/__init__.py +0 -0
- waiross-0.1.0/app/domain/value_objects/user_email.py +27 -0
- waiross-0.1.0/app/domain/value_objects/user_password.py +22 -0
- waiross-0.1.0/app/infrastructure/__init__.py +0 -0
- waiross-0.1.0/app/infrastructure/cache/__init__.py +0 -0
- waiross-0.1.0/app/infrastructure/cache/cache.py +24 -0
- waiross-0.1.0/app/infrastructure/cache/redis_cache.py +19 -0
- waiross-0.1.0/app/infrastructure/database/__init__.py +0 -0
- waiross-0.1.0/app/infrastructure/database/base.py +13 -0
- waiross-0.1.0/app/infrastructure/database/models/__init__.py +11 -0
- waiross-0.1.0/app/infrastructure/database/models/user_model.py +31 -0
- waiross-0.1.0/app/infrastructure/database/session.py +37 -0
- waiross-0.1.0/app/infrastructure/http/__init__.py +0 -0
- waiross-0.1.0/app/infrastructure/http/http_client.py +33 -0
- waiross-0.1.0/app/infrastructure/integrations/__init__.py +0 -0
- waiross-0.1.0/app/infrastructure/mail/__init__.py +0 -0
- waiross-0.1.0/app/infrastructure/mail/console_email_sender.py +18 -0
- waiross-0.1.0/app/infrastructure/mail/email_sender.py +11 -0
- waiross-0.1.0/app/infrastructure/queue/__init__.py +0 -0
- waiross-0.1.0/app/infrastructure/queue/celery_app.py +36 -0
- waiross-0.1.0/app/infrastructure/queue/tasks/__init__.py +0 -0
- waiross-0.1.0/app/infrastructure/queue/tasks/create_user_task.py +52 -0
- waiross-0.1.0/app/infrastructure/repositories/__init__.py +0 -0
- waiross-0.1.0/app/infrastructure/repositories/sqlalchemy_user_repository.py +90 -0
- waiross-0.1.0/app/main.py +76 -0
- waiross-0.1.0/app/presentation/__init__.py +0 -0
- waiross-0.1.0/app/presentation/cli/__init__.py +0 -0
- waiross-0.1.0/app/presentation/cli/main.py +117 -0
- waiross-0.1.0/app/presentation/grpc/__init__.py +0 -0
- waiross-0.1.0/app/presentation/grpc/server.py +40 -0
- waiross-0.1.0/app/presentation/grpc/users.proto +52 -0
- waiross-0.1.0/app/presentation/grpc/users_servicer.py +112 -0
- waiross-0.1.0/app/presentation/http/__init__.py +0 -0
- waiross-0.1.0/app/presentation/http/dependencies.py +49 -0
- waiross-0.1.0/app/presentation/http/routes/__init__.py +0 -0
- waiross-0.1.0/app/presentation/http/routes/http_router.py +22 -0
- waiross-0.1.0/app/presentation/http/routes/v1/__init__.py +0 -0
- waiross-0.1.0/app/presentation/http/routes/v1/auth_router.py +28 -0
- waiross-0.1.0/app/presentation/http/routes/v1/health_router.py +32 -0
- waiross-0.1.0/app/presentation/http/routes/v1/users_router.py +127 -0
- waiross-0.1.0/app/presentation/http/schemas/__init__.py +0 -0
- waiross-0.1.0/app/presentation/http/schemas/auth/__init__.py +0 -0
- waiross-0.1.0/app/presentation/http/schemas/auth/login_schema.py +8 -0
- waiross-0.1.0/app/presentation/http/schemas/auth/token_response_schema.py +19 -0
- waiross-0.1.0/app/presentation/http/schemas/health/__init__.py +0 -0
- waiross-0.1.0/app/presentation/http/schemas/health/health_response_schema.py +9 -0
- waiross-0.1.0/app/presentation/http/schemas/user/__init__.py +0 -0
- waiross-0.1.0/app/presentation/http/schemas/user/create_user_schema.py +9 -0
- waiross-0.1.0/app/presentation/http/schemas/user/update_user_schema.py +10 -0
- waiross-0.1.0/app/presentation/http/schemas/user/user_list_response_schema.py +14 -0
- waiross-0.1.0/app/presentation/http/schemas/user/user_response_schema.py +33 -0
- waiross-0.1.0/app/presentation/middleware/__init__.py +0 -0
- waiross-0.1.0/app/presentation/middleware/exception_handler.py +33 -0
- waiross-0.1.0/app/presentation/middleware/handlers/__init__.py +0 -0
- waiross-0.1.0/app/presentation/middleware/handlers/already_exists_exception_handler.py +14 -0
- waiross-0.1.0/app/presentation/middleware/handlers/app_exception_handler.py +21 -0
- waiross-0.1.0/app/presentation/middleware/handlers/not_found_exception_handler.py +14 -0
- waiross-0.1.0/app/presentation/middleware/handlers/registry.py +54 -0
- waiross-0.1.0/app/presentation/middleware/handlers/unauthorized_exception_handler.py +16 -0
- waiross-0.1.0/app/presentation/middleware/handlers/unhandled_exception_handler.py +25 -0
- waiross-0.1.0/app/presentation/middleware/handlers/validation_exception_handler.py +16 -0
- waiross-0.1.0/app/presentation/websocket/__init__.py +0 -0
- waiross-0.1.0/app/presentation/websocket/routes/__init__.py +0 -0
- waiross-0.1.0/app/presentation/websocket/routes/user_websocket.py +132 -0
- waiross-0.1.0/app/presentation/websocket/ws_router.py +14 -0
- waiross-0.1.0/app/security/__init__.py +0 -0
- waiross-0.1.0/app/security/bcrypt_password_hasher.py +33 -0
- waiross-0.1.0/app/security/jwt_provider.py +29 -0
- waiross-0.1.0/app/security/password_hasher.py +21 -0
- waiross-0.1.0/app/security/pyjwt_provider.py +54 -0
- waiross-0.1.0/app/security/token_payload.py +10 -0
- waiross-0.1.0/app/shared/__init__.py +0 -0
- waiross-0.1.0/app/shared/datetime_utils.py +24 -0
- waiross-0.1.0/docker-compose.yml +54 -0
- waiross-0.1.0/migrations/env.py +66 -0
- waiross-0.1.0/migrations/script.py.mako +26 -0
- waiross-0.1.0/migrations/versions/98b88b47ae11_create_users_table.py +38 -0
- waiross-0.1.0/pyproject.toml +68 -0
- waiross-0.1.0/tests/__init__.py +0 -0
- waiross-0.1.0/tests/conftest.py +42 -0
- waiross-0.1.0/tests/test_health.py +10 -0
- waiross-0.1.0/tests/test_users_http.py +65 -0
- waiross-0.1.0/uv.lock +2245 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# ------------------------------------------------------------------
|
|
2
|
+
# Configuração da aplicação. Copie este arquivo para ".env" e ajuste.
|
|
3
|
+
# Todas as variáveis são lidas por app/config/settings.py (pydantic-settings).
|
|
4
|
+
# ------------------------------------------------------------------
|
|
5
|
+
|
|
6
|
+
# Geral
|
|
7
|
+
APP_NAME="Waiross"
|
|
8
|
+
APP_ENV=development # development | staging | production
|
|
9
|
+
DEBUG=true
|
|
10
|
+
API_V1_PREFIX=/api/v1
|
|
11
|
+
|
|
12
|
+
# Banco de dados (PostgreSQL, driver assíncrono asyncpg)
|
|
13
|
+
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/waiross
|
|
14
|
+
|
|
15
|
+
# Segurança / JWT
|
|
16
|
+
JWT_SECRET_KEY=change-me-to-a-long-random-string
|
|
17
|
+
JWT_ALGORITHM=HS256
|
|
18
|
+
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
|
19
|
+
REFRESH_TOKEN_EXPIRE_DAYS=7
|
|
20
|
+
|
|
21
|
+
# Redis (cache e broker do Celery)
|
|
22
|
+
REDIS_URL=redis://localhost:6379/0
|
|
23
|
+
|
|
24
|
+
# Celery (fila — quarto adaptador de protocolo, veja app/infrastructure/queue/)
|
|
25
|
+
CELERY_BROKER_URL=redis://localhost:6379/1
|
|
26
|
+
CELERY_RESULT_BACKEND=redis://localhost:6379/2
|
|
27
|
+
|
|
28
|
+
# Armazenamento local (app/infrastructure/storage)
|
|
29
|
+
LOCAL_STORAGE_PATH=./storage
|
|
30
|
+
|
|
31
|
+
# CORS
|
|
32
|
+
CORS_ALLOW_ORIGINS=http://localhost:3000
|
|
33
|
+
|
|
34
|
+
# gRPC (terceiro adaptador de protocolo — processo separado, `make grpc-server`)
|
|
35
|
+
GRPC_PORT=50051
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
name: Build package
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- name: Checkout
|
|
13
|
+
uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v3
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
|
|
20
|
+
- name: Build
|
|
21
|
+
run: uv build
|
|
22
|
+
|
|
23
|
+
- name: Check package
|
|
24
|
+
run: uvx twine check dist/*
|
|
25
|
+
|
|
26
|
+
- name: Upload artifacts
|
|
27
|
+
uses: actions/upload-artifact@v4
|
|
28
|
+
with:
|
|
29
|
+
name: distributions
|
|
30
|
+
path: dist/
|
|
31
|
+
|
|
32
|
+
publish:
|
|
33
|
+
name: Publish to PyPI
|
|
34
|
+
needs: build
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
environment:
|
|
37
|
+
name: pypi
|
|
38
|
+
url: https://pypi.org/p/waiross
|
|
39
|
+
permissions:
|
|
40
|
+
id-token: write
|
|
41
|
+
steps:
|
|
42
|
+
- name: Download artifacts
|
|
43
|
+
uses: actions/download-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: distributions
|
|
46
|
+
path: dist/
|
|
47
|
+
|
|
48
|
+
- name: Publish to PyPI
|
|
49
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
waiross-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.venv/
|
|
5
|
+
venv/
|
|
6
|
+
.env
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
*.sqlite3
|
|
11
|
+
storage/
|
|
12
|
+
.DS_Store
|
|
13
|
+
dist/
|
|
14
|
+
build/
|
|
15
|
+
|
|
16
|
+
# Estrutura antiga (pré-arquitetura modular) — o sandbox que gerou este
|
|
17
|
+
# projeto não conseguiu apagar esses arquivos do disco por uma restrição
|
|
18
|
+
# do sistema de arquivos montado; nada no projeto os importa. Apague esta
|
|
19
|
+
# pasta manualmente quando puder.
|
|
20
|
+
_legacy_delete_me/
|
waiross-0.1.0/Dockerfile
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
FROM python:3.11-slim
|
|
2
|
+
|
|
3
|
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
4
|
+
PYTHONUNBUFFERED=1
|
|
5
|
+
|
|
6
|
+
WORKDIR /code
|
|
7
|
+
|
|
8
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
|
9
|
+
COPY pyproject.toml ./
|
|
10
|
+
RUN uv sync --no-dev --no-install-project
|
|
11
|
+
|
|
12
|
+
COPY . .
|
|
13
|
+
RUN uv sync --no-dev
|
|
14
|
+
|
|
15
|
+
EXPOSE 8000
|
|
16
|
+
|
|
17
|
+
CMD ["uv", "run", "fastapi", "run", "app/main.py", "--host", "0.0.0.0", "--port", "8000"]
|
waiross-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tech-iross
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
waiross-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
.PHONY: install run dev migrate migration downgrade test lint worker proto grpc-server cli
|
|
2
|
+
|
|
3
|
+
install:
|
|
4
|
+
uv sync
|
|
5
|
+
|
|
6
|
+
run:
|
|
7
|
+
uv run fastapi run app/main.py --host 0.0.0.0 --port 8000
|
|
8
|
+
|
|
9
|
+
dev:
|
|
10
|
+
uv run fastapi dev app/main.py --host 0.0.0.0 --port 8000
|
|
11
|
+
|
|
12
|
+
migration:
|
|
13
|
+
uv run alembic revision --autogenerate -m "$(m)"
|
|
14
|
+
|
|
15
|
+
migrate:
|
|
16
|
+
uv run alembic upgrade head
|
|
17
|
+
|
|
18
|
+
downgrade:
|
|
19
|
+
uv run alembic downgrade -1
|
|
20
|
+
|
|
21
|
+
test:
|
|
22
|
+
uv run pytest -v
|
|
23
|
+
|
|
24
|
+
lint:
|
|
25
|
+
uv run ruff check app tests
|
|
26
|
+
|
|
27
|
+
worker:
|
|
28
|
+
uv run celery -A app.infrastructure.queue.celery_app worker --loglevel=info
|
|
29
|
+
|
|
30
|
+
# CLI de administração (quinto adaptador de protocolo) — ex.: `make cli args="user:list"`.
|
|
31
|
+
cli:
|
|
32
|
+
uv run waiross $(args)
|
|
33
|
+
|
|
34
|
+
# Gera os stubs Python (users_pb2.py, users_pb2_grpc.py) a partir do
|
|
35
|
+
# users.proto. Requer `uv sync --extra grpc`.
|
|
36
|
+
proto:
|
|
37
|
+
uv run python -m grpc_tools.protoc \
|
|
38
|
+
-I app/presentation/grpc \
|
|
39
|
+
--python_out=app/presentation/grpc \
|
|
40
|
+
--grpc_python_out=app/presentation/grpc \
|
|
41
|
+
app/presentation/grpc/users.proto
|
|
42
|
+
|
|
43
|
+
# Sobe o servidor gRPC (processo separado do FastAPI).
|
|
44
|
+
# Rode `make proto` antes, na primeira vez.
|
|
45
|
+
grpc-server:
|
|
46
|
+
uv run python -m app.presentation.grpc.server
|
waiross-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: waiross
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Waiross — framework HTTP/WebSocket/gRPC/CLI construído sobre FastAPI
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: alembic>=1.13.2
|
|
9
|
+
Requires-Dist: asyncpg>=0.29.0
|
|
10
|
+
Requires-Dist: bcrypt>=4.1.0
|
|
11
|
+
Requires-Dist: celery>=5.4.0
|
|
12
|
+
Requires-Dist: email-validator>=2.2.0
|
|
13
|
+
Requires-Dist: fastapi[standard]>=0.115.0
|
|
14
|
+
Requires-Dist: pydantic-settings>=2.5.0
|
|
15
|
+
Requires-Dist: pydantic>=2.9.0
|
|
16
|
+
Requires-Dist: pyjwt>=2.9.0
|
|
17
|
+
Requires-Dist: python-multipart>=0.0.12
|
|
18
|
+
Requires-Dist: redis>=5.0.8
|
|
19
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0.35
|
|
20
|
+
Requires-Dist: typer>=0.12.5
|
|
21
|
+
Requires-Dist: uvicorn[standard]>=0.32.0
|
|
22
|
+
Provides-Extra: grpc
|
|
23
|
+
Requires-Dist: grpcio-tools>=1.66.0; extra == 'grpc'
|
|
24
|
+
Requires-Dist: grpcio>=1.66.0; extra == 'grpc'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# Waiross
|
|
28
|
+
|
|
29
|
+
Um framework construído sobre FastAPI, no espírito do Laravel: convenção
|
|
30
|
+
clara, injeção de dependências automática e independência de protocolo de
|
|
31
|
+
transporte. Nasceu como boilerplate de um projeto e foi desenhado para
|
|
32
|
+
crescer — a ideia é que qualquer projeto novo comece copiando isto.
|
|
33
|
+
|
|
34
|
+
## Requisitos
|
|
35
|
+
|
|
36
|
+
- Python 3.10+
|
|
37
|
+
- [uv](https://docs.astral.sh/uv/) — gerenciador de pacotes (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
|
|
38
|
+
- Docker + Docker Compose (opcional, mas recomendado para subir Postgres/Redis localmente)
|
|
39
|
+
|
|
40
|
+
## Instalação
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git clone <url-do-seu-fork-ou-template> meu-projeto
|
|
44
|
+
cd meu-projeto
|
|
45
|
+
|
|
46
|
+
cp .env.example .env # ajuste DATABASE_URL e JWT_SECRET_KEY antes de ir pra produção
|
|
47
|
+
make install # uv sync — cria .venv e instala as dependências
|
|
48
|
+
|
|
49
|
+
docker compose up -d db redis # sobe Postgres + Redis localmente
|
|
50
|
+
make migrate # aplica as migrations (alembic upgrade head)
|
|
51
|
+
make dev # sobe a aplicação em http://localhost:8000
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Confirme que subiu certo: `curl http://localhost:8000/api/v1/health` deve
|
|
55
|
+
responder `{"status":"ok","app_name":"Waiross",...}`. Swagger interativo em
|
|
56
|
+
`http://localhost:8000/docs`.
|
|
57
|
+
|
|
58
|
+
Ou tudo via Docker Compose (API + Postgres + Redis + worker Celery, sem
|
|
59
|
+
instalar nada localmente):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
docker compose up --build
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Filosofia
|
|
66
|
+
|
|
67
|
+
- **Organização por camada técnica, não por módulo de negócio.** Tudo que
|
|
68
|
+
é regra de negócio pura mora em `domain/`, toda orquestração em
|
|
69
|
+
`application/`, toda implementação concreta em `infrastructure/`, todo
|
|
70
|
+
adaptador de protocolo em `presentation/`. Para achar qualquer coisa,
|
|
71
|
+
você só precisa saber "que tipo de código é isto" — a árvore é sempre a
|
|
72
|
+
mesma, previsível, para qualquer tamanho de sistema.
|
|
73
|
+
- **Duas convenções de nomenclatura, conforme o volume esperado por
|
|
74
|
+
entidade.** Camadas onde uma entidade normalmente tem só 1-2 arquivos
|
|
75
|
+
(`domain/entities/`, `domain/value_objects/`, `domain/enums/`,
|
|
76
|
+
`domain/repositories/`, `infrastructure/repositories/`,
|
|
77
|
+
`infrastructure/database/models/`, `infrastructure/queue/tasks/`) ficam
|
|
78
|
+
flat, com o nome da entidade como prefixo do arquivo (ex.:
|
|
79
|
+
`domain/entities/user_entity.py`). Camadas onde uma entidade acumula
|
|
80
|
+
vários arquivos (`application/dto/`, `application/use_cases/`,
|
|
81
|
+
`application/services/`, `domain/exceptions/`,
|
|
82
|
+
`presentation/http/schemas/`) ganham uma subpasta por entidade (ex.:
|
|
83
|
+
`application/use_cases/user/create_user_use_case.py`), pra não virar uma
|
|
84
|
+
pasta única lotada de dezenas de arquivos.
|
|
85
|
+
- **HTTP é só mais um adaptador.** `domain/` e `application/` não sabem o
|
|
86
|
+
que é FastAPI, JSON, WebSocket ou gRPC. Um caso de uso é uma classe
|
|
87
|
+
Python comum com um método `execute()`. Isso é o que permite plugar
|
|
88
|
+
HTTP, WebSocket, gRPC, uma fila (Celery) ou uma CLI sem duplicar regra
|
|
89
|
+
de negócio — veja os cinco adaptadores de usuários mais abaixo, todos
|
|
90
|
+
chamando os mesmos casos de uso.
|
|
91
|
+
- **Convenção sobre configuração — inclusive na injeção de dependências.**
|
|
92
|
+
Um caso de uso novo é sempre um arquivo em
|
|
93
|
+
`application/use_cases/<entidade>/`. Um repositório novo (interface em
|
|
94
|
+
`domain/repositories/<entidade>/`, implementação em
|
|
95
|
+
`infrastructure/repositories/<entidade>/`) é descoberto e ligado
|
|
96
|
+
automaticamente no container — ninguém precisa abrir `di/bootstrap.py`
|
|
97
|
+
pra registrar isso na mão. Veja "Injeção de dependências" abaixo.
|
|
98
|
+
- **1 arquivo → 1 classe.** Sem exceções. Facilita navegação, `git blame`
|
|
99
|
+
e review — cada arquivo tem uma única razão para mudar.
|
|
100
|
+
|
|
101
|
+
## Árvore de diretórios
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
app/
|
|
105
|
+
├── domain/ # regra de negócio pura — zero FastAPI/SQLAlchemy/HTTP
|
|
106
|
+
│ ├── entities/ user_entity.py (classe User)
|
|
107
|
+
│ ├── value_objects/ user_email.py, user_password.py (Email, PlainPassword)
|
|
108
|
+
│ ├── enums/ user_status.py (UserStatus)
|
|
109
|
+
│ ├── repositories/ user_repository.py (interface UserRepository)
|
|
110
|
+
│ ├── exceptions/ AppException + subclasses genéricas (raiz)
|
|
111
|
+
│ │ └── user/ not_found_error.py, already_exists_error.py, ... (específicas de User)
|
|
112
|
+
│ ├── services/ regras de negócio que cruzam mais de uma entidade
|
|
113
|
+
│ └── events/ eventos de domínio (futuro: event bus)
|
|
114
|
+
│
|
|
115
|
+
├── application/ # casos de uso — orquestram domain + portas
|
|
116
|
+
│ ├── use_cases/user/ create_user_use_case.py, login_use_case.py, ... (1 classe cada)
|
|
117
|
+
│ ├── dto/user/ create_user_input.py, user_output.py, ...
|
|
118
|
+
│ └── services/user/ serviços de aplicação que orquestram mais de um caso de uso
|
|
119
|
+
│
|
|
120
|
+
├── infrastructure/ # implementações concretas das portas do domain
|
|
121
|
+
│ ├── database/ engine/sessão SQLAlchemy, Base declarativa
|
|
122
|
+
│ │ └── models/ user_model.py (UserModel)
|
|
123
|
+
│ ├── repositories/ sqlalchemy_user_repository.py (implementa UserRepository)
|
|
124
|
+
│ ├── cache/ porta Cache + RedisCache
|
|
125
|
+
│ ├── queue/ Celery (config do broker)
|
|
126
|
+
│ │ └── tasks/ create_user_task.py
|
|
127
|
+
│ ├── storage/ porta FileStorage + LocalFileStorage
|
|
128
|
+
│ ├── mail/ porta EmailSender + ConsoleEmailSender
|
|
129
|
+
│ ├── http/ cliente HTTP de saída (outbound) para APIs externas
|
|
130
|
+
│ └── integrations/ SDKs de terceiros (pagamentos, e-mail transacional etc.)
|
|
131
|
+
│
|
|
132
|
+
├── presentation/ # adaptadores de entrada — um por protocolo
|
|
133
|
+
│ ├── http/
|
|
134
|
+
│ │ ├── routes/
|
|
135
|
+
│ │ │ ├── http_router.py ← ponto de entrada, chamado em main.py
|
|
136
|
+
│ │ │ └── v1/ auth_router.py, health_router.py, users_router.py
|
|
137
|
+
│ │ └── schemas/ Pydantic (request/response), também por entidade
|
|
138
|
+
│ ├── websocket/
|
|
139
|
+
│ │ ├── ws_router.py ← ponto de entrada, chamado em main.py
|
|
140
|
+
│ │ └── routes/ user_websocket.py
|
|
141
|
+
│ ├── grpc/ users.proto + servicer (requer codegen)
|
|
142
|
+
│ ├── cli/ comandos `waiross ...` (Typer)
|
|
143
|
+
│ └── middleware/ exception handlers HTTP estilo Laravel
|
|
144
|
+
│
|
|
145
|
+
├── security/ # PasswordHasher, JWTProvider (portas + implementações)
|
|
146
|
+
├── config/ # Settings (pydantic-settings) + logging — única fonte de env vars
|
|
147
|
+
├── di/ # injeção de dependências (o "motor")
|
|
148
|
+
│ ├── container.py Container genérico: bind()/resolve()
|
|
149
|
+
│ ├── autodiscovery.py descobre e liga interface -> implementação sozinho
|
|
150
|
+
│ └── bootstrap.py registra infraestrutura de base + chama o autodiscovery
|
|
151
|
+
├── shared/ # utilidades técnicas sem estado (ex.: datetime_utils.py)
|
|
152
|
+
└── main.py # composition root: monta o container, registra
|
|
153
|
+
# exception handlers e inclui http_router + ws_router
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
A regra de dependência é sempre "de fora para dentro":
|
|
157
|
+
`presentation → application → domain`, e `infrastructure` implementa
|
|
158
|
+
portas definidas em `domain`. Nada dentro de `domain/` importa de
|
|
159
|
+
`infrastructure/` ou `presentation/`.
|
|
160
|
+
|
|
161
|
+
| Camada | Sabe sobre | Não sabe sobre |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| `domain/` | Regras de negócio, entidades, value objects | FastAPI, SQLAlchemy, HTTP, JSON, protobuf |
|
|
164
|
+
| `application/` | `domain/`, portas (interfaces) | Qual protocolo chamou o caso de uso, qual banco é usado |
|
|
165
|
+
| `infrastructure/` | `domain/`, SQLAlchemy, bibliotecas externas | FastAPI, schemas HTTP |
|
|
166
|
+
| `presentation/` | `application/`, o protocolo específico (HTTP/WS/gRPC/fila/CLI) | Como o caso de uso é implementado por dentro |
|
|
167
|
+
|
|
168
|
+
## Injeção de dependências (automática)
|
|
169
|
+
|
|
170
|
+
Em vez de escrever uma função `get_x_use_case(...)` para cada caso de uso,
|
|
171
|
+
o container resolve automaticamente qualquer classe cujo `__init__` peça
|
|
172
|
+
tipos que ele conhece:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
# em qualquer rota:
|
|
176
|
+
from app.di.container import container
|
|
177
|
+
from app.application.use_cases.user.create_user_use_case import CreateUserUseCase
|
|
178
|
+
|
|
179
|
+
@router.post("/users")
|
|
180
|
+
async def create_user(
|
|
181
|
+
payload: CreateUserSchema,
|
|
182
|
+
use_case: Annotated[CreateUserUseCase, Depends(container.resolve(CreateUserUseCase))],
|
|
183
|
+
):
|
|
184
|
+
...
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`container.resolve(CreateUserUseCase)` lê os type hints do `__init__` de
|
|
188
|
+
`CreateUserUseCase`, vê que ele precisa de um `UserRepository` e de um
|
|
189
|
+
`PasswordHasher`, e resolve os dois recursivamente. Você nunca escreve
|
|
190
|
+
essa fiação manualmente.
|
|
191
|
+
|
|
192
|
+
**O binding interface -> implementação também é automático.** `di/bootstrap.py`
|
|
193
|
+
registra só duas coisas de infraestrutura genérica (`AsyncSession` via
|
|
194
|
+
`get_db_session`, `Settings` via `get_settings`) e chama
|
|
195
|
+
`di/autodiscovery.py`, que varre `domain/repositories/` + `security/`
|
|
196
|
+
(interfaces) e `infrastructure/repositories/` + `security/`
|
|
197
|
+
(implementações), casa cada uma pela herança e registra o binding
|
|
198
|
+
sozinho. Para adicionar uma entidade nova:
|
|
199
|
+
|
|
200
|
+
1. Crie a interface em `domain/repositories/<entidade>_repository.py`
|
|
201
|
+
(uma `ABC` com `@abstractmethod`).
|
|
202
|
+
2. Crie a implementação em
|
|
203
|
+
`infrastructure/repositories/sqlalchemy_<entidade>_repository.py`
|
|
204
|
+
(herdando da interface, recebendo `session: AsyncSession` no
|
|
205
|
+
`__init__`).
|
|
206
|
+
3. Pronto — na próxima vez que a aplicação subir, o binding já existe.
|
|
207
|
+
**Nenhum arquivo de `di/` precisa ser tocado.**
|
|
208
|
+
|
|
209
|
+
Isso só não funciona se o construtor da implementação pedir algo que o
|
|
210
|
+
container não sabe resolver por tipo (nesse caso, registre-a manualmente
|
|
211
|
+
em `di/bootstrap.py` com `container.bind(...)` — é a saída de escape,
|
|
212
|
+
não o caminho padrão).
|
|
213
|
+
|
|
214
|
+
> **Ordem de import importa.** `container.resolve(...)` roda no momento em
|
|
215
|
+
> que o módulo de rota é *importado*, não a cada requisição. Por isso
|
|
216
|
+
> `app/main.py` chama `register_providers()` **antes** de importar
|
|
217
|
+
> `http_router`/`ws_router` — se você criar um novo arquivo de rota,
|
|
218
|
+
> garanta que ele só seja alcançado por um desses dois pontos de entrada
|
|
219
|
+
> depois do bootstrap (o padrão já existente em `main.py` cobre isso).
|
|
220
|
+
|
|
221
|
+
## Tratamento de exceções (estilo Laravel)
|
|
222
|
+
|
|
223
|
+
Nenhuma exceção de negócio vira `HTTPException` dentro de `application/`
|
|
224
|
+
ou `domain/`. Em vez disso:
|
|
225
|
+
|
|
226
|
+
1. A exceção herda de `AppException` (`domain/exceptions/app_exception.py`)
|
|
227
|
+
ou de uma subclasse genérica (`NotFoundException`, `ValidationException`,
|
|
228
|
+
`AlreadyExistsException`, `UnauthorizedException`). Exceções específicas
|
|
229
|
+
de uma entidade ficam em `domain/exceptions/<entidade>/`, um arquivo por
|
|
230
|
+
classe (veja `domain/exceptions/user/`).
|
|
231
|
+
2. Uma classe herdando de `ExceptionHandler`
|
|
232
|
+
(`presentation/middleware/exception_handler.py`) traduz aquela exceção
|
|
233
|
+
para uma resposta HTTP — uma classe por arquivo, em
|
|
234
|
+
`presentation/middleware/handlers/*.py`.
|
|
235
|
+
3. Ela é adicionada à lista `HANDLERS` em
|
|
236
|
+
`presentation/middleware/handlers/registry.py`.
|
|
237
|
+
|
|
238
|
+
O Starlette escolhe automaticamente o handler mais específico percorrendo
|
|
239
|
+
o MRO da exceção, então a ordem na lista não importa. Adaptadores
|
|
240
|
+
não-HTTP (WebSocket, gRPC, fila, CLI) capturam `AppException` diretamente
|
|
241
|
+
e traduzem para o formato deles.
|
|
242
|
+
|
|
243
|
+
## Protocolos suportados
|
|
244
|
+
|
|
245
|
+
O domínio de usuários implementa cinco adaptadores de entrada sobre os
|
|
246
|
+
mesmos casos de uso, provando a independência de protocolo:
|
|
247
|
+
|
|
248
|
+
### HTTP (REST)
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
POST /api/v1/users cria um usuário
|
|
252
|
+
GET /api/v1/users lista usuários (paginado)
|
|
253
|
+
GET /api/v1/users/me usuário autenticado (Bearer token)
|
|
254
|
+
GET /api/v1/users/{id} busca por ID
|
|
255
|
+
PATCH /api/v1/users/{id} atualiza
|
|
256
|
+
DELETE /api/v1/users/{id} remove
|
|
257
|
+
POST /api/v1/auth/login login (retorna access + refresh token)
|
|
258
|
+
GET /api/v1/health health check
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Swagger: `http://localhost:8000/docs` · ReDoc: `http://localhost:8000/redoc`
|
|
262
|
+
|
|
263
|
+
Toda rota HTTP entra pelo mesmo ponto:
|
|
264
|
+
`presentation/http/routes/http_router.py` agrega as versões
|
|
265
|
+
(`routes/v1/`, e no futuro `routes/v2/`) — `main.py` só conhece esse
|
|
266
|
+
arquivo, nunca uma rota individual.
|
|
267
|
+
|
|
268
|
+
### WebSocket
|
|
269
|
+
|
|
270
|
+
`ws://localhost:8000/ws/users` — mensagens JSON com um campo `action`
|
|
271
|
+
(`create`, `list`, `get`). Veja o docstring de
|
|
272
|
+
`app/presentation/websocket/routes/user_websocket.py` para o protocolo
|
|
273
|
+
completo e um exemplo de cliente Python. Assim como o HTTP,
|
|
274
|
+
`presentation/websocket/ws_router.py` é o único ponto de entrada que
|
|
275
|
+
`main.py` conhece.
|
|
276
|
+
|
|
277
|
+
### gRPC
|
|
278
|
+
|
|
279
|
+
Contrato em `app/presentation/grpc/users.proto`. Roda como processo
|
|
280
|
+
separado (não é montado dentro do FastAPI):
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
uv sync --extra grpc # instala grpcio + grpcio-tools (opcional)
|
|
284
|
+
make proto # gera users_pb2.py / users_pb2_grpc.py
|
|
285
|
+
make grpc-server # sobe o servidor na porta GRPC_PORT (padrão 50051)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Filas (Celery)
|
|
289
|
+
|
|
290
|
+
`app/infrastructure/queue/tasks/create_user_task.py` expõe
|
|
291
|
+
`create_user_task`, reaproveitando `CreateUserUseCase` em background:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
make worker # sobe o worker Celery
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
from app.infrastructure.queue.tasks.create_user_task import create_user_task
|
|
299
|
+
create_user_task.delay("jane@example.com", "Jane Doe", "senha-forte")
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### CLI
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
uv run waiross --help
|
|
306
|
+
uv run waiross user:create
|
|
307
|
+
uv run waiross user:list
|
|
308
|
+
uv run waiross user:show <id>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Veja `app/presentation/cli/main.py`.
|
|
312
|
+
|
|
313
|
+
## Como criar uma entidade nova (ex.: `product`)
|
|
314
|
+
|
|
315
|
+
1. `domain/entities/product_entity.py` (classe `Product`),
|
|
316
|
+
`domain/value_objects/product_*.py` se precisar,
|
|
317
|
+
`domain/repositories/product_repository.py` (interface
|
|
318
|
+
`ProductRepository`), `domain/exceptions/product/` para as exceções
|
|
319
|
+
específicas.
|
|
320
|
+
2. `application/use_cases/product/create_product_use_case.py` (e demais
|
|
321
|
+
casos de uso), `application/dto/product/create_product_input.py` (e
|
|
322
|
+
demais DTOs).
|
|
323
|
+
3. `infrastructure/database/models/product_model.py` (adicione o import
|
|
324
|
+
em `infrastructure/database/models/__init__.py`),
|
|
325
|
+
`infrastructure/repositories/sqlalchemy_product_repository.py`
|
|
326
|
+
implementando `ProductRepository` — **não precisa registrar nada em
|
|
327
|
+
`di/`**, o autodiscovery liga sozinho na próxima subida.
|
|
328
|
+
4. `presentation/http/schemas/product/` (Pydantic) e
|
|
329
|
+
`presentation/http/routes/v1/products_router.py`, usando
|
|
330
|
+
`Depends(container.resolve(SeuCasoDeUso))`. Inclua o router novo em
|
|
331
|
+
`presentation/http/routes/http_router.py`.
|
|
332
|
+
5. Se alguma exceção nova precisar de um status HTTP específico, crie o
|
|
333
|
+
handler em `presentation/middleware/handlers/` e registre em
|
|
334
|
+
`registry.py` (senão, o fallback `AppExceptionHandler` já cobre com
|
|
335
|
+
HTTP 400).
|
|
336
|
+
6. `make migration m="create products table"` seguido de `make migrate`.
|
|
337
|
+
7. (Opcional) Repita o padrão de `presentation/websocket/routes/`,
|
|
338
|
+
`presentation/grpc/` e `infrastructure/queue/tasks/` se esta entidade
|
|
339
|
+
também precisar desses protocolos.
|
|
340
|
+
|
|
341
|
+
## Stack
|
|
342
|
+
|
|
343
|
+
- FastAPI + Uvicorn (HTTP e WebSocket no mesmo processo)
|
|
344
|
+
- gRPC (`grpcio`, opcional — extra `grpc`) rodando como processo separado
|
|
345
|
+
- Typer (CLI — `waiross`)
|
|
346
|
+
- Celery + Redis (fila)
|
|
347
|
+
- SQLAlchemy 2.0 (async, driver `asyncpg`) + Alembic para migrations
|
|
348
|
+
- PostgreSQL
|
|
349
|
+
- JWT (access + refresh token) via PyJWT, senhas com `bcrypt` (lib pura, sem passlib)
|
|
350
|
+
- `uv` como gerenciador de pacotes
|
|
351
|
+
|
|
352
|
+
## Testes
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
make test
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Os testes de API (`tests/test_users_http.py`, `tests/test_health.py`)
|
|
359
|
+
rodam contra SQLite em memória (fixture em `tests/conftest.py`), sem
|
|
360
|
+
precisar de Postgres/Docker.
|
|
361
|
+
|
|
362
|
+
## Migrations (Alembic)
|
|
363
|
+
|
|
364
|
+
Os modelos ORM vivem em `app/infrastructure/database/models/<entidade>_model.py`;
|
|
365
|
+
`migrations/env.py` importa `infrastructure/database/models/__init__.py`,
|
|
366
|
+
que por sua vez importa cada `XModel`, para o autogenerate enxergar todas
|
|
367
|
+
as tabelas. Ao criar uma entidade nova com tabela própria, adicione o
|
|
368
|
+
import do model novo em `infrastructure/database/models/__init__.py`.
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
make migration m="descrição da mudança" # gera migration por autogenerate
|
|
372
|
+
make migrate # aplica
|
|
373
|
+
make downgrade # desfaz a última
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Comandos (Makefile)
|
|
377
|
+
|
|
378
|
+
| Comando | Descrição |
|
|
379
|
+
|---|---|
|
|
380
|
+
| `make install` | instala dependências (`uv sync`) |
|
|
381
|
+
| `make dev` | sobe HTTP + WebSocket com reload |
|
|
382
|
+
| `make run` | sobe a API em modo produção |
|
|
383
|
+
| `make migration m="..."` | gera nova migration |
|
|
384
|
+
| `make migrate` | aplica migrations pendentes |
|
|
385
|
+
| `make test` | roda a suíte de testes |
|
|
386
|
+
| `make lint` | roda o ruff |
|
|
387
|
+
| `make worker` | sobe um worker Celery |
|
|
388
|
+
| `make cli args="..."` | roda um comando da CLI (`waiross`) |
|
|
389
|
+
| `make proto` | gera os stubs gRPC a partir do `.proto` |
|
|
390
|
+
| `make grpc-server` | sobe o servidor gRPC (processo separado) |
|
|
391
|
+
|
|
392
|
+
## Nota sobre este repositório
|
|
393
|
+
|
|
394
|
+
Este projeto passou por reestruturações "hard" sucessivas até chegar na
|
|
395
|
+
árvore atual (camadas técnicas no topo, entidade como subpasta dentro de
|
|
396
|
+
cada camada, DI auto-discovery). O sandbox que gerou esta versão não
|
|
397
|
+
conseguiu apagar os arquivos antigos do disco (restrição do sistema de
|
|
398
|
+
arquivos montado) — eles foram movidos para `_legacy_delete_me/` na raiz
|
|
399
|
+
do projeto (já no `.gitignore`) e não são importados por nada. Pode
|
|
400
|
+
apagar essa pasta manualmente quando quiser.
|
|
401
|
+
|
|
402
|
+
Publicação como template/framework e próximos passos (site de docs): veja
|
|
403
|
+
`PUBLISHING.md`.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Publicando o Waiross
|
|
2
|
+
|
|
3
|
+
Hoje o Waiross é uma aplicação concreta (tem `users` implementado de ponta a
|
|
4
|
+
ponta) que também serve de template — não é uma biblioteca `pip install`
|
|
5
|
+
abstrata. Isso muda qual caminho de publicação faz sentido primeiro.
|
|
6
|
+
|
|
7
|
+
## Caminho 1 — GitHub Template Repository (comece por aqui)
|
|
8
|
+
|
|
9
|
+
É o formato certo para o que existe hoje: alguém clica em "Use this
|
|
10
|
+
template", ganha uma cópia limpa do repo (sem histórico de commits) e
|
|
11
|
+
começa a apagar/adaptar o que não precisa (o módulo `users` de exemplo,
|
|
12
|
+
o adaptador gRPC, etc.).
|
|
13
|
+
|
|
14
|
+
Passos:
|
|
15
|
+
|
|
16
|
+
1. Crie o repositório no GitHub (se ainda não existir): `github.com/new`.
|
|
17
|
+
2. Suba o código: dentro da pasta do projeto,
|
|
18
|
+
`git init && git add . && git commit -m "Waiross v0.1.0"`, depois
|
|
19
|
+
`git remote add origin <url-do-repo> && git push -u origin main`.
|
|
20
|
+
(Confirme antes que `_legacy_delete_me/` está no `.gitignore` — já
|
|
21
|
+
está.)
|
|
22
|
+
3. No GitHub: **Settings → General → Template repository**, marque a
|
|
23
|
+
caixa. A partir daí o botão "Use this template" aparece na página do
|
|
24
|
+
repo.
|
|
25
|
+
4. Adicione uma licença (`LICENSE` — MIT é a escolha usual para
|
|
26
|
+
boilerplates) e tópicos no GitHub (`fastapi`, `python`, `boilerplate`,
|
|
27
|
+
`ddd`, `clean-architecture`) para aparecer em buscas.
|
|
28
|
+
5. (Opcional) Crie uma release (`git tag v0.1.0 && git push --tags` +
|
|
29
|
+
"Draft a new release" no GitHub) para dar uma versão fixa a quem quiser
|
|
30
|
+
`pip install` uma versão específica do template mais adiante.
|
|
31
|
+
|
|
32
|
+
Onde acessar: tudo isso é feito na própria interface web do GitHub, na
|
|
33
|
+
página do repositório (`Settings`, aba lateral esquerda) — não precisa de
|
|
34
|
+
conta separada nem de aprovação de terceiros.
|
|
35
|
+
|
|
36
|
+
## Caminho 2 — Pacote no PyPI (etapa futura, se fizer sentido)
|
|
37
|
+
|
|
38
|
+
Só vale a pena se o Waiross deixar de ser "aplicação de exemplo + template"
|
|
39
|
+
e virar de fato uma biblioteca instalável (ex.: um pacote `waiross-core` com
|
|
40
|
+
o `Container`, o `ExceptionHandler`, os decorators — sem o módulo `users`
|
|
41
|
+
de exemplo, que ficaria só no template do GitHub). Isso é um trabalho de
|
|
42
|
+
extração, não um passo de publicação.
|
|
43
|
+
|
|
44
|
+
Se/quando chegar lá:
|
|
45
|
+
|
|
46
|
+
1. Crie uma conta em `pypi.org` (e em `test.pypi.org` para testar antes).
|
|
47
|
+
2. Gere um API token em `pypi.org/manage/account/token/`.
|
|
48
|
+
3. `uv build` gera o `.whl`/`.tar.gz` em `dist/`.
|
|
49
|
+
4. `uv publish` (ou `twine upload dist/*`) envia para o PyPI, usando o
|
|
50
|
+
token como credencial.
|
|
51
|
+
5. A partir daí, qualquer pessoa roda `pip install waiross-core` (o nome
|
|
52
|
+
`waiross` sozinho no PyPI pode já estar ocupado — vale checar em
|
|
53
|
+
`pypi.org/project/waiross/` antes e ter um nome alternativo em mente,
|
|
54
|
+
como `waiross-framework` ou `waiross-core`).
|
|
55
|
+
|
|
56
|
+
## Próxima etapa: site do projeto
|
|
57
|
+
|
|
58
|
+
Um site de documentação (não uma landing page de marketing) é o que mais
|
|
59
|
+
ajuda adoção nesse estágio. Duas opções diretas, ambas grátis via GitHub
|
|
60
|
+
Pages:
|
|
61
|
+
|
|
62
|
+
- **MkDocs + Material for MkDocs** — Python, Markdown puro, configuração
|
|
63
|
+
em um único `mkdocs.yml`. Mais rápido de montar a partir do que já
|
|
64
|
+
existe (o `README.md` já está estruturado em seções que viram páginas).
|
|
65
|
+
`pip install mkdocs-material`, `mkdocs new docs`, `mkdocs gh-deploy`.
|
|
66
|
+
- **Docusaurus** — React/Node, mais recursos prontos (versionamento de
|
|
67
|
+
docs, busca integrada, blog). Vale se o projeto crescer e quiser algo
|
|
68
|
+
mais parecido com a documentação do Laravel/Next.js.
|
|
69
|
+
|
|
70
|
+
Passos práticos, na ordem:
|
|
71
|
+
|
|
72
|
+
1. Quebrar o `README.md` atual em páginas: Introdução, Arquitetura
|
|
73
|
+
(a árvore de diretórios), DI, Exceções, Protocolos (HTTP/WS/gRPC/
|
|
74
|
+
Fila/CLI), Como criar uma capacidade nova, Deploy.
|
|
75
|
+
2. Rodar localmente (`mkdocs serve`) até o conteúdo estar bom.
|
|
76
|
+
3. `mkdocs gh-deploy` publica automaticamente em
|
|
77
|
+
`https://<seu-usuário>.github.io/<repo>/` — sem precisar de hospedagem
|
|
78
|
+
própria.
|
|
79
|
+
4. (Opcional) Domínio próprio: registrar algo como `waiross.dev` ou
|
|
80
|
+
`wairossframework.dev` (verificar disponibilidade em qualquer registrar —
|
|
81
|
+
Namecheap, Cloudflare Registrar) e apontar via `CNAME` para o GitHub
|
|
82
|
+
Pages.
|
|
83
|
+
5. Adicionar um badge/link do site no `README.md` do repo assim que estiver
|
|
84
|
+
no ar.
|