Skip to content

Commit 7709ea4

Browse files
refactor read/write connection pool (#240)
* refactor read/write connection pool * rename settings from POSTGRES_* to PG* * catch warnings * typo * Update stac_fastapi/pgstac/config.py Co-authored-by: Henry Rodman <[email protected]> * update from review * update docstring * add docs --------- Co-authored-by: Henry Rodman <[email protected]>
1 parent 9736027 commit 7709ea4

File tree

16 files changed

+400
-144
lines changed

16 files changed

+400
-144
lines changed

.github/workflows/cicd.yaml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,10 @@ jobs:
9090
- name: Load data and validate
9191
run: python -m stac_fastapi.pgstac.app & ./scripts/wait-for-it.sh localhost:8080 && python ./scripts/ingest_joplin.py http://localhost:8080 && ./scripts/validate http://localhost:8080
9292
env:
93-
POSTGRES_USER: username
94-
POSTGRES_PASS: password
95-
POSTGRES_DBNAME: postgis
96-
POSTGRES_HOST_READER: localhost
97-
POSTGRES_HOST_WRITER: localhost
98-
POSTGRES_PORT: 5432
9993
PGUSER: username
10094
PGPASSWORD: password
95+
PGHOST: localhost
96+
PGPORT: 5432
10197
PGDATABASE: postgis
10298
APP_HOST: 0.0.0.0
10399
APP_PORT: 8080

CHANGES.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,53 @@
44

55
### Changed
66

7+
- rename `POSTGRES_HOST_READER` to `PGHOST` in config **breaking change**
8+
- rename `POSTGRES_USER` to `PGUSER` in config **breaking change**
9+
- rename `POSTGRES_PASS` to `PGPASSWORD` in config **breaking change**
10+
- rename `POSTGRES_PORT` to `PGPORT` in config **breaking change**
11+
- rename `POSTGRES_DBNAME` to `PGDATABASE` in config **breaking change**
12+
```python
13+
from stac_fastapi.pgstac.config import PostgresSettings
14+
15+
# before
16+
settings = PostgresSettings(
17+
postgres_user="user",
18+
postgres_pass="password",
19+
postgres_host_reader="0.0.0.0",
20+
postgres_host_writer="0.0.0.0",
21+
postgres_port=1111,
22+
postgres_dbname="pgstac",
23+
)
24+
25+
# now
26+
settings = PostgresSettings(
27+
pguser="user",
28+
pgpassword="password",
29+
pghost="0.0.0.0",
30+
pgport=1111,
31+
pgdatabase="pgstac",
32+
)
33+
```
34+
35+
- rename `reader_connection_string` to `connection_string` in `PostgresSettings` class **breaking change**
736
- add `ENABLE_TRANSACTIONS_EXTENSIONS` env variable to enable `transaction` extensions
837
- disable transaction and bulk_transactions extensions by default **breaking change**
938
- update `stac-fastapi-*` version requirements to `>=5.2,<6.0`
1039
- add pgstac health-check in `/_mgmt/health`
1140
- switch from using pygeofilter to cql2
1241

42+
### Added
43+
44+
- add `write_connection_pool` option in `stac_fastapi.pgstac.db.connect_to_db` function
45+
- add `write_postgres_settings` option in `stac_fastapi.pgstac.db.connect_to_db` function to set specific settings for the `writer` DB connection pool
46+
47+
### removed
48+
49+
- `stac_fastapi.pgstac.db.DB` class
50+
- `POSTGRES_HOST_WRITER` in config
51+
- `writer_connection_string` in `PostgresSettings` class
52+
- `testing_connection_string` in `PostgresSettings` class
53+
1354
## [5.0.2] - 2025-04-07
1455

1556
### Fixed
@@ -183,7 +224,7 @@ As a part of this release, this repository was extracted from the main
183224
### Added
184225

185226
- Nginx service as second docker-compose stack to demonstrate proxy ([#503](https://github.com/stac-utils/stac-fastapi/pull/503))
186-
- Validation checks in CI using [stac-api-validator](github.com/stac-utils/stac-api-validator) ([#508](https://github.com/stac-utils/stac-fastapi/pull/508))
227+
- Validation checks in CI using [stac-api-validator](https://github.com/stac-utils/stac-api-validator) ([#508](https://github.com/stac-utils/stac-fastapi/pull/508))
187228
- Required links to the sqlalchemy ItemCollection endpoint ([#508](https://github.com/stac-utils/stac-fastapi/pull/508))
188229
- Publication of docker images to GHCR ([#525](https://github.com/stac-utils/stac-fastapi/pull/525))
189230

docker-compose.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ services:
88
- APP_PORT=8082
99
- RELOAD=true
1010
- ENVIRONMENT=local
11-
- POSTGRES_USER=username
12-
- POSTGRES_PASS=password
13-
- POSTGRES_DBNAME=postgis
14-
- POSTGRES_HOST_READER=database
15-
- POSTGRES_HOST_WRITER=database
16-
- POSTGRES_PORT=5432
11+
- PGUSER=username
12+
- PGPASS=password
13+
- PGDATABASE=postgis
14+
- PGHOST=database
15+
- PGPORT=5432
1716
- WEB_CONCURRENCY=10
1817
- VSI_CACHE=TRUE
1918
- GDAL_HTTP_MERGE_CONSECUTIVE_RANGES=YES

docs/api/stac_fastapi/pgstac/version.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/settings.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
2+
3+
### Application Extension
4+
5+
The default `stac-fastapi-pgstac` application comes will **all** extensions enabled (except transaction). Users can use `ENABLED_EXTENSIONS` environment variable to limit the supported extensions.
6+
7+
Available values for `ENABLED_EXTENSIONS`:
8+
9+
- `query`
10+
- `sort`
11+
- `fields`
12+
- `filter`
13+
- `free_text` (only for collection-search)
14+
- `pagination`
15+
- `collection_search`
16+
17+
Example: `ENABLED_EXTENSIONS="pagination,sort"`
18+
19+
20+
Since `6.0.0`, the transaction extension is not enabled by default. To add the transaction endpoints, users can set `ENABLE_TRANSACTIONS_EXTENSIONS=TRUE/YES/1`.
21+
22+
### Database config
23+
24+
- `PGUSER`: postgres username
25+
- `PGPASSWORD`: postgres password
26+
- `PGHOST`: hostname for the connection
27+
- `PGPORT`: database port
28+
- `PGDATABASE`: database name
29+
- `DB_MIN_CONN_SIZE`: Number of connection the pool will be initialized with. Defaults to `1`
30+
- `DB_MAX_CONN_SIZE` Max number of connections in the pool. Defaults to `10`
31+
- `DB_MAX_QUERIES`: Number of queries after a connection is closed and replaced with a new connection. Defaults to `50000`
32+
- `DB_MAX_INACTIVE_CONN_LIFETIME`: Number of seconds after which inactive connections in the pool will be closed. Defaults to `300`
33+
- `SEARCH_PATH`: Postgres search path. Defaults to `"pgstac,public"`
34+
- `APPLICATION_NAME`: PgSTAC Application name. Defaults to `"pgstac"`
35+
36+
##### Deprecated
37+
38+
In version `6.0.0` we've renamed the PG configuration variable to match the official naming convention:
39+
40+
- `POSTGRES_USER` -> `PGUSER`
41+
- `POSTGRES_PASS` -> `PGPASSWORD`
42+
- `POSTGRES_HOST_READER` -> `PGHOST`
43+
- `POSTGRES_HOST_WRITER` -> `PGHOST`*
44+
- `POSTGRES_PORT` -> `PGPORT`
45+
- `POSTGRES_DBNAME` -> `PGDATABASE`
46+
47+
\* Since version `6.0`, users cannot set a different host for `writer` and `reader` database but will need to customize the application and pass a specific `stac_fastapi.pgstac.config.PostgresSettings` instance to the `connect_to_db` function.
48+
49+
### Validation/Serialization
50+
51+
- `ENABLE_RESPONSE_MODELS`: use pydantic models to validate endpoint responses. Defaults to `False`
52+
- `ENABLE_DIRECT_RESPONSE`: by-pass the default FastAPI serialization by wrapping the endpoint responses into `starlette.Response` classes. Defaults to `False`
53+
54+
### Misc
55+
56+
- `STAC_FASTAPI_VERSION` (string) is the version number of your API instance (this is not the STAC version)
57+
- `STAC FASTAPI_TITLE` (string) should be a self-explanatory title for your API
58+
- `STAC FASTAPI_DESCRIPTION` (string) should be a good description for your API. It can contain CommonMark
59+
- `STAC_FASTAPI_LANDING_ID` (string) is a unique identifier for your Landing page
60+
- `ROOT_PATH`: set application root-path (when using proxy)
61+
- `CORS_ORIGINS`: A list of origins that should be permitted to make cross-origin requests. Defaults to `*`
62+
- `CORS_METHODS`: A list of HTTP methods that should be allowed for cross-origin requests. Defaults to `"GET,POST,OPTIONS"`
63+
- `USE_API_HYDRATE`: perform hydration of stac items within stac-fastapi
64+
- `INVALID_ID_CHARS`: list of characters that are not allowed in item or collection ids (used in Transaction endpoints)

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extra:
1515
# Layout
1616
nav:
1717
- Home: "index.md"
18+
- Configuration: "settings.md"
1819
- API:
1920
- stac_fastapi.pgstac:
2021
- module: api/stac_fastapi/pgstac/index.md

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"brotli_asgi",
1818
"cql2>=0.3.6",
1919
"pypgstac>=0.8,<0.10",
20+
"typing_extensions>=4.9.0",
2021
]
2122

2223
extra_reqs = {

stac_fastapi/pgstac/app.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,12 @@
9595

9696
application_extensions = []
9797

98-
if os.environ.get("ENABLE_TRANSACTIONS_EXTENSIONS", "").lower() in ["yes", "true", "1"]:
98+
with_transactions = os.environ.get("ENABLE_TRANSACTIONS_EXTENSIONS", "").lower() in [
99+
"yes",
100+
"true",
101+
"1",
102+
]
103+
if with_transactions:
99104
application_extensions.append(
100105
TransactionExtension(
101106
client=TransactionsClient(),
@@ -150,7 +155,7 @@
150155
@asynccontextmanager
151156
async def lifespan(app: FastAPI):
152157
"""FastAPI Lifespan."""
153-
await connect_to_db(app)
158+
await connect_to_db(app, add_write_connection_pool=with_transactions)
154159
yield
155160
await close_db_connection(app)
156161

stac_fastapi/pgstac/config.py

Lines changed: 95 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Postgres API configuration."""
22

3-
from typing import List, Type
3+
import warnings
4+
from typing import Annotated, Any, List, Optional, Type
45
from urllib.parse import quote_plus as quote
56

6-
from pydantic import BaseModel, field_validator
7+
from pydantic import BaseModel, Field, field_validator, model_validator
78
from pydantic_settings import BaseSettings, SettingsConfigDict
89
from stac_fastapi.types.config import ApiSettings
910

@@ -52,22 +53,60 @@ class PostgresSettings(BaseSettings):
5253
"""Postgres-specific API settings.
5354
5455
Attributes:
55-
postgres_user: postgres username.
56-
postgres_pass: postgres password.
57-
postgres_host_reader: hostname for the reader connection.
58-
postgres_host_writer: hostname for the writer connection.
59-
postgres_port: database port.
60-
postgres_dbname: database name.
61-
use_api_hydrate: perform hydration of stac items within stac-fastapi.
62-
invalid_id_chars: list of characters that are not allowed in item or collection ids.
56+
pguser: postgres username.
57+
pgpassword: postgres password.
58+
pghost: hostname for the connection.
59+
pgport: database port.
60+
pgdatabase: database name.
61+
6362
"""
6463

65-
postgres_user: str
66-
postgres_pass: str
67-
postgres_host_reader: str
68-
postgres_host_writer: str
69-
postgres_port: int
70-
postgres_dbname: str
64+
postgres_user: Annotated[
65+
Optional[str],
66+
Field(
67+
deprecated="`postgres_user` is deprecated, please use `pguser`", default=None
68+
),
69+
]
70+
postgres_pass: Annotated[
71+
Optional[str],
72+
Field(
73+
deprecated="`postgres_pass` is deprecated, please use `pgpassword`",
74+
default=None,
75+
),
76+
]
77+
postgres_host_reader: Annotated[
78+
Optional[str],
79+
Field(
80+
deprecated="`postgres_host_reader` is deprecated, please use `pghost`",
81+
default=None,
82+
),
83+
]
84+
postgres_host_writer: Annotated[
85+
Optional[str],
86+
Field(
87+
deprecated="`postgres_host_writer` is deprecated, please use `pghost`",
88+
default=None,
89+
),
90+
]
91+
postgres_port: Annotated[
92+
Optional[int],
93+
Field(
94+
deprecated="`postgres_port` is deprecated, please use `pgport`", default=None
95+
),
96+
]
97+
postgres_dbname: Annotated[
98+
Optional[str],
99+
Field(
100+
deprecated="`postgres_dbname` is deprecated, please use `pgdatabase`",
101+
default=None,
102+
),
103+
]
104+
105+
pguser: str
106+
pgpassword: str
107+
pghost: str
108+
pgport: int
109+
pgdatabase: str
71110

72111
db_min_conn_size: int = 1
73112
db_max_conn_size: int = 10
@@ -78,24 +117,52 @@ class PostgresSettings(BaseSettings):
78117

79118
model_config = {"env_file": ".env", "extra": "ignore"}
80119

120+
@model_validator(mode="before")
121+
@classmethod
122+
def _pg_settings_compat(cls, data: Any) -> Any:
123+
if isinstance(data, dict):
124+
compat = {
125+
"postgres_user": "pguser",
126+
"postgres_pass": "pgpassword",
127+
"postgres_host_reader": "pghost",
128+
"postgres_host_writer": "pghost",
129+
"postgres_port": "pgport",
130+
"postgres_dbname": "pgdatabase",
131+
}
132+
for old_key, new_key in compat.items():
133+
if val := data.get(old_key, None):
134+
warnings.warn(
135+
f"`{old_key}` is deprecated, please use `{new_key}`",
136+
DeprecationWarning,
137+
stacklevel=1,
138+
)
139+
data[new_key] = val
140+
141+
if (pgh_reader := data.get("postgres_host_reader")) and (
142+
pgh_writer := data.get("postgres_host_writer")
143+
):
144+
if pgh_reader != pgh_writer:
145+
raise ValueError(
146+
"In order to use different host values for reading and writing "
147+
"you must explicitly provide write_postgres_settings to the connect_to_db function"
148+
)
149+
150+
return data
151+
81152
@property
82-
def reader_connection_string(self):
153+
def connection_string(self):
83154
"""Create reader psql connection string."""
84-
return f"postgresql://{self.postgres_user}:{quote(self.postgres_pass)}@{self.postgres_host_reader}:{self.postgres_port}/{self.postgres_dbname}"
155+
return f"postgresql://{self.pguser}:{quote(self.pgpassword)}@{self.pghost}:{self.pgport}/{self.pgdatabase}"
85156

86-
@property
87-
def writer_connection_string(self):
88-
"""Create writer psql connection string."""
89-
return f"postgresql://{self.postgres_user}:{quote(self.postgres_pass)}@{self.postgres_host_writer}:{self.postgres_port}/{self.postgres_dbname}"
90157

91-
@property
92-
def testing_connection_string(self):
93-
"""Create testing psql connection string."""
94-
return f"postgresql://{self.postgres_user}:{quote(self.postgres_pass)}@{self.postgres_host_writer}:{self.postgres_port}/pgstactestdb"
158+
class Settings(ApiSettings):
159+
"""API settings.
95160
161+
Attributes:
162+
use_api_hydrate: perform hydration of stac items within stac-fastapi.
163+
invalid_id_chars: list of characters that are not allowed in item or collection ids.
96164
97-
class Settings(ApiSettings):
98-
"""Api Settings."""
165+
"""
99166

100167
use_api_hydrate: bool = False
101168
invalid_id_chars: List[str] = DEFAULT_INVALID_ID_CHARS

0 commit comments

Comments
 (0)