# Best Practices

Recommendations for working effectively with the FlyMyAI CLI.

## Project structure

### Keep the project directory clean

The project root must contain **only** your model folder and `metadata.yaml`. Any extra directories will cause a deployment error.

```
my_model/
    └── model.py
metadata.yaml
```

Do not place virtual environments, notebooks, data files, or other folders alongside the model directory.

### Use descriptive model names

Model names become part of your deployment identifier. Use lowercase with underscores:

| Good                  | Bad                |
|-----------------------|--------------------|
| `image_classifier`    | `ImageClassifier`  |
| `text_summarizer`     | `my-model-v2`     |
| `sd3_turbo`           | `test`             |

## Model development

### Put imports inside methods

All imports except `pydantic`, `typing`, and `fma.toolkit` must go inside `initialize()` and `predict()`. This ensures dependencies are resolved at runtime on the target infrastructure.

```python
class Model(fma_model.Model):
    requirements: List[str] = ["transformers==4.45.2"]

    def initialize(self):
        # highlight-next-line
        from transformers import pipeline
        self.pipe = pipeline("sentiment-analysis")

    def predict(self, input: Input) -> Output:
        result = self.pipe(input["text"])
        return {"label": result[0]["label"]}
```

### Load heavy resources in `initialize()`, not `predict()`

Model weights, pipelines, and tokenizers should be loaded **once** in `initialize()` and stored on `self`. The `predict()` method runs per-request and should be as fast as possible.

```python
# Bad - loads the model on every request
def predict(self, input: Input) -> Output:
    from transformers import pipeline
    pipe = pipeline("sentiment-analysis")  # slow!
    return {"label": pipe(input["text"])[0]["label"]}

# Good - loaded once, reused across requests
def initialize(self):
    from transformers import pipeline
    self.pipe = pipeline("sentiment-analysis")

def predict(self, input: Input) -> Output:
    return {"label": self.pipe(input["text"])[0]["label"]}
```

### Handle optional inputs with `.get()`

Since `predict()` receives plain Python dicts (not Pydantic model instances), use `.get()` with defaults for optional fields:

```python
def predict(self, input: Input) -> Output:
    prompt = input["prompt"]                           # required
    width = input.get("width", 1024)                   # optional with default
    height = input.get("height", 1024)                 # optional with default
    steps = input.get("num_inference_steps", 4)        # optional with default
    # ...
```

## Dependency management

### Pin exact versions

Always specify exact versions in the `requirements` list. Unpinned dependencies can break between deployments.

```python
# Good
requirements: List[str] = [
    "diffusers==0.30.0",
    "Pillow==10.4.0",
    "protobuf==5.28.0",
]

# Bad - may install incompatible versions
requirements: List[str] = [
    "diffusers",
    "Pillow",
    "protobuf",
]
```

### Respect pre-installed package versions

The infrastructure provides specific versions of common ML libraries (see [Supported packages](https://docs.flymy.ai/cli/intro.md#supported-third-party-packages)). Requesting conflicting versions will cause installation failures. If you need `torch`, do not add it to `requirements` - it is already available.

### Keep requirements minimal

Only list packages that are not pre-installed. Redundant entries increase deployment time without benefit.

## Authentication and security

### Never hardcode secrets in model code

Use environment variables set inside `initialize()` rather than committing tokens to source control:

```python
def initialize(self):
    import os
    os.environ["HF_TOKEN"] = "<set-at-runtime>"
    # ...
```

If your model needs API keys or tokens, consider using `metadata.yaml` or environment-based configuration rather than hardcoding values in `model.py`.

### Prefer interactive login

Avoid passing `--password` directly on the command line, as it may be saved in shell history:

```bash
# Preferred - password is prompted securely
fma login

# Acceptable for CI/CD
fma login --username "$FMA_USER" --password "$FMA_PASS"
```

## Deployment workflow

### Check logs after every deployment

Run `fma logs` immediately after `fma deploy` to verify that the model initialized correctly. Startup logs are most useful when something fails silently:

```bash
fma deploy
fma logs
```

Save logs to a file for sharing with your team or for later review:

```bash
fma logs --output-file startup_logs.txt
```

### Use `fma update` for iterative development

Instead of manually running `fma delete` followed by `fma deploy`, use `fma update` which handles both steps:

```bash
# Edit model.py...
fma update
```

### Test locally before deploying

Before deploying, verify that your model loads and runs locally:

```python
from my_model.model import Model, Input

m = Model()
m.initialize()

sample_input = {"prompt": "a cat sitting on a cloud"}
result = m.predict(sample_input)
print(result)
```

This catches import errors, missing dependencies, and logic issues without waiting for remote builds.

## Debugging

### Enable debug mode

Use the `--debug` flag for verbose output during any CLI operation:

```bash
fma --debug deploy
fma --debug logs
```

This reveals HTTP request details, API responses, and detailed error traces.

### Common failure patterns

| Symptom | Likely cause | Fix |
|---------|-------------|-----|
| `ModuleNotFoundError` | Missing entry in `requirements` | Add the package with a pinned version |
| Deployment timeout | Too many or too large dependencies | Minimize requirements, use pre-installed packages |
| `metadata.yaml` errors | Extra directories in project root | Remove all folders except the model directory |
| Logs not available | Model started successfully | No action needed - logs are captured on failure |
| 4xx error on deploy | Model already exists | Run `fma update` instead of `fma deploy` |
