Skip to content
Closed
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
11 changes: 11 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
__pycache__/
*.pyc
*.pyo
*.pyd
*.db
*.sqlite3
*.log
*.env
*.egg-info/
.venv/
.env
42 changes: 42 additions & 0 deletions .github/workflows/docker-api.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Build and Test eyecite API Docker

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build Docker image
run: docker build -t eyecite-api .

- name: Run Docker container
run: |
docker run -d -p 8000:8000 --name eyecite-api-test eyecite-api
sleep 10

- name: Test /extract endpoint
run: |
curl -X POST "http://localhost:8000/extract" \
-H "Content-Type: application/json" \
-d '{"text": "See 410 U.S. 113 (1973)."}'

- name: Show container logs on failure
if: failure()
run: docker logs eyecite-api-test

- name: Stop container
if: always()
run: docker stop eyecite-api-test && docker rm eyecite-api-test
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Dockerfile for eyecite API
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"]
125 changes: 125 additions & 0 deletions PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# PLAN.md

## Goal

Deploy eyecite in a Docker container and expose its core functions as RESTful API endpoints, enabling programmatic access to citation extraction, resolution, annotation, and cleaning.

---

## 1. Identify Core Functions to Expose

Based on [README.rst](README.rst) and [eyecite/__init__.py](eyecite/__init__.py), the main user-facing functions are:

- [`get_citations`](eyecite/find.py): Extract citations from text.
- [`resolve_citations`](eyecite/resolve.py): Cluster and resolve citations to logical referents.
- [`annotate_citations`](eyecite/annotate.py): Annotate text with markup around citations.
- [`clean_text`](eyecite/clean.py): Clean and preprocess text for citation extraction.

Optional:
- [`dump_citations`](eyecite/find.py): For debugging, returns detailed metadata for each citation.

---

## 2. Design API Endpoints

Proposed endpoints (all accept/return JSON):

- `POST /extract`
- Input: `{ "text": "...", "options": {...} }`
- Output: `{ "citations": [...] }`
- Calls: `get_citations`

- `POST /resolve`
- Input: `{ "citations": [...], "options": {...} }`
- Output: `{ "clusters": {...} }`
- Calls: `resolve_citations`

- `POST /annotate`
- Input: `{ "text": "...", "annotations": [...], "options": {...} }`
- Output: `{ "annotated_text": "..." }`
- Calls: `annotate_citations`

- `POST /clean`
- Input: `{ "text": "...", "steps": [...] }`
- Output: `{ "cleaned_text": "..." }`
- Calls: `clean_text`

- `POST /extract-resolve`
- Input: `{ "text": "...", "options": {...} }`
- Output: `{ "clusters": {...} }`
- Calls: `get_citations` + `resolve_citations`

- (Optional) `POST /dump`
- Input: `{ "text": "...", "options": {...} }`
- Output: `{ "citations": [...] }`
- Calls: `dump_citations`

---

## 3. Choose API Framework

