Skip to content
Merged
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
22 changes: 22 additions & 0 deletions .github/workflows/pr_agent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
on:
pull_request:
types: [opened, reopened, ready_for_review]
issue_comment:
jobs:
pr_agent_job:
if: ${{ github.event.sender.type != 'Bot' }}
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
contents: write
name: Run pr agent on every pull request, respond to user comments
steps:
- name: PR Agent action step
id: pragent
uses: qodo-ai/[email protected]
with:
args: '/improve --pr_code_suggestions.commitable_code_suggestions=true'
env:
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
10 changes: 6 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
name: Test
on: [push, pull_request]
on: [ push, pull_request ]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ '3.6', '3.7', '3.8', '3.9', '3.10' ]
python-version: [ '3.9', '3.10', '3.11', '3.12' ]

steps:
- uses: actions/checkout@v2
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,4 @@ venv.bak/
.mypy_cache/

.idea/
.qodo
37 changes: 37 additions & 0 deletions agent-coding-standards.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Agent Coding Standards

# ALL CODE MUSE BE UP TO THIS STANDARD

## Follow Clean Code principles (Robert C. Martin) when writing code

- Specifically the correct order of functions

## No comments!

- Most likely there is absolutely no reason to add a comment
- If there is, it's probably a sign that the code is not clear

## Don't use dicts.

- str keys are red
- Use pydantic models instead

## Do not hallucinate

- If you are not sure, ask the user
- If you don't know the library, do the research

## Do not regress when moving code

- Make sure quality of moved code is at least as good as the original

## Don't make me ask you twice

- Follow all these rules
- Every time, all the time
- If it's hard, ask me how to solve it

## Only essential complexity, not accidental

- Use the simplest approach that works
- Question whether each line adds value or just complexity
11 changes: 6 additions & 5 deletions mockfirestore/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from mockfirestore import AlreadyExists
from mockfirestore._helpers import generate_random_string, Store, get_by_path, set_by_path, Timestamp
from mockfirestore.query import Query
from mockfirestore.query import Query, FieldFilter
from mockfirestore.document import DocumentReference, DocumentSnapshot


Expand Down Expand Up @@ -41,9 +41,10 @@ def add(self, document_data: Dict, document_id: str = None) \
timestamp = Timestamp.from_now()
return timestamp, doc_ref

def where(self, field: str, op: str, value: Any) -> Query:
query = Query(self, field_filters=[(field, op, value)])
return query
def where(self, field: str = None, op: str = None, value: Any = None, *, filter: FieldFilter = None) -> Query:
if filter is not None:
return Query(self, field_filters=[(filter.field, filter.op, filter.value)])
return Query(self, field_filters=[(field, op, value)])

