Organization API rejects valid organization_type values
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 200
- Avg merge
- 4h 56m
- Merged PRs (30d)
- 5
Description
`POST /api/organization/add` fails with a 400 for any request that includes a valid `organization_type` value (e.g. `"Non-Profit"`), even though the value is one of the documented valid options.
- **Component:** `datagov-harvester` admin app, `app/api/organizations.py` / `app/api_schemas.py`
- **Environment:** reproduced locally (`flask run` against a local Postgres), branch `6180-catalog-metadata`, but the root cause is unrelated to that branch and appears on `main` as well.
**Error logs:**
```
ERROR:root:Error: (builtins.LookupError) 'OrganizationType.Non-Profit' is not among the defined enum values. Enum name: organization_type_enum. Possible values: Federal Gov.., City Govern.., State Gover.., ..., Non-Profit
[SQL: INSERT INTO organization (name, logo, description, slug, organization_type, aliases, id) VALUES (%(name)s::VARCHAR, %(logo)s::VARCHAR, %(description)s::VARCHAR, %(slug)s::VARCHAR, %(organization_type)s, %(aliases)s::VARCHAR[], %(id)s::VARCHAR)]
[parameters: [{'slug': 'opentopography', 'name': 'OpenTopography', 'organization_type': , 'logo': None, 'description': None, 'aliases': None}]]
```
## How to reproduce
1. Start the admin app locally against a local Postgres instance.
2. Send:
```bash
curl -s -X POST http://localhost:8080/api/organization/add -H "X-API-Key: " -H "Content-Type: application/json" -d '{"name": "OpenTopography", "slug": "opentopography", "organization_type": "Non-Profit"}'
```
3. Observe the response and server log.
## Expected behavior
The organization is created successfully with `organization_type` set to `"Non-Profit"`, matching the value sent in the request (same as it works today when `organization_type` is omitted from the payload).
## Actual behavior
The request returns `{"error":"Failed to add organization."}` (HTTP 400). The server log shows the actual underlying `LookupError` above — the insert is rejected by Postgres because the value being bound is the string `"OrganizationType.Non-Profit"` instead of `"Non-Profit"`.
Root cause: `app/api_schemas.py`'s `OrgCreate.organization_type` field is built via `_to_enum("OrganizationType", ORGANIZATION_TYPE_VALUES)`, which deserializes the incoming JSON string into an actual Python `Enum` **member** (`OrganizationType.Non-Profit`), not a plain string. That enum member then flows straight through `deps.add_organization()` → `Organization(**org_data)` without ever calling `.value` on it. The `Organization.organization_type` database column is a plain string-based SQLAlchemy `Enum(*ORGANIZATION_TYPE_VALUES, name="organization_type_enum")` (defined in `datagov-databases`), so when SQLAlchemy binds the Python `Enum` member as a query parameter, it stringifies it via the enum's default `__str__`, producing `"OrganizationType.Non-Profit"` — not a valid value in the Postgres `organization_type_enum` type.
Any request that omits `organization_type` succeeds today, since that fallback path never encounters this conversion at all. This means organizations can currently only be created via this API by leaving `organization_type` unset, or by inserting directly into the database.
## Sketch
- Convert the deserialized enum member back to its `.value` before handing `org_data` to `Organization(...)` — likely in `add_organization_api()` in `app/api/organizations.py`, or inside `deps.add_organization()` — so a plain string reaches the SQLAlchemy `Enum` column, consistent with how the raw column is defined.
- Check whether `edit_organization_api()` / `update_organization()` have the same issue when updating `organization_type` on an existing org.
- Confirm whether the web UI's organization form goes through the same `OrgCreate` schema/path, or a different one — if the same, it's likely affected too and should be covered by the same fix.
- Add a regression test asserting an organization can be created via the API with each valid `organization_type` value, not just with the field omitted.
Contributor guide
Research direction
Start with app/api/organizations.py and app/api_schemas.py, then trace deps.add_organization() into the Organization model and its organization_type column. Reproduce the POST request locally against Postgres, and inspect the edit/update path for the same conversion issue. Done means valid organization_type values create successfully, updates are checked, and regression coverage exercises every valid value.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- flask, postgresql, python, sqlalchemy
- Domain
- api, backend, database
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100