- Use [FastAPI](https://fastapi.tiangolo.com/) (recommended for Python, async, OpenAPI support) or Flask.
- Add a `Dockerfile` to build the container.
- Add a `requirements.txt` or update `pyproject.toml` for API dependencies.

---

## 4. Implementation Steps

1. **API Server**
- Create `api/` directory with `main.py` (FastAPI app).
- Implement endpoints, mapping JSON requests to eyecite functions.
- Validate and serialize input/output (handle citation objects, spans, etc.).

2. **Dockerization**
- Write a `Dockerfile`:
- Use official Python base image.
- Install eyecite and API dependencies.
- Set entrypoint to run the API server (e.g., `uvicorn api.main:app`).

3. **Testing**
- Add example requests for each endpoint.
- Add unit/integration tests for API.

4. **Documentation**
- Document endpoints in `README.md` or via OpenAPI (FastAPI auto-generates).
- Provide usage examples (e.g., with `curl` or Python requests).

5. **Deployment**
- Optionally add a `docker-compose.yml` for local development.
- Push image to a registry if needed.

---

## 5. Example Directory Structure

```
eyecite/
api/
main.py
Dockerfile
requirements.txt
PLAN.md
README.md
...
```

---

## 6. Notes

- Consider exposing tokenizer selection and options via API.
- For large texts, support file uploads or streaming if needed.
- Ensure security best practices (limit request size, sanitize input).
- Optionally, add authentication for production deployments.

---

## 7. References

- [eyecite/README.rst](README.rst)
- [eyecite/__init__.py](eyecite/__init__.py)
- [eyecite/annotate.py](eyecite/annotate.py)
- [eyecite/find.py](eyecite/find.py)
- [eyecite/resolve.py](eyecite/resolve.py)
- [eyecite/clean.py](eyecite/clean.py)
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# eyecite API

This project exposes eyecite's core citation extraction, resolution, annotation, and cleaning functions as a RESTful API using FastAPI.

## Endpoints

- `POST /extract` — Extract citations from text
- `POST /resolve` — Cluster and resolve citations
- `POST /annotate` — Annotate text with citation markup
- `POST /clean` — Clean and preprocess text
- `POST /extract-resolve` — Extract and resolve citations in one step
- `POST /dump` — (Optional) Dump detailed citation metadata

All endpoints accept and return JSON.

## Example Usage

### Run Locally

```bash
# Build and run with Docker
cd /workspaces/eyecite

docker build -t eyecite-api .
docker run -p 8000:8000 eyecite-api
```

### Example Request (extract citations)

```bash
curl -X POST "http://localhost:8000/extract" \
-H "Content-Type: application/json" \
-d '{"text": "See 410 U.S. 113 (1973)."}'
```

### API Documentation

Once running, visit [http://localhost:8000/docs](http://localhost:8000/docs) for interactive OpenAPI docs.

## Development

- Edit `api/main.py` to modify or add endpoints.
- Update `requirements.txt` for dependencies.
- See `PLAN.md` for design notes.

## Security & Production

- Limit request size and sanitize input for production.
- Add authentication if needed.

---

See `PLAN.md` for more details and roadmap.
91 changes: 91 additions & 0 deletions api/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import List, Optional, Any, Dict

import sys
sys.path.append("..") # Ensure eyecite is importable

from eyecite import get_citations, resolve_citations, annotate_citations, clean_text
try:
from eyecite.find import dump_citations
except ImportError:
dump_citations = None

app = FastAPI(title="eyecite API", description="RESTful API for citation extraction, resolution, annotation, and cleaning.")

# --- Pydantic Models ---
class ExtractRequest(BaseModel):
text: str
options: Optional[Dict[str, Any]] = None

class ExtractResponse(BaseModel):
citations: Any

class ResolveRequest(BaseModel):
citations: Any
options: Optional[Dict[str, Any]] = None

class ResolveResponse(BaseModel):
clusters: Any

class AnnotateRequest(BaseModel):
text: str
annotations: Any
options: Optional[Dict[str, Any]] = None

class AnnotateResponse(BaseModel):
annotated_text: str

class CleanRequest(BaseModel):
text: str
steps: Optional[List[str]] = None

class CleanResponse(BaseModel):
cleaned_text: str

class ExtractResolveRequest(BaseModel):
text: str
options: Optional[Dict[str, Any]] = None

class ExtractResolveResponse(BaseModel):
clusters: Any

class DumpRequest(BaseModel):
text: str
options: Optional[Dict[str, Any]] = None

class DumpResponse(BaseModel):
citations: Any

# --- Endpoints ---
@app.post("/extract", response_model=ExtractResponse)
def extract(req: ExtractRequest):
citations = get_citations(req.text, **(req.options or {}))
return {"citations": citations}

@app.post("/resolve", response_model=ResolveResponse)
def resolve(req: ResolveRequest):
clusters = resolve_citations(req.citations, **(req.options or {}))
return {"clusters": clusters}

@app.post("/annotate", response_model=AnnotateResponse)
def annotate(req: AnnotateRequest):
annotated = annotate_citations(req.text, req.annotations, **(req.options or {}))
return {"annotated_text": annotated}

@app.post("/clean", response_model=CleanResponse)
def clean(req: CleanRequest):
cleaned = clean_text(req.text, steps=req.steps)
return {"cleaned_text": cleaned}

@app.post("/extract-resolve", response_model=ExtractResolveResponse)
def extract_resolve(req: ExtractResolveRequest):
citations = get_citations(req.text, **(req.options or {}))
clusters = resolve_citations(citations, **(req.options or {}))
return {"clusters": clusters}

if dump_citations:
@app.post("/dump", response_model=DumpResponse)
def dump(req: DumpRequest):
citations = dump_citations(req.text, **(req.options or {}))
return {"citations": citations}
17 changes: 17 additions & 0 deletions api/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import requests

BASE = "http://localhost:8000"

def test_extract():
resp = requests.post(f"{BASE}/extract", json={"text": "See 410 U.S. 113 (1973)."})
assert resp.status_code == 200
print("/extract:", resp.json())

def test_clean():
resp = requests.post(f"{BASE}/clean", json={"text": " See 410 U.S. 113 (1973). "})
assert resp.status_code == 200
print("/clean:", resp.json())

if __name__ == "__main__":
test_extract()
test_clean()
11 changes: 11 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fastapi
uvicorn
pydantic
fast_diff_match_patch
lxml
regex
pyahocorasick
reporters_db
courts_db
hyperscan
# Add any other dependencies for eyecite if not already in pyproject.toml
Loading