Metadata-Version: 2.4
Name: openfoodfacts-proxy
Version: 0.1.0
Summary: A FastAPI proxy for OpenFoodFacts
Project-URL: homepage, https://twsl.github.io/openfoodfacts-proxy/
Project-URL: repository, https://github.com/twsl/openfoodfacts-proxy
Project-URL: documentation, https://twsl.github.io/openfoodfacts-proxy/
Author-email: twsl <45483159+twsl@users.noreply.github.com>
License: MIT
License-File: LICENSE
Keywords: openfoodfacts-proxy
Requires-Python: >=3.12
Requires-Dist: apscheduler>=3.11.0
Requires-Dist: click>=8.1.0
Requires-Dist: fastapi>=0.115.0
Requires-Dist: httpx>=0.28.0
Requires-Dist: openfoodfacts>=5.0.1
Requires-Dist: pydantic-settings>=2.9.0
Requires-Dist: pymongo>=4.7.0
Requires-Dist: uvicorn[standard]>=0.46.0
Description-Content-Type: text/markdown

# openfoodfacts-proxy

[![Build](https://github.com/twsl/openfoodfacts-proxy/actions/workflows/build.yaml/badge.svg)](https://github.com/twsl/openfoodfacts-proxy/actions/workflows/build.yaml)
[![Documentation](https://github.com/twsl/openfoodfacts-proxy/actions/workflows/docs.yaml/badge.svg)](https://github.com/twsl/openfoodfacts-proxy/actions/workflows/docs.yaml)

<!--- [![PyPI - Package Version](https://img.shields.io/pypi/v/openfoodfacts-proxy?logo=pypi&style=flat&color=orange)](https://pypi.org/project/openfoodfacts-proxy/) -->
<!--- [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/openfoodfacts-proxy?logo=pypi&style=flat&color=blue)](https://pypi.org/project/openfoodfacts-proxy/) -->

[![Docs with MkDocs](https://img.shields.io/badge/MkDocs-docs?style=flat&logo=materialformkdocs&logoColor=white&color=%23526CFE)](https://squidfunk.github.io/mkdocs-material/)
[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
[![linting: ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![ty](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json)](https://github.com/astral-sh/ty)
[![prek](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/j178/prek/master/docs/assets/badge-v0.json)](https://github.com/j178/prek)
[![security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)
[![Semantic Versions](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--versions-e10079.svg)](https://github.com/twsl/openfoodfacts-proxy/releases)
[![Copier](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/copier-org/copier/master/img/badge/badge-grayscale-border.json)](https://github.com/copier-org/copier)
[![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)

A FastAPI proxy for OpenFoodFacts

Supports:

- https://openfoodfacts.github.io/openfoodfacts-server/api/ref-v2/
- https://openfoodfacts.github.io/openfoodfacts-server/api/ref-v3/
- https://search.openfoodfacts.org/docs

## Features

- **Local-first product reads** — serves product data from a local MongoDB cache with automatic upstream fallback
- **API v2 and v3 support** — compatible with both OFF API versions, including field projection
- **Transparent upstream proxying** — unrecognized requests are forwarded to the real OFF server
- **Full and delta sync** — import the complete OFF MongoDB dump or apply incremental delta updates
- **Rate limiting** — configurable per-endpoint rate limits (product reads, search)
- **Search integration** — local v2 search with upstream fallback; search-a-licious proxy support
- **Taxonomy caching** — periodic sync of categories, brands, countries, ingredients, and labels
- **Image URL rewriting** — rewrite OFF S3 image URLs through your own route/CDN
- **Reference data endpoints** — CGI-compatible and v2/v3 taxonomy/reference routes
- **Facet browsing** — serve facet-based product listings from the local cache
- **CLI tooling** — `serve`, `import-full`, and `import-delta` commands

## Installation

With `pip`:

```bash
python -m pip install openfoodfacts-proxy
```

With [`uv`](https://docs.astral.sh/uv/):

```bash
uv add openfoodfacts-proxy
```

## How to use it

### CLI

Start the proxy server:

```bash
openfoodfacts-proxy serve --host 0.0.0.0 --port 8000
```

Import the full OFF MongoDB dump:

```bash
openfoodfacts-proxy import-full
```

Apply delta updates:

```bash
openfoodfacts-proxy import-delta
```

### Python

```python
from openfoodfacts_proxy.app import create_app

app = create_app()
```

### Supported endpoints

| Endpoint                               | Description                                           |
| -------------------------------------- | ----------------------------------------------------- |
| `GET /api/v2/product/{barcode}`        | Product read (v2 format) with optional `fields` query |
| `GET /api/v3/product/{barcode}`        | Product read (v3 format) with optional `fields` query |
| `GET /api/v2/search`                   | Search products with filters and pagination           |
| `GET /{facet_type}/{facet_value}.json` | Facet-based product listings                          |

### Configuration

All settings are configurable via environment variables with the `OFF_PROXY_` prefix:

| Variable                              | Default                           | Description                         |
| ------------------------------------- | --------------------------------- | ----------------------------------- |
| `OFF_PROXY_MONGODB_URI`               | `mongodb://mongodb:27017`         | MongoDB connection string           |
| `OFF_PROXY_MONGODB_DB`                | `openfoodfacts`                   | Database name                       |
| `OFF_PROXY_OFF_BASE_URL`              | `https://world.openfoodfacts.org` | Upstream OFF server                 |
| `OFF_PROXY_PRODUCT_RATE_LIMIT`        | `15`                              | Max product requests per window     |
| `OFF_PROXY_SEARCH_RATE_LIMIT`         | `10`                              | Max search requests per window      |
| `OFF_PROXY_RATE_LIMIT_WINDOW_SECONDS` | `60`                              | Rate limit window duration          |
| `OFF_PROXY_UPSTREAM_TIMEOUT_SECONDS`  | `30.0`                            | Timeout for upstream requests       |
| `OFF_PROXY_STARTUP_SYNC_ENABLED`      | `true`                            | Run full sync on startup            |
| `OFF_PROXY_SCHEDULER_ENABLED`         | `true`                            | Enable periodic delta/taxonomy sync |
| `OFF_PROXY_REWRITE_IMAGE_URLS`        | `true`                            | Rewrite OFF image URLs              |

## Docker

`Dockerfile.aio` builds the default all-in-one image and starts MongoDB inside the same container.
`Dockerfile` builds the app-only image and expects MongoDB to run in a separate container.

Build the all-in-one image:

```bash
docker build -f Dockerfile.aio -t openfoodfacts-proxy:aio .
```

Build the separate-DB image:

```bash
docker build -f Dockerfile -t openfoodfacts-proxy:app .
```

Run the default all-in-one container:

```bash
docker compose up --build
```

Run with a separate MongoDB container:

```bash
docker compose -f docker-compose.production.yml up --build
```

Image URL rewriting is configurable so OFF image URLs can be exposed through your own Cloudflare route instead of leaking S3 bucket details.
Set `OFF_PROXY_REWRITE_IMAGE_URLS=false` to disable rewriting completely.
The runtime settings are split into `OFF_PROXY_IMAGE_ROUTE_BASE_URL`, `OFF_PROXY_IMAGE_BUCKET_NAME`, `OFF_PROXY_IMAGE_BUCKET_REGION`, and `OFF_PROXY_IMAGE_BUCKET_PREFIX`.
With the defaults, a source URL like `https://images.openfoodfacts.org/images/products/301/762/042/2003/front_en.820.400.jpg` is rewritten to `/images/data/301/762/042/2003/front_en.820.400.jpg`.
If you want custom rewriting logic, pass your own `OpenFoodFactsUrlMapper` implementation into `create_app(...)`.

## OFF SDK E2E

The OFF SDK integration suite does not use a dedicated Compose file. It starts the proxy stack directly and always seeds MongoDB from the committed fixture dump plus fixture delta under `tests/integration/off_sdk/fixtures/`, not from the public OFF dataset.

Run it explicitly so normal test runs stay fast:

```bash
OFF_PROXY_RUN_SDK_E2E=1 uv run pytest tests/integration/test_off_sdk_proxy_e2e.py -m off_sdk_e2e
```

By default the test prefers a real local MongoDB process when `mongod` and `mongosh` are available, then falls back to Docker. You can force either mode explicitly:

```bash
OFF_PROXY_RUN_SDK_E2E=1 OFF_PROXY_SDK_E2E_MODE=local uv run pytest tests/integration/test_off_sdk_proxy_e2e.py -m off_sdk_e2e
```

```bash
OFF_PROXY_RUN_SDK_E2E=1 OFF_PROXY_SDK_E2E_MODE=docker uv run pytest tests/integration/test_off_sdk_proxy_e2e.py -m off_sdk_e2e
```

Local mode requires `mongod` and `mongosh` on `PATH`.

The committed fixtures live under `tests/integration/off_sdk/fixtures/`. The `data/off-sdk-e2e/` directory is disposable gitignored runtime state written by the test harness.

## Docs

```bash
uv run mkdocs build -f ./mkdocs.yml -d ./_build/
```

## Update template

```bash
copier update --trust -A --vcs-ref=HEAD
```

## Credits

This project was generated with [![🚀 python project template.](https://img.shields.io/badge/python--project--template-%F0%9F%9A%80-brightgreen)](https://github.com/twsl/python-project-template)
