# Metro
Metro is a lightweight, opinionated, batteries-included Python web framework built on top of FastAPI and MongoEngine.
It is means to provide helpful, lightweight abstractions to enable standard ways of implementing common patters to
prevent the SaaSification of the developer stack.
**The goal is to enhance not inhibit.**
## Features
- Built on top of FastAPI and MongoEngine ODM
- CLI tool for project management and code generation
- Built-in database management (MongoDB)
- Support for both local and Docker-based development
- Environment-specific configurations
- Automatic API documentation
---
## Installation
Install Metro using pip:
`pip install metroapi`
---
## Creating a New Project
Create a new Metro project using the `new` command:
```
metro new my_project
cd my_project
```
This will create a new directory `my_project` with the default project structure:
```
my_project/
├── app/
│ ├── controllers/
│ ├── models/
│ └── __init__.py
├── config/
│ ├── development.py
│ ├── production.py
│ └── __init__.py
├── main.py
├── Dockerfile
└── docker-compose.yml
```
---
## Starting the Development Server
Start the development server using the `run` command:
```
metro run
```
This will start the development server on http://localhost:8000.
You can also run the service using Docker:
```
metro run --docker
```
---
## Scaffolding Resources
Metro includes a scaffold generator to quickly create models, controllers, and route definitions for a new resource.
To generate a scaffold for a `Post` resource with `title` and `body` fields:
```
metro generate scaffold Post title:str body:str
```
This will generate:
- `app/models/post.py` with a `Post` model class
- `app/controllers/posts_controller.py` with CRUD route handlers
- Update `app/controllers/__init__.py` to import the new controller
- Update `app/models/__init__.py` to import the new model
### Scaffold Generator Options
```
metro generate scaffold NAME [FIELDS...] [OPTIONS]
```
#### Available Options:
`--actions`, `-a` (multiple): Define additional custom routes beyond CRUD operations
* Format: `http_method:path (query: params) (body: params) (desc: description) (action_name: action_name)`
* Example: `-a "get:search (query: term:str) (desc: Search users) (action_name: search_users)"`
`--exclude-crud`, `-x` (multiple): Specify which CRUD operations to exclude
* Choices: `index`, `show`, `create`, `update`, `delete`
* Example: `-x delete -x update`
`--model-inherits`: Specify base class(es) for the model
* Format: Single class or comma-separated list
* Example: `--model-inherits UserBase` or `--model-inherits "UserBase,SomeMixin"`
`--controller-inherits`: Specify base class(es) for the controller
* Format: Single class or comma-separated list
* Example: `--controller-inherits AdminController`
`--before-request`, `--before` (multiple): Add lifecycle hooks to run before each request
* Format: `hook_name` or `hook_name:description`
* Example: `--before "check_admin:Verify admin access"`
`--after-request`, `--after` (multiple): Add lifecycle hooks to run after each request
* Format: hook_name or hook_name:description
* Example: --after "log_action:Log all activities"
### Advanced Usage Examples
Full CRUD scaffold with search and custom actions:
```bash
metro generate scaffold Product name:str price:float \
-a "get:search (query: term:str,min_price:float,max_price:float) (desc: Search products) (action_name: search_products)" \
-a "post:bulk-update (body: ids:list,price:float) (desc: Update multiple products) (action_name: bulk_update_products)"
```
Scaffold with limited CRUD and inheritance:
```bash
metro generate scaffold AdminUser email:str role:str \
--model-inherits "UserBase,AuditableMixin" \
--controller-inherits AdminController \
-x delete \
--before "check_admin:Verify admin permissions" \
--after "log_admin_action:Log admin activities"
```
Custom API endpoints with complex parameters:
```bash
metro generate scaffold Order items:list:ref:Product status:str \
-a "post:process/{id} (body: payment_method:str) (desc: Process order payment) (action_name: process_order)" \
-a "get:user/{user_id} (query: status:str,date_from:datetime) (desc: Get user orders) (action_name: get_user_orders)"
--controller-inherits "BaseController,PaymentMixin"
```
#### Adding Custom Routes
Use the `--actions` or `-a` option to add additional routes beyond the standard CRUD endpoints:
ex.
```bash
metro generate scaffold Comment post_id:ref:Post author:str content:str --actions "post:reply"
# or
metro generate scaffold Post title:str body:str -a "post:publish" -a "get:drafts"
```
This will generate the standard CRUD routes plus two additional routes:
- `POST /posts/publish`
- `GET /posts/drafts`
#### Excluding CRUD Routes
Use the `--exclude-crud` or `-x` option to exclude specific CRUD routes you don't need:
```bash
metro generate scaffold Post title:str body:str -x delete -x update
```
This will generate a scaffold without the delete and update endpoints.
You can combine both options:
```bash
metro generate scaffold Post title:str body:str -a "post:publish" -x delete
```
## Generating Models and Controllers
You can also generate models and controllers individually.
### Generating a Model
To generate a `Comment` model with `post_id`, `author`, and `content` fields:
```
metro generate model Comment post_id:str author:str content:str
```
### Model Generator Options
```
metro generate model NAME [FIELDS...] [OPTIONS]
```
#### Available Options:
`--model-inherits`: Specify base class(es) for the model
* Format: Single class or comma-separated list
* Example: `--model-inherits UserBase` or `--model-inherits "UserBase,SomeMixin"`
Example with all options:
```
metro generate model User email:str password:hashed_str profile:ref:Profile roles:list:str --model-inherits "UserBase"
```
### Generating a Controller
To generate a controller for `Auth` routes:
```
metro generate controller Auth
```
You can also pass in the routes to generate as arguments:
```
metro generate controller Auth post:login post:register
```
### Controller Generator Options
```
metro generate controller NAME [ACTIONS...] [OPTIONS]
```
#### Available Options:
`--controller-inherits`: Specify base class(es) for the controller
* Format: Single class or comma-separated list
* Example: `--controller-inherits AdminController`
`--before-request`, `--before` (multiple): Add lifecycle hooks to run before each request
* Format: `hook_name` or `hook_name:description`
* Example: `--before "check_auth:Verify user authentication"`
`--after-request`, `--after` (multiple): Add lifecycle hooks to run after each request
* Format: `hook_name` or `hook_name:description`
* Example: `--after "log_request:Log API request"`
Example with all options:
```
metro generate controller Auth \
"post:login (body: email:str,password:str) (desc: User login)" \
"post:register (body: email:str,password:str,name:str) (desc: User registration)" \
"post:reset-password/{token} (body: password:str) (desc: Reset password)" \
--controller-inherits AuthBaseController \
--before "rate_limit:Apply rate limiting" \
--after "log_auth_attempt:Log authentication attempt"
```
## Field types
### Basic Field Types:
`str`, `int`, `float`, `bool`, `datetime`, `date`, `dict`, `list`.
### Special Field Types:
`ref`, `file`, `list:ref`, `list:file`, `hashed_str`.
### Defining Model Relationships
You can define relationships between models using the following syntax:
- **One-to-Many Relationship**: Use the `ref:` prefix followed by the related model name.
```
metro generate model Post author:ref:User
```
This will generate a `Post` model with an `author` field referencing the `User` model.
- **Many-to-Many Relationship**: Use `list:` and `ref:` together.
```
metro generate model Student courses:list:ref:Course
```
This will generate a `Student` model with a `courses` field that is a list of references to `Course` models.
### Field Modifiers
`?` and `^`are used to define a field as optional or unique respectively.
#### Optional Field:
Append `?` to the field name to mark it as optional.
```
metro generate model User email?:str
```
This will generate a `User` model with an optional `email` field.
#### Unique Field:
Append `^` to the field name to specify it as unique.
```
metro generate model User username^:str
```
This will generate a `User` model with a unique `username` field.
#### Field Choices:
For string fields that should only accept specific values, use the `choices` syntax with optional default value (marked with `*`):
```bash
# required role with no default value (role must be specified)
metro generate model User role:string:choices[user,admin]
# optional role with default value of 'user'
metro generate model User role:string:choices[user*,admin] # note it would be redundant to add ? here since the default value makes it optional
```
You can combine these modifiers to create fields with multiple attributes:
```bash
metro generate model Product \
sku^:str \ # unique identifier
name^:str \ # unique name
price:float \ # no modifier makes price required
description?:str \ # optional description
status:string:choices[active*,discontinued] # enum with default value
```
#### Indexes:
Use the `--index` flag to create indexes for more efficient querying. The syntax supports various MongoDB index options:
Note that built in timestamp fields like `created_at`, `updated_at`, and `deleted_at` are automatically indexed and don't need to be specified.
```bash
# Basic single field index
metro generate model User email:str --index "email"
# Basic compound index
metro generate model Product name:str price:float --index "name,price"
# Unique compound index
metro generate model Product name:str price:float --index "name,price[unique]"
# Compound index with descending order and sparse option
metro generate model Order total:float --index "created_at,total[desc,sparse]" # note that created_at is a built-in field so it doesn't need to be defined explicitly
```
You can specify multiple compound indexes:
```bash
metro generate model Product \
name:str \
price:float \
category:str \
--index "name,price[unique]" \
--index "category,created_at[desc,sparse]"
```
This will generate:
```python
class Product(BaseModel):
name = StringField(required=True)
price = FloatField(required=True)
category = StringField(required=True)
created_at = DateTimeField(required=True)
meta = {
"collection": "product",
'indexes': [
{
'fields': ['name', 'price'],
'unique': True
},
{
'fields': ['-category', '-created_at'],
'sparse': True
}
],
}
```
#### Index Options:
* `unique`: Ensures no two documents can have the same values for these fields
* `sparse`: Only includes documents in the index if they have values for all indexed fields
* `desc`: Creates the index in descending order (useful for sorting)
## Specialty Field Types
### Hashed Field
`hashed_str` is a special field type that automatically hashes the value before storing it in the database.
```
metro generate model User name:str password_hashed:str
```
This will generate a `User` model with a `password` field stored as a hashed value.
### File Fields
`file` and `list:file` are special field types for handling file uploads. They automatically upload
files to the specified storage backend (local filesystem, AWS S3, etc.) and store the file path and file metadata in the database.
- **`file`**: Generates a single `FileField` on the model.
- **`list:file`**: Generates a `FileListField`, allowing multiple files.
Example usage:
```
metro generate model User avatar:file
metro generate model Post attachments:list:file
```
This will generate the following model classes:
```python
class User(BaseModel):
avatar = FileField()
class Post(BaseModel):
attachments = FileListField()
```
Uploading files to s3 then becomes as easy as:
```python
# Set an individual file field
@put('/users/{id}/update-avatar')
async def update_avatar(
self,
id: str,
avatar: UploadFile = File(None),
):
user = User.find_by_id(id=id)
if avatar:
# This stages the file for upload
user.avatar = avatar
# This actually uploads the file and stores the metadata in the database
user.save()
return user.to_dict()
# Work with a list of files
@post('/posts/{id}/upload-attachments')
async def upload_attachments(
self,
id: str,
attachments: list[UploadFile] = File(None),
):
post = Post.find_by_id(id=id)
if attachments:
# This stages the new files for upload
post.attachments.extend(attachments)
# This actually uploads the files and adds appends to the attachments list in the db with the new metadata
post.save()
return post.to_dict()
```
### File Storage Configuration
The default configuration is set to use the local filesystem and store files in the `uploads` directory. You can change the storage backend and location in the `config/development.py` or `config/production.py` file.
Default configuration:
```python
FILE_STORAGE_BACKEND = "filesystem"
FILE_SYSTEM_STORAGE_LOCATION = "./uploads"
FILE_SYSTEM_BASE_URL = "/uploads/"
```
Custom configuration in `config/development.py` or `config/production.py`:
```python
FILE_STORAGE_BACKEND = 'filesystem'
FILE_SYSTEM_STORAGE_LOCATION = './uploads_dev'
FILE_SYSTEM_BASE_URL = '/uploads_dev/'
```
Or to use AWS S3:
```python
FILE_STORAGE_BACKEND = 's3'
S3_BUCKET_NAME = "my-bucket"
AWS_ACCESS_KEY_ID = "..."
AWS_SECRET_ACCESS_KEY = "..."
AWS_REGION_NAME = "us-east-1"
```
---
## Controllers
Controllers handle incoming HTTP requests and define the behavior of your API endpoints. Metro provides a simple,
decorator-based routing system similar to Flask or FastAPI.
### Basic Controller
```python
from metro.controllers import Controller, Request, get, post, put, delete
class UsersController(Controller):
meta = {
'url_prefix': '/users' # Optional URL prefix for all routes in this controller
}
@get("/")
async def index(self, request: Request):
return {"users": []}
@post("/")
async def create(self, request: Request):
user_data = await request.json()
return {"message": "User created"}
...
```
### Request Parameters
```python
from metro import Controller, Request, Body, Query, Path
class ProductsController(Controller):
@get("/search")
async def search(
self,
request: Request,
query: str, # Query parameter (?query=...)
category: str = None, # Optional query parameter
page: int = 1, # Query parameter with default value
):
return {"results": []}
@post("/{id}/reviews")
async def add_review(
self,
request: Request,
id: str, # Path parameter
rating: int = Body(...), # Body parameter (required)
comment: str = Body(None) # Optional body parameter
):
return {"message": "Review added"}
```
### Response Types
Controllers can return various types of responses:
```python
from metro.responses import JSONResponse, HTMLResponse, RedirectResponse
class ContentController(Controller):
@get("/data")
async def get_data(self, request: Request):
# Automatically converted to JSON
return {"data": "value"}
@get("/page")
async def get_page(self, request: Request):
# Explicit HTML response
return HTMLResponse("<h1>Hello World</h1>")
@get("/old-path")
async def redirect(self, request: Request):
# Redirect response
return RedirectResponse("/new-path")
```
### Error Handling
Metro provides standard exceptions for common HTTP error cases:
```python
from metro.exceptions import NotFoundError, BadRequestError, UnauthorizedError
class ArticlesController(Controller):
@get("/{id}")
async def show(self, request: Request, id: str):
article = None # Replace with actual lookup
if not article:
raise NotFoundError(detail="Article not found")
return article
@post("/")
async def create(self, request: Request):
data = await request.json()
if "title" not in data:
raise BadRequestError(detail="Title is required")
return {"message": "Article created"}
```
### Directory-Based URL Prefixes
Metro automatically generates URL prefixes based on your controller's location in the directory structure. This helps
organize your API endpoints logically:
```
app/controllers/
├── users_controller.py # Routes will be at /
├── api/
│ ├── v1/
│ │ ├── posts_controller.py # Routes will be at /api/v1
│ │ └── tags_controller.py # Routes will be at /api/v1
│ └── v2/
│ └── posts_controller.py # Routes will be at /api/v2
└── admin/
└── users_controller.py # Routes will be at /admin
```
#### For example:
```python
# app/controllers/api/v1/posts_controller.py
class PostsController(Controller):
@get("/") # Final URL: /api/v1/posts
async def index(self, request: Request):
return {"posts": []}
@get("/{id}") # Final URL: /api/v1/posts/{id}
async def show(self, request: Request, id: str):
return {"post": {"id": id}}
# You can still add your own prefix that combines with the directory prefix
meta = {
'url_prefix': '/blog' # Routes will be at /api/v1/blog
}
```
## Controller Lifecycle Hooks
Lifecycle hooks like `before_request` and `after_request` can be defined directly in a controller or inherited from a parent controller. Hooks are useful for tasks such as authentication, logging, or cleanup.
### Example: AdminController and AdminUserController
**`admin_controller.py`**
```python
from metro.controllers import Controller, before_request, after_request
from metro.exceptions import UnauthorizedError
from metro import Request
class AdminController(Controller):
@before_request
async def check_admin(self, request: Request):
is_admin = False # Replace with actual logic
print("Checking admin status... (this will be run before each request)")
if not is_admin:
raise UnauthorizedError(detail="Unauthorized access.")
@after_request
async def cleanup_stuff(self, request: Request):
print("Cleaning up... (this will be run after each request)")
```
**`admin_user_controller.py`**
```python
from app.controllers.admin_controller import AdminController
class AdminUserController(AdminController):
@get('/admin-user/all-users')
async def all_users(self, request):
return {"users": []}
```
### Key Points:
- Hooks like `check_admin` and `after_request` can be defined directly in a controller or inherited from a parent.
- In `AdminUserController`, hooks are inherited from `AdminController` and run before and after each request handler.
### Execution:
- If a `before_request` hook raises an exception (e.g., `UnauthorizedError`), the request handler is skipped, but the `after_request` hook still runs.
---
## Rate Limiting
Metro includes a built-in rate limiter that can be applied to specific routes or controllers.
### Throttling Controller Endpoints:
To apply rate limiting to a controller endpoint, use the `@throttle` decorator:
```python
from metro.rate_limiting import throttle
class UserController(Controller):
@get('/users/{id}')
@throttle(per_second=1, per_minute=10)
async def get_user(self, request: Request, id: str):
return {"id": id}
```
### Throttling Routes
To apply rate limiting to a specific route, pass the `Throttler` class as a dependency:
```python
from metro.rate_limiting import Throttler
@app.get("/users/{id}", dependencies=[Depends(Throttler(per_second=1, per_minute=10))]
async def get_user(request: Request, id: str):
return {"id": id}
```
### Customizing Rate Limiting
Parameters:
- `name`: Namespace for the rate limiter.
- `limits`: Compound rate limit definition. Can be a RateLimits() object or a function that returns a RateLimits() object.
- `per_second`: Number of requests allowed per second.
- `per_minute`: Number of requests allowed per minute.
- `per_hour`: Number of requests allowed per hour.
- `per_day`: Number of requests allowed per day.
- `per_week`: Number of requests allowed per week.
- `per_month`: Number of requests allowed per month.
- `backend`: Rate limiting backend (e.g., `InMemoryRateLimiterBackend`, `RedisRateLimiterBackend`). Defaults to InMemoryRateLimiterBackend.
- `callback`: Callback function to execute when the rate limit is exceeded. (request, limited, limit_info) => .... Defaults to raising a `TooManyRequestsError` if limit is exceeded.
- `key`: Custom key function to generate a unique key for rate limiting. (request) => str. Defaults to request IP.
- `cost`: Custom cost function to calculate the cost of a request. (request) => int. Defaults to 1.
---
## Email Sending
Easily send emails using the built-in `EmailSender` class, which supports multiple email providers like Mailgun and AWS SES.
```python
# 1. Configure the provider:
# - For Mailgun
mailgun_provider = MailgunProvider(
domain=os.getenv("MAILGUN_DOMAIN"), api_key=os.getenv("MAILGUN_API_KEY")
)
mailgun_sender = EmailSender(provider=mailgun_provider)
# - For AWS SES (coming soon)
ses_provider = AWSESProvider(region_name="us-west-2")
ses_sender = EmailSender(provider=ses_provider)
# 2. Send the email:
mailgun_sender.send_email(
source="sender@example.com",
recipients=["recipient@example.com"],
subject="Test Email",
body="This is a test email sent using Mailgun.",
)
```
---
## SMS Sending
Easily send SMS messages using the built-in `SMSSender` class, which supports multiple SMS providers like Twilio and Vonage.
1. Add the provider credentials to the environment variables or config file:
```
# For Twilio
TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXX
TWILIO_AUTH_TOKEN=your_auth_token
TWILIO_PHONE_NUMBER=+1234567890
# For Vonage
VONAEG_API_KEY=your_api_key
VONAGE_API_SECRET=your_api_secret
VONAGE_PHONE_NUMBER=+1234567890
```
2. Send an SMS message:
```python
sms_sender = SMSSender() # provider will be automatically detected based on environment variables but can also be specified explicitly
sms_sender.send_sms(
source="+1234567890",
recipients=["+1234567891"],
message="This is a test SMS message!",
)
```
---
## Database Management
Metro provides commands to manage your MongoDB database.
### Starting a Local MongoDB Instance
To start a local MongoDB instance for development:
```
metro db up
```
### Stopping the Local MongoDB Instance
To stop the local MongoDB instance:
```
metro db down
```
### Running MongoDB in a Docker Container
You can also specify the environment and run MongoDB in a Docker container:
```
metro db up --env production --docker
```
---
## Configuration
Environment-specific configuration files are located in the `config` directory:
- `config/development.py`
- `config/production.py`
Here you can set your `DATABASE_URL`, API keys, and other settings that vary between environments.
---
## Admin Panel
Metro includes a built-in admin panel. You can view this at `/admin`
You can disable this or change the admin route in the `config/development.py` or `config/production.py` file:
```python
ENABLE_ADMIN_PANEL = False
ADMIN_PANEL_ROUTE_PREFIX = "/admin-panel"
```
---
## Conductor
"If the Rails generator was powered by an LLM"
### Configuring API Keys for Conductor
Add your OpenAI/Anthropic API keys to power Conductor
`metro conductor setup add-key`
`metro conductor setup list-keys`
`metro conductor setup remove-key`
### Initializing a New Project
Generate the starter code for a Metro project from a project description using the `init` command:
`metro conductor init <project_name> <description>`
ex.
`metro conductor init my-app "A social media app where users can make posts, comment, like, and share posts, and follow other users."`
---
## Documentation and Help
- **API Documentation**: http://localhost:8000/docs
- **CLI help**: `metro --help`
For guides, tutorials, and detailed API references, check out the Metro documentation site.
---
## License
Metro is open-source software licensed under the MIT license.
Raw data
{
"_id": null,
"home_page": "https://github.com/ricardo-agz/metro",
"name": "metroapi",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "web, framework, api",
"author": "Ricardo Gonzalez",
"author_email": "ricardo@rgon.me",
"download_url": "https://files.pythonhosted.org/packages/42/63/c637d33e41c948e4a6e8e8bafc11844e4446c6ce6e53e60a9b05b7503336/metroapi-0.0.8.tar.gz",
"platform": null,
"description": "# Metro\n\nMetro is a lightweight, opinionated, batteries-included Python web framework built on top of FastAPI and MongoEngine. \nIt is means to provide helpful, lightweight abstractions to enable standard ways of implementing common patters to \nprevent the SaaSification of the developer stack. \n\n**The goal is to enhance not inhibit.**\n\n\n## Features\n\n- Built on top of FastAPI and MongoEngine ODM\n- CLI tool for project management and code generation\n- Built-in database management (MongoDB)\n- Support for both local and Docker-based development\n- Environment-specific configurations\n- Automatic API documentation\n\n---\n\n## Installation\n\nInstall Metro using pip:\n\n`pip install metroapi`\n\n---\n\n## Creating a New Project\n\nCreate a new Metro project using the `new` command:\n\n```\nmetro new my_project\ncd my_project\n```\n\nThis will create a new directory `my_project` with the default project structure:\n\n```\nmy_project/\n\u251c\u2500\u2500 app/\n\u2502 \u251c\u2500\u2500 controllers/\n\u2502 \u251c\u2500\u2500 models/\n\u2502 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 config/\n\u2502 \u251c\u2500\u2500 development.py\n\u2502 \u251c\u2500\u2500 production.py \n\u2502 \u2514\u2500\u2500 __init__.py\n\u251c\u2500\u2500 main.py\n\u251c\u2500\u2500 Dockerfile\n\u2514\u2500\u2500 docker-compose.yml\n```\n\n---\n\n## Starting the Development Server\n\nStart the development server using the `run` command:\n\n```\nmetro run\n```\n\nThis will start the development server on http://localhost:8000.\n\n\nYou can also run the service using Docker:\n\n```\nmetro run --docker\n```\n\n---\n\n## Scaffolding Resources\n\nMetro includes a scaffold generator to quickly create models, controllers, and route definitions for a new resource.\n\nTo generate a scaffold for a `Post` resource with `title` and `body` fields:\n\n```\nmetro generate scaffold Post title:str body:str\n```\n\nThis will generate:\n\n- `app/models/post.py` with a `Post` model class\n- `app/controllers/posts_controller.py` with CRUD route handlers\n- Update `app/controllers/__init__.py` to import the new controller\n- Update `app/models/__init__.py` to import the new model \n\n\n### Scaffold Generator Options\n\n```\nmetro generate scaffold NAME [FIELDS...] [OPTIONS]\n```\n\n#### Available Options:\n\n`--actions`, `-a` (multiple): Define additional custom routes beyond CRUD operations\n\n* Format: `http_method:path (query: params) (body: params) (desc: description) (action_name: action_name)`\n* Example: `-a \"get:search (query: term:str) (desc: Search users) (action_name: search_users)\"`\n\n\n`--exclude-crud`, `-x` (multiple): Specify which CRUD operations to exclude\n\n* Choices: `index`, `show`, `create`, `update`, `delete`\n* Example: `-x delete -x update`\n\n\n`--model-inherits`: Specify base class(es) for the model\n\n* Format: Single class or comma-separated list\n* Example: `--model-inherits UserBase` or `--model-inherits \"UserBase,SomeMixin\"`\n\n\n`--controller-inherits`: Specify base class(es) for the controller\n\n* Format: Single class or comma-separated list\n* Example: `--controller-inherits AdminController`\n\n\n`--before-request`, `--before` (multiple): Add lifecycle hooks to run before each request\n\n* Format: `hook_name` or `hook_name:description`\n* Example: `--before \"check_admin:Verify admin access\"`\n\n\n`--after-request`, `--after` (multiple): Add lifecycle hooks to run after each request\n\n* Format: hook_name or hook_name:description\n* Example: --after \"log_action:Log all activities\"\n\n\n### Advanced Usage Examples\n\nFull CRUD scaffold with search and custom actions:\n\n```bash\nmetro generate scaffold Product name:str price:float \\\n -a \"get:search (query: term:str,min_price:float,max_price:float) (desc: Search products) (action_name: search_products)\" \\\n -a \"post:bulk-update (body: ids:list,price:float) (desc: Update multiple products) (action_name: bulk_update_products)\"\n```\n\nScaffold with limited CRUD and inheritance:\n\n```bash\nmetro generate scaffold AdminUser email:str role:str \\\n --model-inherits \"UserBase,AuditableMixin\" \\\n --controller-inherits AdminController \\\n -x delete \\\n --before \"check_admin:Verify admin permissions\" \\\n --after \"log_admin_action:Log admin activities\"\n```\n\nCustom API endpoints with complex parameters:\n\n```bash\nmetro generate scaffold Order items:list:ref:Product status:str \\\n -a \"post:process/{id} (body: payment_method:str) (desc: Process order payment) (action_name: process_order)\" \\\n -a \"get:user/{user_id} (query: status:str,date_from:datetime) (desc: Get user orders) (action_name: get_user_orders)\"\n --controller-inherits \"BaseController,PaymentMixin\"\n```\n\n\n#### Adding Custom Routes\nUse the `--actions` or `-a` option to add additional routes beyond the standard CRUD endpoints:\n\nex.\n```bash\nmetro generate scaffold Comment post_id:ref:Post author:str content:str --actions \"post:reply\"\n# or\nmetro generate scaffold Post title:str body:str -a \"post:publish\" -a \"get:drafts\"\n```\n\nThis will generate the standard CRUD routes plus two additional routes:\n- `POST /posts/publish`\n- `GET /posts/drafts`\n\n\n#### Excluding CRUD Routes\nUse the `--exclude-crud` or `-x` option to exclude specific CRUD routes you don't need:\n\n```bash\nmetro generate scaffold Post title:str body:str -x delete -x update\n```\n\nThis will generate a scaffold without the delete and update endpoints.\n\nYou can combine both options:\n\n```bash\nmetro generate scaffold Post title:str body:str -a \"post:publish\" -x delete\n```\n\n## Generating Models and Controllers\n\nYou can also generate models and controllers individually.\n\n### Generating a Model\n\nTo generate a `Comment` model with `post_id`, `author`, and `content` fields:\n\n```\nmetro generate model Comment post_id:str author:str content:str\n```\n\n### Model Generator Options\n\n```\nmetro generate model NAME [FIELDS...] [OPTIONS]\n```\n\n#### Available Options:\n\n`--model-inherits`: Specify base class(es) for the model\n\n* Format: Single class or comma-separated list\n* Example: `--model-inherits UserBase` or `--model-inherits \"UserBase,SomeMixin\"`\n\nExample with all options:\n\n```\nmetro generate model User email:str password:hashed_str profile:ref:Profile roles:list:str --model-inherits \"UserBase\"\n```\n\n### Generating a Controller\n\nTo generate a controller for `Auth` routes:\n\n```\nmetro generate controller Auth\n```\n\nYou can also pass in the routes to generate as arguments:\n\n```\nmetro generate controller Auth post:login post:register\n```\n\n### Controller Generator Options\n\n```\nmetro generate controller NAME [ACTIONS...] [OPTIONS]\n```\n\n#### Available Options:\n\n`--controller-inherits`: Specify base class(es) for the controller\n\n* Format: Single class or comma-separated list\n* Example: `--controller-inherits AdminController`\n\n`--before-request`, `--before` (multiple): Add lifecycle hooks to run before each request\n\n* Format: `hook_name` or `hook_name:description`\n* Example: `--before \"check_auth:Verify user authentication\"`\n\n`--after-request`, `--after` (multiple): Add lifecycle hooks to run after each request\n\n* Format: `hook_name` or `hook_name:description`\n* Example: `--after \"log_request:Log API request\"`\n\nExample with all options:\n```\nmetro generate controller Auth \\\n \"post:login (body: email:str,password:str) (desc: User login)\" \\\n \"post:register (body: email:str,password:str,name:str) (desc: User registration)\" \\\n \"post:reset-password/{token} (body: password:str) (desc: Reset password)\" \\\n --controller-inherits AuthBaseController \\\n --before \"rate_limit:Apply rate limiting\" \\\n --after \"log_auth_attempt:Log authentication attempt\"\n```\n\n## Field types\n\n### Basic Field Types:\n`str`, `int`, `float`, `bool`, `datetime`, `date`, `dict`, `list`.\n\n\n### Special Field Types:\n`ref`, `file`, `list:ref`, `list:file`, `hashed_str`.\n\n### Defining Model Relationships\n\nYou can define relationships between models using the following syntax:\n\n- **One-to-Many Relationship**: Use the `ref:` prefix followed by the related model name.\n\n```\nmetro generate model Post author:ref:User\n```\n\nThis will generate a `Post` model with an `author` field referencing the `User` model.\n\n- **Many-to-Many Relationship**: Use `list:` and `ref:` together.\n\n```\nmetro generate model Student courses:list:ref:Course\n```\n\nThis will generate a `Student` model with a `courses` field that is a list of references to `Course` models.\n\n### Field Modifiers\n`?` and `^`are used to define a field as optional or unique respectively.\n\n#### Optional Field: \nAppend `?` to the field name to mark it as optional.\n\n```\nmetro generate model User email?:str\n```\n\nThis will generate a `User` model with an optional `email` field.\n\n#### Unique Field: \nAppend `^` to the field name to specify it as unique.\n\n```\nmetro generate model User username^:str\n```\n\nThis will generate a `User` model with a unique `username` field.\n\n#### Field Choices:\nFor string fields that should only accept specific values, use the `choices` syntax with optional default value (marked with `*`):\n\n```bash\n# required role with no default value (role must be specified)\nmetro generate model User role:string:choices[user,admin]\n\n# optional role with default value of 'user'\nmetro generate model User role:string:choices[user*,admin] # note it would be redundant to add ? here since the default value makes it optional\n```\n\nYou can combine these modifiers to create fields with multiple attributes:\n\n```bash\nmetro generate model Product \\\n sku^:str \\ # unique identifier \n name^:str \\ # unique name\n price:float \\ # no modifier makes price required\n description?:str \\ # optional description\n status:string:choices[active*,discontinued] # enum with default value\n```\n\n#### Indexes:\nUse the `--index` flag to create indexes for more efficient querying. The syntax supports various MongoDB index options:\n\nNote that built in timestamp fields like `created_at`, `updated_at`, and `deleted_at` are automatically indexed and don't need to be specified.\n\n```bash\n# Basic single field index\nmetro generate model User email:str --index \"email\"\n\n# Basic compound index\nmetro generate model Product name:str price:float --index \"name,price\"\n\n# Unique compound index\nmetro generate model Product name:str price:float --index \"name,price[unique]\"\n\n# Compound index with descending order and sparse option\nmetro generate model Order total:float --index \"created_at,total[desc,sparse]\" # note that created_at is a built-in field so it doesn't need to be defined explicitly\n```\n\nYou can specify multiple compound indexes:\n \n```bash\nmetro generate model Product \\\n name:str \\\n price:float \\\n category:str \\\n --index \"name,price[unique]\" \\\n --index \"category,created_at[desc,sparse]\"\n```\n\nThis will generate:\n\n```python\nclass Product(BaseModel):\n name = StringField(required=True)\n price = FloatField(required=True)\n category = StringField(required=True) \n created_at = DateTimeField(required=True)\n\n meta = {\n \"collection\": \"product\",\n 'indexes': [\n {\n 'fields': ['name', 'price'],\n 'unique': True\n },\n {\n 'fields': ['-category', '-created_at'],\n 'sparse': True\n }\n ],\n }\n```\n \n#### Index Options:\n\n* `unique`: Ensures no two documents can have the same values for these fields\n* `sparse`: Only includes documents in the index if they have values for all indexed fields\n* `desc`: Creates the index in descending order (useful for sorting)\n\n## Specialty Field Types\n\n### Hashed Field \n`hashed_str` is a special field type that automatically hashes the value before storing it in the database.\n\n```\nmetro generate model User name:str password_hashed:str\n```\n\nThis will generate a `User` model with a `password` field stored as a hashed value.\n\n### File Fields\n`file` and `list:file` are special field types for handling file uploads. They automatically upload\nfiles to the specified storage backend (local filesystem, AWS S3, etc.) and store the file path and file metadata in the database.\n\n- **`file`**: Generates a single `FileField` on the model.\n- **`list:file`**: Generates a `FileListField`, allowing multiple files.\n\nExample usage:\n```\nmetro generate model User avatar:file\nmetro generate model Post attachments:list:file\n```\n\nThis will generate the following model classes:\n\n```python\nclass User(BaseModel):\n avatar = FileField()\n \nclass Post(BaseModel):\n attachments = FileListField()\n```\n\nUploading files to s3 then becomes as easy as:\n\n```python\n# Set an individual file field\n@put('/users/{id}/update-avatar')\nasync def update_avatar(\n self,\n id: str,\n avatar: UploadFile = File(None),\n):\n user = User.find_by_id(id=id)\n if avatar:\n # This stages the file for upload\n user.avatar = avatar\n # This actually uploads the file and stores the metadata in the database\n user.save()\n \n return user.to_dict()\n\n# Work with a list of files \n@post('/posts/{id}/upload-attachments')\nasync def upload_attachments(\n self,\n id: str,\n attachments: list[UploadFile] = File(None),\n):\n post = Post.find_by_id(id=id)\n if attachments:\n # This stages the new files for upload\n post.attachments.extend(attachments)\n # This actually uploads the files and adds appends to the attachments list in the db with the new metadata\n post.save()\n \n return post.to_dict()\n```\n\n### File Storage Configuration\nThe default configuration is set to use the local filesystem and store files in the `uploads` directory. You can change the storage backend and location in the `config/development.py` or `config/production.py` file.\n\nDefault configuration:\n```python\nFILE_STORAGE_BACKEND = \"filesystem\"\nFILE_SYSTEM_STORAGE_LOCATION = \"./uploads\"\nFILE_SYSTEM_BASE_URL = \"/uploads/\"\n```\n\nCustom configuration in `config/development.py` or `config/production.py`:\n```python\nFILE_STORAGE_BACKEND = 'filesystem'\nFILE_SYSTEM_STORAGE_LOCATION = './uploads_dev'\nFILE_SYSTEM_BASE_URL = '/uploads_dev/'\n```\nOr to use AWS S3:\n```python\nFILE_STORAGE_BACKEND = 's3'\nS3_BUCKET_NAME = \"my-bucket\"\nAWS_ACCESS_KEY_ID = \"...\"\nAWS_SECRET_ACCESS_KEY = \"...\"\nAWS_REGION_NAME = \"us-east-1\"\n```\n\n---\n\n## Controllers\n\nControllers handle incoming HTTP requests and define the behavior of your API endpoints. Metro provides a simple, \ndecorator-based routing system similar to Flask or FastAPI.\n\n### Basic Controller\n \n```python\nfrom metro.controllers import Controller, Request, get, post, put, delete\n\nclass UsersController(Controller):\n meta = {\n 'url_prefix': '/users' # Optional URL prefix for all routes in this controller\n }\n\n @get(\"/\")\n async def index(self, request: Request):\n return {\"users\": []}\n \n @post(\"/\")\n async def create(self, request: Request):\n user_data = await request.json()\n return {\"message\": \"User created\"}\n \n ...\n```\n\n### Request Parameters\n```python\nfrom metro import Controller, Request, Body, Query, Path\n\nclass ProductsController(Controller):\n @get(\"/search\")\n async def search(\n self,\n request: Request,\n query: str, # Query parameter (?query=...)\n category: str = None, # Optional query parameter\n page: int = 1, # Query parameter with default value\n ):\n return {\"results\": []}\n\n @post(\"/{id}/reviews\")\n async def add_review(\n self,\n request: Request,\n id: str, # Path parameter\n rating: int = Body(...), # Body parameter (required)\n comment: str = Body(None) # Optional body parameter\n ):\n return {\"message\": \"Review added\"}\n```\n\n### Response Types\n\nControllers can return various types of responses:\n\n```python\nfrom metro.responses import JSONResponse, HTMLResponse, RedirectResponse\n\nclass ContentController(Controller):\n @get(\"/data\")\n async def get_data(self, request: Request):\n # Automatically converted to JSON\n return {\"data\": \"value\"}\n \n @get(\"/page\")\n async def get_page(self, request: Request):\n # Explicit HTML response\n return HTMLResponse(\"<h1>Hello World</h1>\")\n \n @get(\"/old-path\")\n async def redirect(self, request: Request):\n # Redirect response\n return RedirectResponse(\"/new-path\")\n```\n\n### Error Handling\n\nMetro provides standard exceptions for common HTTP error cases:\n\n```python\nfrom metro.exceptions import NotFoundError, BadRequestError, UnauthorizedError\n\nclass ArticlesController(Controller):\n @get(\"/{id}\")\n async def show(self, request: Request, id: str):\n article = None # Replace with actual lookup\n if not article:\n raise NotFoundError(detail=\"Article not found\")\n return article\n\n @post(\"/\")\n async def create(self, request: Request):\n data = await request.json()\n if \"title\" not in data:\n raise BadRequestError(detail=\"Title is required\")\n return {\"message\": \"Article created\"}\n```\n\n### Directory-Based URL Prefixes\n\nMetro automatically generates URL prefixes based on your controller's location in the directory structure. This helps \norganize your API endpoints logically:\n\n```\napp/controllers/\n\u251c\u2500\u2500 users_controller.py # Routes will be at /\n\u251c\u2500\u2500 api/\n\u2502 \u251c\u2500\u2500 v1/\n\u2502 \u2502 \u251c\u2500\u2500 posts_controller.py # Routes will be at /api/v1\n\u2502 \u2502 \u2514\u2500\u2500 tags_controller.py # Routes will be at /api/v1\n\u2502 \u2514\u2500\u2500 v2/\n\u2502 \u2514\u2500\u2500 posts_controller.py # Routes will be at /api/v2\n\u2514\u2500\u2500 admin/\n \u2514\u2500\u2500 users_controller.py # Routes will be at /admin\n```\n\n#### For example:\n\n```python\n# app/controllers/api/v1/posts_controller.py\nclass PostsController(Controller):\n @get(\"/\") # Final URL: /api/v1/posts\n async def index(self, request: Request):\n return {\"posts\": []}\n\n @get(\"/{id}\") # Final URL: /api/v1/posts/{id}\n async def show(self, request: Request, id: str):\n return {\"post\": {\"id\": id}}\n\n # You can still add your own prefix that combines with the directory prefix\n meta = {\n 'url_prefix': '/blog' # Routes will be at /api/v1/blog\n }\n```\n\n\n## Controller Lifecycle Hooks\n\nLifecycle hooks like `before_request` and `after_request` can be defined directly in a controller or inherited from a parent controller. Hooks are useful for tasks such as authentication, logging, or cleanup.\n\n### Example: AdminController and AdminUserController\n\n**`admin_controller.py`**\n```python\nfrom metro.controllers import Controller, before_request, after_request\nfrom metro.exceptions import UnauthorizedError\nfrom metro import Request\n\nclass AdminController(Controller):\n @before_request\n async def check_admin(self, request: Request):\n is_admin = False # Replace with actual logic\n print(\"Checking admin status... (this will be run before each request)\")\n if not is_admin:\n raise UnauthorizedError(detail=\"Unauthorized access.\")\n\n @after_request\n async def cleanup_stuff(self, request: Request):\n print(\"Cleaning up... (this will be run after each request)\")\n```\n\n**`admin_user_controller.py`**\n```python\nfrom app.controllers.admin_controller import AdminController\n\nclass AdminUserController(AdminController):\n @get('/admin-user/all-users')\n async def all_users(self, request):\n return {\"users\": []}\n```\n\n### Key Points:\n- Hooks like `check_admin` and `after_request` can be defined directly in a controller or inherited from a parent.\n- In `AdminUserController`, hooks are inherited from `AdminController` and run before and after each request handler.\n\n### Execution:\n- If a `before_request` hook raises an exception (e.g., `UnauthorizedError`), the request handler is skipped, but the `after_request` hook still runs.\n\n---\n\n## Rate Limiting\n\nMetro includes a built-in rate limiter that can be applied to specific routes or controllers.\n\n### Throttling Controller Endpoints:\n\nTo apply rate limiting to a controller endpoint, use the `@throttle` decorator:\n\n```python\nfrom metro.rate_limiting import throttle\n\nclass UserController(Controller):\n @get('/users/{id}')\n @throttle(per_second=1, per_minute=10)\n async def get_user(self, request: Request, id: str):\n return {\"id\": id}\n```\n\n### Throttling Routes\n\nTo apply rate limiting to a specific route, pass the `Throttler` class as a dependency:\n\n```python\nfrom metro.rate_limiting import Throttler\n\n@app.get(\"/users/{id}\", dependencies=[Depends(Throttler(per_second=1, per_minute=10))]\nasync def get_user(request: Request, id: str):\n return {\"id\": id}\n```\n\n### Customizing Rate Limiting\n\nParameters:\n- `name`: Namespace for the rate limiter.\n- `limits`: Compound rate limit definition. Can be a RateLimits() object or a function that returns a RateLimits() object.\n- `per_second`: Number of requests allowed per second.\n- `per_minute`: Number of requests allowed per minute.\n- `per_hour`: Number of requests allowed per hour.\n- `per_day`: Number of requests allowed per day.\n- `per_week`: Number of requests allowed per week.\n- `per_month`: Number of requests allowed per month.\n- `backend`: Rate limiting backend (e.g., `InMemoryRateLimiterBackend`, `RedisRateLimiterBackend`). Defaults to InMemoryRateLimiterBackend.\n- `callback`: Callback function to execute when the rate limit is exceeded. (request, limited, limit_info) => .... Defaults to raising a `TooManyRequestsError` if limit is exceeded.\n- `key`: Custom key function to generate a unique key for rate limiting. (request) => str. Defaults to request IP.\n- `cost`: Custom cost function to calculate the cost of a request. (request) => int. Defaults to 1.\n\n---\n\n## Email Sending\nEasily send emails using the built-in `EmailSender` class, which supports multiple email providers like Mailgun and AWS SES.\n\n```python\n# 1. Configure the provider:\n# - For Mailgun\nmailgun_provider = MailgunProvider(\n domain=os.getenv(\"MAILGUN_DOMAIN\"), api_key=os.getenv(\"MAILGUN_API_KEY\")\n)\nmailgun_sender = EmailSender(provider=mailgun_provider)\n\n# - For AWS SES (coming soon)\nses_provider = AWSESProvider(region_name=\"us-west-2\")\nses_sender = EmailSender(provider=ses_provider)\n\n# 2. Send the email:\nmailgun_sender.send_email(\n source=\"sender@example.com\",\n recipients=[\"recipient@example.com\"],\n subject=\"Test Email\",\n body=\"This is a test email sent using Mailgun.\",\n)\n```\n\n---\n\n## SMS Sending\nEasily send SMS messages using the built-in `SMSSender` class, which supports multiple SMS providers like Twilio and Vonage.\n\n1. Add the provider credentials to the environment variables or config file:\n```\n# For Twilio\nTWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXX\nTWILIO_AUTH_TOKEN=your_auth_token\nTWILIO_PHONE_NUMBER=+1234567890\n\n# For Vonage\nVONAEG_API_KEY=your_api_key\nVONAGE_API_SECRET=your_api_secret\nVONAGE_PHONE_NUMBER=+1234567890\n```\n\n2. Send an SMS message:\n```python\nsms_sender = SMSSender() # provider will be automatically detected based on environment variables but can also be specified explicitly\n\nsms_sender.send_sms(\n source=\"+1234567890\",\n recipients=[\"+1234567891\"],\n message=\"This is a test SMS message!\",\n)\n```\n---\n\n## Database Management\n\nMetro provides commands to manage your MongoDB database.\n\n### Starting a Local MongoDB Instance\n\nTo start a local MongoDB instance for development:\n\n```\nmetro db up\n```\n\n### Stopping the Local MongoDB Instance\n\nTo stop the local MongoDB instance:\n\n```\nmetro db down\n```\n\n### Running MongoDB in a Docker Container\n\nYou can also specify the environment and run MongoDB in a Docker container:\n\n```\nmetro db up --env production --docker\n```\n\n---\n\n## Configuration\n\nEnvironment-specific configuration files are located in the `config` directory:\n\n- `config/development.py`\n- `config/production.py`\n\nHere you can set your `DATABASE_URL`, API keys, and other settings that vary between environments.\n\n---\n\n## Admin Panel\n\nMetro includes a built-in admin panel. You can view this at `/admin`\n\nYou can disable this or change the admin route in the `config/development.py` or `config/production.py` file:\n\n```python\nENABLE_ADMIN_PANEL = False\nADMIN_PANEL_ROUTE_PREFIX = \"/admin-panel\"\n```\n\n---\n\n## Conductor\n\n\"If the Rails generator was powered by an LLM\"\n\n### Configuring API Keys for Conductor\n\nAdd your OpenAI/Anthropic API keys to power Conductor\n\n`metro conductor setup add-key`\n\n`metro conductor setup list-keys`\n\n`metro conductor setup remove-key`\n\n### Initializing a New Project\n\nGenerate the starter code for a Metro project from a project description using the `init` command:\n\n`metro conductor init <project_name> <description>`\n\nex.\n\n`metro conductor init my-app \"A social media app where users can make posts, comment, like, and share posts, and follow other users.\"`\n\n---\n\n## Documentation and Help\n\n- **API Documentation**: http://localhost:8000/docs\n- **CLI help**: `metro --help`\n\nFor guides, tutorials, and detailed API references, check out the Metro documentation site.\n\n---\n\n## License\n\nMetro is open-source software licensed under the MIT license.\n",
"bugtrack_url": null,
"license": null,
"summary": "Metro: A batteries-included web framework for the fastest development experience.",
"version": "0.0.8",
"project_urls": {
"Homepage": "https://github.com/ricardo-agz/metro"
},
"split_keywords": [
"web",
" framework",
" api"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "62fd0273d15d1ec3f2afa8b58a6372968dccca6fe60f48086161ff981b1e1b63",
"md5": "62b35a22b525e4e1d2480ff3e17491f4",
"sha256": "90529bce930c5b8f49ae2d3f4087f9e63fbeec8b65ca299633458c1f7cbd2aca"
},
"downloads": -1,
"filename": "metroapi-0.0.8-py3-none-any.whl",
"has_sig": false,
"md5_digest": "62b35a22b525e4e1d2480ff3e17491f4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 128317,
"upload_time": "2025-01-18T00:29:39",
"upload_time_iso_8601": "2025-01-18T00:29:39.217170Z",
"url": "https://files.pythonhosted.org/packages/62/fd/0273d15d1ec3f2afa8b58a6372968dccca6fe60f48086161ff981b1e1b63/metroapi-0.0.8-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "4263c637d33e41c948e4a6e8e8bafc11844e4446c6ce6e53e60a9b05b7503336",
"md5": "268da7f9b1614297ceed5fb89d3e5b9c",
"sha256": "7205595e966b1db15e6a6a24aea2078b5c94bf5a4dd0c55791c711127fbdc0fe"
},
"downloads": -1,
"filename": "metroapi-0.0.8.tar.gz",
"has_sig": false,
"md5_digest": "268da7f9b1614297ceed5fb89d3e5b9c",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 103884,
"upload_time": "2025-01-18T00:29:41",
"upload_time_iso_8601": "2025-01-18T00:29:41.770438Z",
"url": "https://files.pythonhosted.org/packages/42/63/c637d33e41c948e4a6e8e8bafc11844e4446c6ce6e53e60a9b05b7503336/metroapi-0.0.8.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-01-18 00:29:41",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "ricardo-agz",
"github_project": "metro",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [
{
"name": "click",
"specs": [
[
"~=",
"8.1.7"
]
]
},
{
"name": "certifi",
"specs": [
[
"~=",
"2024.8.30"
]
]
},
{
"name": "mongoengine",
"specs": [
[
"~=",
"0.29.0"
]
]
},
{
"name": "pymongo",
"specs": [
[
"~=",
"4.10.1"
]
]
},
{
"name": "metroapi",
"specs": [
[
"~=",
"0.1"
]
]
},
{
"name": "uvicorn",
"specs": [
[
"~=",
"0.30.6"
]
]
},
{
"name": "aioredis",
"specs": [
[
"~=",
"2.0.1"
]
]
},
{
"name": "pydantic",
"specs": [
[
"~=",
"2.10.4"
]
]
},
{
"name": "requests",
"specs": [
[
"~=",
"2.32.3"
]
]
},
{
"name": "httpx",
"specs": [
[
"~=",
"0.27.2"
]
]
},
{
"name": "jinja2",
"specs": [
[
"~=",
"3.1.4"
]
]
},
{
"name": "inflect",
"specs": [
[
"~=",
"7.4.0"
]
]
},
{
"name": "bcrypt",
"specs": [
[
"~=",
"4.2.0"
]
]
},
{
"name": "cryptography",
"specs": [
[
"~=",
"43.0.1"
]
]
},
{
"name": "python-dotenv",
"specs": [
[
"~=",
"1.0.1"
]
]
},
{
"name": "fastapi",
"specs": [
[
"~=",
"0.114.2"
]
]
},
{
"name": "starlette",
"specs": [
[
"~=",
"0.38.5"
]
]
},
{
"name": "setuptools",
"specs": [
[
"~=",
"65.5.1"
]
]
},
{
"name": "bson",
"specs": [
[
"~=",
"0.5.10"
]
]
},
{
"name": "PyJWT",
"specs": [
[
"~=",
"2.10.1"
]
]
},
{
"name": "redis",
"specs": [
[
"~=",
"5.1.0"
]
]
},
{
"name": "twilio",
"specs": [
[
"~=",
"9.4.1"
]
]
},
{
"name": "vonage",
"specs": [
[
"~=",
"4.1.2"
]
]
}
],
"lcname": "metroapi"
}