Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4a4937c
urllib3 2.2.3 → 2.6.
GinaStavropoulou Aug 18, 2026
8d04a71
switched to uv
GinaStavropoulou Aug 18, 2026
0288191
urllib3 2.7.0
GinaStavropoulou Aug 18, 2026
c82674e
pytest upgrade
GinaStavropoulou Aug 18, 2026
eaf524b
removed black added ruff
GinaStavropoulou Aug 18, 2026
e07180c
removed setuptools
GinaStavropoulou Aug 18, 2026
952713c
gitignore
GinaStavropoulou Aug 20, 2026
fbeadf5
ruff formatting
GinaStavropoulou Aug 20, 2026
7bf59ae
unsafe fixes
GinaStavropoulou Aug 20, 2026
3b251e4
fix exceptions
GinaStavropoulou Aug 20, 2026
bc878b6
exceptions
GinaStavropoulou Aug 20, 2026
3e881f0
RUF012
GinaStavropoulou Aug 20, 2026
011d758
manually fixed ruff errors
GinaStavropoulou Aug 20, 2026
b9e7183
some unit tests
GinaStavropoulou Aug 20, 2026
c39754a
added coverage
GinaStavropoulou Aug 20, 2026
4398a5b
added action
GinaStavropoulou Aug 20, 2026
ab6074f
gitignore update
GinaStavropoulou Aug 20, 2026
c8fc57b
fixed types
GinaStavropoulou Aug 20, 2026
519b135
mypy to action
GinaStavropoulou Aug 20, 2026
da7d5f4
renamed changelog
GinaStavropoulou Aug 20, 2026
4c613ba
test in action
GinaStavropoulou Aug 20, 2026
1107741
upgraded to bookworm
GinaStavropoulou Aug 20, 2026
f308baa
added postgis
GinaStavropoulou Aug 20, 2026
0b12f20
Merge pull request #48 from cityjson/fix-vulnerabilities
GinaStavropoulou Aug 20, 2026
5e844fe
fixed issue #47
GinaStavropoulou Aug 20, 2026
e3f2998
formatting
GinaStavropoulou Aug 20, 2026
fbd20b6
error handling for #39
GinaStavropoulou Aug 20, 2026
26067c5
removed test files
GinaStavropoulou Aug 20, 2026
669ed1c
releasing
GinaStavropoulou Aug 20, 2026
82fb905
v 2.3.0
GinaStavropoulou Aug 20, 2026
b121cf2
update docs for v2.3.0
GinaStavropoulou Aug 20, 2026
6352fb1
udpate RELEASING
GinaStavropoulou Aug 20, 2026
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
33 changes: 33 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Lint

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

permissions:
contents: read

jobs:
ruff:
name: Ruff lint & format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v10.0.1
with:
enable-cache: true

- name: Install dependencies
run: uv sync --frozen --group dev

- name: Ruff check
run: uv run ruff check cjdb tests

- name: Ruff format check
run: uv run ruff format --check cjdb tests

- name: Mypy type check
run: uv run mypy cjdb tests
37 changes: 37 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Test

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

permissions:
contents: read

jobs:
test:
name: Tests (PostgreSQL)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v10.0.1
with:
enable-cache: true
python-version: "3.11"

- name: Install PostgreSQL and PostGIS
run: |
sudo apt-get update
sudo apt-get install -y postgresql
PG_VERSION=$(ls /usr/lib/postgresql | head -n1)
sudo apt-get install -y postgresql-${PG_VERSION}-postgis-3

- name: Install dependencies
run: uv sync --frozen --group dev

- name: Run tests
run: |
PG_CTL=$(find /usr/lib/postgresql -name pg_ctl | head -n1)
uv run pytest tests/ --cov=cjdb --cov-report=term-missing --postgresql-exec="$PG_CTL"
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
mypy.ini
todo
venv
.venv
*.egg-info
*.whl
*.gz
done
done
.coverage
.mypy_cache
.pytest_cache
.ruff_cache
32 changes: 32 additions & 0 deletions changelog.md → CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,38 @@ for soon-to-be removed features.

`Security` in case of vulnerabilities.

## [2.3.0] - 2026-08-20

