Skip to content

Commit fc0907a

Browse files
committed
Google Firestore support
1 parent 59e497b commit fc0907a

12 files changed

+504
-51
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
Changelog
22
=========
33

4+
## [v0.0.22] - 2024-04-26
5+
- Google Firestore support
6+
47
## [v0.0.21] - 2024-02-14
58
- use a singleton Azure Cosmos DB client for the lifetime of the application
69

README.md

+60-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Basic CRUD and query support for NoSQL databases, allowing for portable cloud na
44

55
- AWS DynamoDB <img height="15" width="15" src="https://unpkg.com/simple-icons@v9/icons/amazondynamodb.svg" />
66
- Azure Cosmos NoSQL <img height="15" width="15" src="https://unpkg.com/simple-icons@v9/icons/microsoftazure.svg" />
7+
- Google Firestore <img height="15" width="15" src="https://unpkg.com/simple-icons@v9/icons/firebase.svg" />
78

89
This library is not intended to create databases/tables, use Terraform/ARM/CloudFormation etc for that
910

@@ -28,10 +29,12 @@ Why not just use the name 'nosql' or 'pynosql'? because they already exist on py
2829
- [Configuration](#configuration)
2930
- [AWS DynamoDB](#aws-dynamodb)
3031
- [Azure Cosmos NoSQL](#azure-cosmos-nosql)
32+
- [Google Firestore](#google-firestore)
3133
- [Plugins and Hooks](#plugins-and-hooks)
3234
- [Testing](#testing)
3335
- [AWS DynamoDB](#aws-dynamodb-1)
3436
- [Azure Cosmos NoSQL](#azure-cosmos-nosql-1)
37+
- [Google Firestore](#google-firestore-1)
3538
- [CLI](#cli)
3639
- [Future Enhancements / Ideas](#future-enhancements--ideas)
3740

@@ -41,6 +44,7 @@ Why not just use the name 'nosql' or 'pynosql'? because they already exist on py
4144
```
4245
pip install 'abnosql[dynamodb]'
4346
pip install 'abnosql[cosmos]'
47+
pip install 'abnosql[firestore]'
4448
```
4549

4650
For optional [client side](#client-side-encryption) field level envelope encryption
@@ -121,6 +125,8 @@ During mocked tests, [SQLGlot](https://sqlglot.com/) is used to [execute](https:
121125

122126
Care should be taken with `query_sql()` to not to use SQL features that are specific to any specific provider (breaking the abstraction capability of using abnosql in the first place)
123127

128+
The Firestore plugin uses sqlglot to parse simple SQL statements (eg AND only supported)
129+
124130
## Indexes
125131

126132
Beyond partition and range keys defined on the table, indexes currently have limited support within abnosql
@@ -140,6 +146,8 @@ If you don't need to do any updates and only need to do create/replace, then the
140146

141147
All items being updated must actually exist first, or else exception raised
142148

149+
Firestore does not return updated item, so if this is required use `put_get` = `True` config variable
150+
143151

144152
## Existence Checking
145153

@@ -183,7 +191,7 @@ A few methods such as `get_item()`, `delete_item()` and `query()` need to know p
183191

184192
`query` and `query_sql` accept `limit` and `next` optional kwargs and return `next` in response. Use these to paginate.
185193

186-
This works for AWS DyanmoDB, however Azure Cosmos has a limitation with continuation token for cross partitions queries (see [Python SDK documentation](https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/cosmos/azure-cosmos)). For Cosmos, abnosql appends OFFSET and LIMIT in the SQL statement if not already present, and returns `next`. `limit` is defaulted to 100. See the tests for examples
194+
This works for AWS DyanmoDB & Firestore, however Azure Cosmos has a limitation with continuation token for cross partitions queries (see [Python SDK documentation](https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/cosmos/azure-cosmos)). For Cosmos, abnosql appends OFFSET and LIMIT in the SQL statement if not already present, and returns `next`. `limit` is defaulted to 100. See the tests for examples
187195

188196
## Audit
189197

@@ -228,6 +236,8 @@ This behaviour is enabled by default, however can be disabled by setting `ABNOSQ
228236

229237
To write an Azure Function / AWS Lambda that is able to process both DynamoDB and Cosmos events, look for `changeMetadata` first and if present use that otherwise look for `eventName` and `eventSourceARN` in the event payload assuming its DynamoDB
230238

239+
**Google Firestore** should support [triggering functions](https://firebase.google.com/docs/functions/firestore-events?gen=2nd#python-preview) similar to DynamoDB Streams, so changeMetadata is not required
240+
231241
## Client Side Encryption
232242

233243
If configured in table config with `kms` attribute, abnosql will perform client side encryption using AWS KMS or Azure KeyVault
@@ -288,6 +298,7 @@ if `ABNOSQL_DB` env var is not set, abnosql will attempt to apply defaults based
288298

289299
- `AWS_DEFAULT_REGION` - sets database to `dynamodb` (see [aws docs](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html))
290300
- `FUNCTIONS_WORKER_RUNTIME` - sets database to `cosmos` (see [azure docs](https://learn.microsoft.com/en-us/azure/azure-functions/functions-app-settings#functions_worker_runtime))
301+
- `K_SERVICE` - sets database to `firestore` (though this could also get confused if running on knative)
291302

292303

293304
## AWS DynamoDB
@@ -335,6 +346,36 @@ tb = table(
335346
)
336347
```
337348

349+
350+
## Google Firestore
351+
352+
Set the following environment variables:
353+
354+
- `ABNOSQL_DB` = "firestore"
355+
- `ABNOSQL_FIRESTORE_PROJECT` or `GOOGLE_CLOUD_PROJECT` = google cloud project
356+
- `ABNOSQL_FIRESTORE_DATABASE` = Firestore database
357+
- `ABNOSQL_FIRESTORE_CREDENTIALS` = oauth, optional - if using google CLI, its also picked up from `~/.config/gcloud/application_default_credentials.json` if found
358+
359+
**OR** - use the connection string format:
360+
361+
- `ABNOSQL_DB` = "firestore://project@credential:database"
362+
363+
Alternatively, define in config (though ideally you want to use env vars to avoid application / environment specific code).
364+
365+
```
366+
from abnosql import table
367+
368+
tb = table(
369+
'mytable',
370+
config={'project': 'foo', 'database': 'bar'},
371+
database='firestore'
372+
)
373+
```
374+
375+
See also https://cloud.google.com/firestore/docs/authentication
376+
377+
378+
338379
# Plugins and Hooks
339380

340381
abnosql uses pluggy and registers in the `abnosql.table` namespace
@@ -390,6 +431,23 @@ def test_something():
390431

391432
More examples in [tests/test_cosmos.py](./tests/test_cosmos.py)
392433

434+
435+
## Google Firestore
436+
437+
Use [python-mock-firestore](https://github.com/mdowds/python-mock-firestore) and pass `MockFirestore()` to table config as `client` attribute
438+
439+
Example:
440+
441+
```
442+
from mockfirestore import MockFirestore
443+
444+
445+
def test_something():
446+
tb = table('mytable', {'client': MockFirestore()})
447+
item = tb.get_item(foo='bar')
448+
449+
```
450+
393451
# CLI
394452

395453
Small abnosql CLI installed with few of the commands above
@@ -430,7 +488,7 @@ p2 p2.2 5 {'foo': 'bar', 'num': 5, 'list': [1, 2, 3]} [1, 2, 3]
430488

431489
- [x] client side encryption
432490
- [x] test pagination & exception handling
433-
- [ ] [Google Firestore](https://cloud.google.com/python/docs/reference/firestore/latest) support, ideally in the core library (though could be added outside via use of the plugin system). Would need something like [FireSQL](https://firebaseopensource.com/projects/jsayol/firesql/) implemented for oython, maybe via sqlglot
491+
- [x] [Google Firestore](https://cloud.google.com/python/docs/reference/firestore/latest) support, ideally in the core library (though could be added outside via use of the plugin system). Would need something like [FireSQL](https://firebaseopensource.com/projects/jsayol/firesql/) implemented for python, maybe via sqlglot
434492
- [ ] [Google Vault](https://cloud.google.com/python/docs/reference/cloudkms/latest/) KMS support
435493
- [ ] [Hashicorp Vault](https://github.com/hashicorp/vault-examples/blob/main/examples/_quick-start/python/example.py) KMS support
436494
- [ ] Simple caching (maybe) using globals (used for AWS Lambda / Azure Functions)

abnosql/plugins/table/cosmos.py

+3-7
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ def __init__(
125125
self.pm = pm
126126
self.name = name
127127
self.set_config(config)
128+
self.database = 'cosmos'
128129
self.database_client = DATABASE_CLIENT
129130
if os.environ.get('ABNOSQL_DISABLE_GLOBAL_CACHE', 'FALSE') == 'TRUE':
130131
self.database_client = None
@@ -316,15 +317,11 @@ def query(
316317
key = key or {}
317318
validate_query_attrs(key, filters)
318319
parameters = {
319-
f'@{k}': v
320-
for k, v in filters.items()
320+
f'@{k}': v for k, v in
321+
(filters | key).items()
321322
}
322323
# cosmos doesnt like hyphens in table names
323324
table_alias = 'c' if '-' in self.name else self.name
324-
parameters.update({
325-
f'@{k}': v
326-
for k, v in key.items()
327-
})
328325
statement = f'SELECT * FROM {table_alias}'
329326
op = 'WHERE'
330327
for param in parameters.keys():
@@ -337,7 +334,6 @@ def query(
337334
limit=limit,
338335
next=next
339336
)
340-
resp['items'] = kms_process_query_items(self.config, resp['items'])
341337
return resp
342338

343339
@cosmos_ex_handler()

abnosql/plugins/table/dynamodb.py

+1
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ def __init__(
156156
) -> None:
157157
self.pm = pm
158158
self.name = name
159+
self.database = 'dynamodb'
159160
self.set_config(config)
160161
self.session = self.config.get(
161162
'session', boto3.session.Session(

0 commit comments

Comments
 (0)