def order_by(self, key: str, direction: Optional[str] = None) -> Query:
query = Query(self, orders=[(key, direction)])
Expand Down Expand Up @@ -82,4 +83,4 @@ def list_documents(self, page_size: Optional[int] = None) -> Sequence[DocumentRe
def stream(self, transaction=None) -> Iterable[DocumentSnapshot]:
for key in sorted(get_by_path(self._data, self._path)):
doc_snapshot = self.document(key).get()
yield doc_snapshot
yield doc_snapshot
21 changes: 18 additions & 3 deletions mockfirestore/query.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import warnings
from itertools import islice, tee
from typing import Iterator, Any, Optional, List, Callable, Union
from dataclasses import dataclass

from mockfirestore.document import DocumentSnapshot
from mockfirestore._helpers import T

@dataclass
class FieldFilter:
field: str
op: str
value: Any

def __init__(self, field: str, op: str, value: Any):
self.field = field
self.op = op
self.value = value


class Query:
def __init__(self, parent: 'CollectionReference', projection=None,
Expand Down Expand Up @@ -61,8 +73,11 @@ def _add_field_filter(self, field: str, op: str, value: Any):
compare = self._compare_func(op)
self._field_filters.append((field, compare, value))

def where(self, field: str, op: str, value: Any) -> 'Query':
self._add_field_filter(field, op, value)
def where(self, field: str = None, op: str = None, value: Any = None, *, filter: FieldFilter = None) -> 'Query':
if filter is not None:
self._add_field_filter(filter.field, filter.op, filter.value)
else:
self._add_field_filter(field, op, value)
return self

def order_by(self, key: str, direction: Optional[str] = 'ASCENDING') -> 'Query':
Expand Down Expand Up @@ -136,4 +151,4 @@ def _compare_func(self, op: str) -> Callable[[T, T], bool]:
elif op == 'array_contains':
return lambda x, y: y in x
elif op == 'array_contains_any':
return lambda x, y: any([val in y for val in x])
return lambda x, y: any([val in y for val in x])
2 changes: 1 addition & 1 deletion requirements-dev-minimal.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
google-cloud-firestore
google-cloud-firestore
6 changes: 2 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="mock-firestore",
version="0.11.0",
version="0.12.0",
author="Matt Dowds",
description="In-memory implementation of Google Cloud Firestore for use in tests",
long_description=long_description,
Expand All @@ -14,11 +14,9 @@
packages=setuptools.find_packages(),
test_suite='',
classifiers=[
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
"License :: OSI Approved :: MIT License",
],
)
138 changes: 138 additions & 0 deletions tests/test_where_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
from unittest import TestCase

from mockfirestore import MockFirestore
from mockfirestore.query import FieldFilter


class TestWhereField(TestCase):
def test_collection_whereEquals(self):
fs = MockFirestore()
fs._data = {'foo': {
'first': {'valid': True},
'second': {'gumby': False}
}}

docs = list(fs.collection('foo').where(field='valid', op='==', value=True).stream())
self.assertEqual({'valid': True}, docs[0].to_dict())

def test_collection_whereEquals_with_filter(self):
fs = MockFirestore()
fs._data = {'foo': {
'first': {'valid': True},
'second': {'gumby': False}
}}

docs = list(fs.collection('foo').where(filter=FieldFilter('valid', '==', True)).stream())
self.assertEqual({'valid': True}, docs[0].to_dict())

def test_collection_whereNotEquals(self):
fs = MockFirestore()
fs._data = {'foo': {
'first': {'count': 1},
'second': {'count': 5}
}}

docs = list(fs.collection('foo').where('count', '!=', 1).stream())
self.assertEqual({'count': 5}, docs[0].to_dict())

def test_collection_whereLessThan(self):
fs = MockFirestore()
fs._data = {'foo': {
'first': {'count': 1},
'second': {'count': 5}
}}

docs = list(fs.collection('foo').where('count', '<', 5).stream())
self.assertEqual({'count': 1}, docs[0].to_dict())

def test_collection_whereLessThanOrEqual(self):
fs = MockFirestore()
fs._data = {'foo': {
'first': {'count': 1},
'second': {'count': 5}
}}

docs = list(fs.collection('foo').where('count', '<=', 5).stream())
self.assertEqual({'count': 1}, docs[0].to_dict())
self.assertEqual({'count': 5}, docs[1].to_dict())

def test_collection_whereGreaterThan(self):
fs = MockFirestore()
fs._data = {'foo': {
'first': {'count': 1},
'second': {'count': 5}
}}

docs = list(fs.collection('foo').where('count', '>', 1).stream())
self.assertEqual({'count': 5}, docs[0].to_dict())

def test_collection_whereGreaterThanOrEqual(self):
fs = MockFirestore()
fs._data = {'foo': {
'first': {'count': 1},
'second': {'count': 5}
}}

docs = list(fs.collection('foo').where('count', '>=', 1).stream())
self.assertEqual({'count': 1}, docs[0].to_dict())
self.assertEqual({'count': 5}, docs[1].to_dict())

def test_collection_whereMissingField(self):
fs = MockFirestore()
fs._data = {'foo': {
'first': {'count': 1},
'second': {'count': 5}
}}

docs = list(fs.collection('foo').where('no_field', '==', 1).stream())
self.assertEqual(len(docs), 0)

def test_collection_whereNestedField(self):
fs = MockFirestore()
fs._data = {'foo': {
'first': {'nested': {'a': 1}},
'second': {'nested': {'a': 2}}
}}

docs = list(fs.collection('foo').where('nested.a', '==', 1).stream())
self.assertEqual(len(docs), 1)
self.assertEqual({'nested': {'a': 1}}, docs[0].to_dict())

def test_collection_whereIn(self):
fs = MockFirestore()
fs._data = {'foo': {
'first': {'field': 'a1'},
'second': {'field': 'a2'},
'third': {'field': 'a3'},
'fourth': {'field': 'a4'},
}}

docs = list(fs.collection('foo').where('field', 'in', ['a1', 'a3']).stream())
self.assertEqual(len(docs), 2)
self.assertEqual({'field': 'a1'}, docs[0].to_dict())
self.assertEqual({'field': 'a3'}, docs[1].to_dict())

def test_collection_whereArrayContains(self):
fs = MockFirestore()
fs._data = {'foo': {
'first': {'field': ['val4']},
'second': {'field': ['val3', 'val2']},
'third': {'field': ['val3', 'val2', 'val1']}
}}

docs = list(fs.collection('foo').where('field', 'array_contains', 'val1').stream())
self.assertEqual(len(docs), 1)
self.assertEqual(docs[0].to_dict(), {'field': ['val3', 'val2', 'val1']})

def test_collection_whereArrayContainsAny(self):
fs = MockFirestore()
fs._data = {'foo': {
'first': {'field': ['val4']},
'second': {'field': ['val3', 'val2']},
'third': {'field': ['val3', 'val2', 'val1']}
}}

contains_any_docs = list(fs.collection('foo').where('field', 'array_contains_any', ['val1', 'val4']).stream())
self.assertEqual(len(contains_any_docs), 2)
self.assertEqual({'field': ['val4']}, contains_any_docs[0].to_dict())
self.assertEqual({'field': ['val3', 'val2', 'val1']}, contains_any_docs[1].to_dict())