`Added`
- Clear error message when a CityJSON object's resolved geometry exceeds the
PostgreSQL jsonb size limit (~256 MB), instead of the cryptic database error
(issue #39)
- GitHub Actions CI: linting (ruff + mypy) and testing (PostgreSQL/PostGIS)
workflows
- Additional unit and integration tests (jsonb size estimation, metadata/CRS
handling, grid-download error handling, missing input path)
- Coverage reporting with pytest-cov

`Changed`
- Switched dependency management and build tooling from Poetry to `uv`
(`uv.lock`, hatchling build backend)
- Docker image now based on `python:3.11-slim-bookworm` and built with `uv`
- Replaced Black/isort/flake8 with Ruff
- Files without a `metadata` member no longer abort the import: it proceeds,
and a `MissingCRSException` is raised only when the CRS cannot be determined
(no metadata and no `-I/--srid` flag) (issue #47)
- Removed `InvalidMetadataException`
- Minimum supported Python version raised to 3.10

`Fixed`
- Partial attribute indexes: the `WHERE attributes->>'...' IS NOT NULL`
condition was previously never included in the index definition
- Overly broad exception handlers narrowed (`except Exception`,
`except ...`, `except BaseException`)
- The exporter now always closes its output file (context manager)
- Re-raised exceptions preserve the original traceback
- urllib3 updated to 2.7.0 to address vulnerabilities

## [2.2.0] - 2025-02-12
`Added`
Flag --clustering to make clustering optional
Expand Down
159 changes: 159 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Releasing a new version of cjdb

This document describes how to release a new version of `cjdb`. It is meant for the maintainers.

## Versioning

- cjdb follows [Semantic Versioning](https://semver.org/) (`MAJOR.MINOR.PATCH`).
- The version lives in a single place: the `version` field in [`pyproject.toml`](pyproject.toml). `cjdb.__version__` reads it via `importlib.metadata`, so nothing else needs to change.
- Releases are tagged `v<version>` (e.g. `v2.2.0`) on the `main` branch.
- `main` only contains release commits; development happens on `develop` and feature branches.

## Prerequisites

- Maintainer access to the `cityjson/cjdb` GitHub repository.
- An account on [PyPI](https://pypi.org) that is a maintainer of the `cjdb` project, with credentials set up for [twine](https://twine.readthedocs.io/) (e.g. a `~/.pypirc` or an API token in `TWINE_PASSWORD`).
- Access to the container registry the Docker image is published to (see [Docker image](#4-build-and-publish-the-docker-image)).

## Release checklist

Perform the checks below from the repository root.

### 0. Verify the release branch is ready

```bash
git checkout develop
git pull
```

Make sure the CI workflows pass on `develop` (they run on every push/PR):

- **Lint** (`.github/workflows/lint.yml`): ruff lint, ruff format, mypy.
- **Test** (`.github/workflows/test.yml`): the full pytest suite against a PostgreSQL/PostGIS instance.

Run the same checks locally as a final gate:

```bash
make test # pytest (needs a local PostgreSQL/PostGIS) + mypy
uv run ruff check cjdb tests
uv run ruff format --check cjdb tests
```

### 1. Bump the version

Edit the `version` field in [`pyproject.toml`](pyproject.toml):

```toml
version = "2.3.0"
```

### 2. Update the changelog

Add an entry at the top of [`CHANGELOG.md`](CHANGELOG.md), following the
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format already in use:

```markdown
## [2.3.0] - YYYY-MM-DD
`Added`
- new feature

`Changed`
- behavioural change

`Fixed`
- bug fix
```

### 3. Open a PR and merge to `main`

Commit the version bump and changelog, open a pull request `develop` → `main`,
wait for the CI checks to go green, and merge it.

Then:

```bash
git checkout main
git pull
```

### 4. Rebuild and commit the docs

The online docs (https://cityjson.github.io/cjdb) are built with Sphinx from
the `sphinx/` directory and committed as static HTML in `docs/` (served via
GitHub Pages). Rebuild them so they reflect the new version:

```bash
cd sphinx
uv run sphinx-build -b html . ../docs
cd ..
git add docs/
git commit -m "update docs for v2.3.0"
git push
```

### 5. Tag the release

```bash
git tag -a v2.3.0 -m "cjdb v2.3.0"
git push origin v2.3.0
```

### 6. Publish to PyPI

Build the wheel and source distribution, then upload them. There is no CI
workflow for publishing, so this is done manually:

```bash
uv build
uvx twine upload dist/*
```

Verify the package installs from PyPI:

```bash
pip install --index-url https://test.pypi.org/simple --extra-index-url https://pypi.org/simple cjdb # deps (cjio, etc.) come from PyPI
# or, once live:
pip install -U cjdb
cjdb --help
```

### 7. Build and publish the Docker image

There is no CI workflow for the image either; build and push it manually.
Tag it with the version and, for convenience, `latest`:

```bash
docker build -t cjdb:2.3.0 -t cjdb:latest .
docker tag cjdb:2.3.0 <registry>/cityjson/cjdb:2.3.0
docker tag cjdb:latest <registry>/cityjson/cjdb:latest
docker push <registry>/cityjson/cjdb:2.3.0
docker push <registry>/cityjson/cjdb:latest
```

### 8. Create the GitHub release

Go to https://github.com/cityjson/cjdb/releases/new, choose the `v2.3.0` tag,
and paste the changelog entry for this version into the release notes.

### 9. Prepare `develop` for the next cycle (optional)

If the next release is likely to be a feature release, you can leave the
version as-is and bump it at release time. If you prefer to keep `develop`
ahead, bump it to the next `MINOR` version after tagging:

```bash
git checkout develop
# edit version to "2.4.0" in pyproject.toml
git commit -am "bump version to 2.4.0"
git push
```

## Rollback

- **PyPI**: versions cannot be deleted; release a new `PATCH` version with the
fix instead. You can `yank` a broken release from
https://pypi.org/project/cjdb.
- **GitHub release / tag**: delete and re-create the tag and release, then
force-push the corrected `main`.
- **Docker image**: overwrite the `latest` tag with the previous version and
keep the broken version tag in place for provenance.
5 changes: 1 addition & 4 deletions cjdb/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
try:
import importlib.metadata as importlib_metadata
except ModuleNotFoundError:
import importlib_metadata
import importlib.metadata as importlib_metadata

__version__ = importlib_metadata.version(__name__)
2 changes: 1 addition & 1 deletion cjdb/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def export_cj(query, host, port, user, password, database, schema, output):
base = os.path.basename(output)
dirname = os.path.abspath(os.path.dirname(output))
if not os.path.exists(dirname):
raise click.ClickException('Output path does not exist: "%s"' % (dirname))
raise click.ClickException(f'Output path does not exist: "{dirname}"')
output_abs = os.path.join(dirname, base)
conn = get_db_psycopg_conn(user, password, host, port, database)
with Exporter(conn, schema, query, output_abs) as exp:
Expand Down
13 changes: 7 additions & 6 deletions cjdb/model/sqlalchemy_models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from typing import ClassVar

from geoalchemy2 import Geometry
from sqlalchemy import (Column, ForeignKey, Integer, String, UniqueConstraint,
func)
from sqlalchemy import Column, ForeignKey, Integer, String, UniqueConstraint, func
from sqlalchemy.dialects.postgresql import JSONB, TIMESTAMP
from sqlalchemy.orm import declarative_base, relationship

Base = declarative_base()


class BaseModel(Base):
class BaseModel(Base): # type: ignore[valid-type, misc]
__abstract__ = True
id = Column(Integer, primary_key=True)

Expand All @@ -18,7 +19,7 @@ def NullableJSONB():

class CjMetadataModel(BaseModel):
__tablename__ = "cj_metadata"
__table_args__ = {"schema": "cjdb"}
__table_args__: ClassVar[dict] = {"schema": "cjdb"}
source_file = Column(String)
version = Column(String(10), nullable=False)
meta = Column(JSONB, name="metadata")
Expand Down Expand Up @@ -58,7 +59,7 @@ def different_srid_meta(self, session):

class CjObjectModel(BaseModel):
__tablename__ = "city_object"
__table_args__ = {"schema": "cjdb"}
__table_args__: ClassVar[dict] = {"schema": "cjdb"}
cj_metadata_id = Column(Integer, ForeignKey(CjMetadataModel.id, ondelete="CASCADE"))
object_id = Column(String, nullable=False)
type = Column(String, nullable=False)
Expand Down Expand Up @@ -103,7 +104,7 @@ def get_max_id(cls, session) -> int:

class CityObjectRelationshipModel(BaseModel):
__tablename__ = "city_object_relationships"
__table_args__ = {"schema": "cjdb"}
__table_args__: ClassVar[dict] = {"schema": "cjdb"}
parent_id = Column(Integer, ForeignKey(CjObjectModel.id, ondelete="CASCADE"))
child_id = Column(Integer, ForeignKey(CjObjectModel.id, ondelete="CASCADE"))

Expand Down
4 changes: 3 additions & 1 deletion cjdb/modules/checks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from urllib.error import URLError

from pyproj import CRS, datadir
from pyproj.transformer import TransformerGroup

Expand Down Expand Up @@ -56,7 +58,7 @@ def check_reprojection(source_srid, target_srid):

try:
group.download_grids(datadir.get_data_dir())
except ...:
except (URLError, OSError):
logger.warning("Failed to download the missing grids.")
else:
logger.info("Download successful.")
Loading
Loading