Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/workflows/cli_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ jobs:

cd "$app_name"
uv run pynest generate resource -n user
uv run pynest generate gateway -n chat -p src

- name: Verify Boilerplate
run: |
Expand Down Expand Up @@ -109,4 +110,80 @@ jobs:
fi
done

gateway_file="$app_name/src/chat_gateway.py"
if [ -f "$gateway_file" ]; then
echo "$gateway_file exists."
else
echo "$gateway_file does not exist."
exit 1
fi
if ! grep -q '@WebSocketGateway(namespace="/chat")' "$gateway_file"; then
echo "$gateway_file is missing the expected @WebSocketGateway decorator."
exit 1
fi

echo "Boilerplate for ${{ matrix.app_type }} generated successfully."

- name: Ping generated WebSocket app
if: matrix.app_type == 'Blank'
run: |
cd "${{ matrix.app_type }}App"
uv run python - <<'PY'
import asyncio
import importlib.util
import json
import socket

import uvicorn
import websockets

from nest.core import Module, PyNestContainer, PyNestFactory

spec = importlib.util.spec_from_file_location(
"chat_gateway", "src/chat_gateway.py"
)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
ChatGateway = mod.ChatGateway

@Module(providers=[ChatGateway])
class App:
pass

def free_port():
with socket.socket() as sock:
sock.bind(("127.0.0.1", 0))
return sock.getsockname()[1]

async def main():
PyNestContainer._instance = None
app = PyNestFactory.create(App).get_server()
port = free_port()
config = uvicorn.Config(
app, host="127.0.0.1", port=port,
log_level="critical", lifespan="off",
)
server = uvicorn.Server(config)
task = asyncio.create_task(server.serve())
for _ in range(200):
if server.started:
break
await asyncio.sleep(0.05)
try:
async with websockets.connect(
f"ws://127.0.0.1:{port}/chat"
) as ws:
await ws.send(json.dumps(
{"event": "ping", "data": {"hello": "world"}}
))
response = json.loads(await ws.recv())
assert response == {
"event": "pong", "data": {"hello": "world"},
}, response
print("WEBSOCKET_PING_OK")
finally:
server.should_exit = True
await task

asyncio.run(main())
PY
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ Each module contains a collection of related controllers, services, and provider
PyNest supports dependency injection, which makes it easy to manage dependencies and write testable code. You can easily
inject services and providers into your controllers using decorators.

### WebSocket Gateways

PyNest supports native FastAPI WebSocket gateways for real-time APIs. Gateways are registered as providers, participate
in dependency injection, and can use event handlers, lifecycle hooks, guards, rooms, and token streaming patterns.

### Decorators

PyNest makes extensive use of decorators to define routes, middleware, and other application components. This helps keep
Expand Down
22 changes: 21 additions & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,26 @@ pynest generate service --name users

This will create a new service named `users` in the default path.

**Gateway**

Generate a new WebSocket gateway file.

```bash
pynest generate gateway --name <gateway_name>
```

**Options**

* `--name`, `-n`: The name of the new gateway. (Required)
* `--path`, `-p`: The path where the gateway should be created. (Optional)

**Example**
```bash
pynest generate gateway --name chat
```

This creates `chat_gateway.py` with a starter `@WebSocketGateway(namespace="/chat")` class and a `ping` message handler. Add the generated gateway to a module's `providers` list to mount it.


## Best Practices 🌟

Expand All @@ -163,4 +183,4 @@ The PyNest CLI is a powerful tool that simplifies the development of PyNest appl
<a href="/PyNest/modules" class="md-footer-nav__link">
<span>Modules &rarr;</span>
</a>
</nav>
</nav>
11 changes: 10 additions & 1 deletion docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@ python main.py

You should see the Uvicorn server starting, and you can access your API at <http://localhost:8000>.

## Next Steps

After the first HTTP endpoint is running, you can add more framework features:

* [Modules](modules.md) for organizing application boundaries.
* [Providers](providers.md) for injectable business logic.
* [Guards](guards.md) for authorization.
* [WebSocket Gateways](websockets.md) for real-time event APIs.

---
<nav class="md-footer-nav">
<a href="/PyNest/introduction" class="md-footer-nav__link">
Expand All @@ -151,4 +160,4 @@ You should see the Uvicorn server starting, and you can access your API at <http
<a href="/PyNest/cli" class="md-footer-nav__link">
<span>CLI Usage &rarr;</span>
</a>
</nav>
</nav>
26 changes: 25 additions & 1 deletion docs/guards.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,31 @@ class AdminController:

In this example `AdminGuard` protects all routes while `PublicOnlyGuard` is applied only to the `login` route.

## WebSocket Guards

`@UseGuards` also works on WebSocket gateways and individual `@SubscribeMessage` handlers. WebSocket guards receive an execution context instead of a FastAPI `Request`.

```python
from nest.core import BaseGuard, UseGuards
from nest.websockets import SubscribeMessage, WebSocketGateway


class WsTokenGuard(BaseGuard):
async def can_activate(self, context):
ws = context.switch_to_ws()
return ws.get_client().headers.get("x-token") == "secret"


@WebSocketGateway(namespace="/private")
@UseGuards(WsTokenGuard)
class PrivateGateway:
@SubscribeMessage("secret")
async def secret(self):
return {"event": "secret_ack", "data": {}}
```

Use `context.switch_to_ws().get_client()` for the active socket, `get_data()` for the message body, `get_event()` for the event name, and `get_server()` for the gateway server.

## Combining Multiple Guards

`UseGuards` accepts any number of guard classes. All specified guards must return `True` in order for the request to proceed.
Expand Down Expand Up @@ -562,4 +587,3 @@ class EnterpriseController:
| Multi-Auth | Any | Flexible authentication | ✅ |

PyNest guards provide a powerful, flexible, and standards-compliant way to secure your APIs while maintaining excellent developer experience and automatic documentation generation.

4 changes: 4 additions & 0 deletions docs/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ PyNest follows the modular architecture of NestJS, which allows for easy separat

PyNest supports dependency injection, which makes it easy to manage dependencies and write testable code. You can easily inject services and providers into your controllers using decorators.

### WebSocket Gateways

PyNest supports native FastAPI WebSocket gateways for real-time APIs. Gateways are registered as providers, participate in dependency injection, and can use lifecycle hooks, guards, rooms, and event handlers. See [WebSocket Gateways](websockets.md) for the full guide.

### Decorators 🏷️

PyNest makes extensive use of decorators to define routes, middleware, and other application components. This helps keep the code concise and easy to read.
Expand Down
Loading
Loading