Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ETag + current value in case of operation's failure #54

Merged
merged 1 commit into from Nov 30, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 36 additions & 0 deletions README.md
Expand Up @@ -234,6 +234,42 @@ data = {

db.update(data)
```
#### Conditional Requests

It's possible to do conditional sets and removes by using the `conditional_set()` and `conitional_remove()` methods respectively. You can read more about conditional requests in Firebase [here](https://firebase.google.com/docs/reference/rest/database/#section-conditional-requests).

To use these methods, you first get the ETag of a particular path by using the `get_etag()` method. You can then use that tag in your conditional request.

```python
etag = db.child("users").child("Morty").get_etag()
data = {"name": "Mortimer 'Morty' Smith"}
db.child("users").child("Morty").conditional_set(data, etag["ETag"])
```

If the passed ETag does not match the ETag of the path in the database, the data will not be written, and both conditional request methods will return a single key-value pair with the new ETag to use of the following form:

```json
{ "ETag": "8KnE63B6HiKp67Wf3HQrXanujSM=", "value": "<current value>" }
```

Here's an example of checking whether or not a conditional removal was successful:

```python
etag = db.child("users").child("Morty").get_etag()
response = db.child("users").child("Morty").conditional_remove(etag["ETag"])
if type(response) is dict and "ETag" in response:
etag = response["ETag"] # our ETag was out-of-date
else:
print("We removed the data successfully!")
```
Here's an example of looping to increase age by 1:

```python
etag = db.child("users").child("Morty").child("age").get_etag()
while type(etag) is dict and "ETag" in etag:
new_age = etag["value"] + 1
etag = db.child("users").child("Morty").child("age").conditional_set(new_age, etag["ETag"])
```

### Retrieve Data

Expand Down
67 changes: 38 additions & 29 deletions pyrebase/pyrebase.py
Expand Up @@ -387,41 +387,50 @@ def sort(self, origin, by_key, reverse=False):
return PyreResponse(convert_to_pyre(data), origin.key())

def get_etag(self, token=None, json_kwargs={}):
request_ref = self.build_request_url(token)
headers = self.build_headers(token)
# extra header to get ETag
headers['X-Firebase-ETag'] = 'true'
request_object = self.requests.get(request_ref, headers=headers)
raise_detailed_error(request_object)
return request_object.headers['ETag']
request_ref = self.build_request_url(token)
headers = self.build_headers(token)
# extra header to get ETag
headers['X-Firebase-ETag'] = 'true'
request_object = self.requests.get(request_ref, headers=headers)
raise_detailed_error(request_object)
return {
'ETag': request_object.headers['ETag'],
'value': request_object.json()
}

def conditional_set(self, data, etag, token=None, json_kwargs={}):
request_ref = self.check_token(self.database_url, self.path, token)
self.path = ""
headers = self.build_headers(token)
headers['if-match'] = etag
request_object = self.requests.put(request_ref, headers=headers, data=json.dumps(data, **json_kwargs).encode("utf-8"))
request_ref = self.check_token(self.database_url, self.path, token)
self.path = ""
headers = self.build_headers(token)
headers['if-match'] = etag
request_object = self.requests.put(request_ref, headers=headers, data=json.dumps(data, **json_kwargs).encode("utf-8"))

# ETag didn't match, so we should return the correct one for the user to try again
if request_object.status_code == 412:
return {'ETag': request_object.headers['ETag']}
# ETag didn't match, so we should return the correct one for the user to try again
if request_object.status_code == 412:
return {
'ETag': request_object.headers['ETag'],
'value': request_object.json()
}

raise_detailed_error(request_object)
return request_object.json()
raise_detailed_error(request_object)
return request_object.json()

def conditional_remove(self, etag, token=None):
request_ref = self.check_token(self.database_url, self.path, token)
self.path = ""
headers = self.build_headers(token)
headers['if-match'] = etag
request_object = self.requests.delete(request_ref, headers=headers)

# ETag didn't match, so we should return the correct one for the user to try again
if request_object.status_code == 412:
return {'ETag': request_object.headers['ETag']}

raise_detailed_error(request_object)
return request_object.json()
request_ref = self.check_token(self.database_url, self.path, token)
self.path = ""
headers = self.build_headers(token)
headers['if-match'] = etag
request_object = self.requests.delete(request_ref, headers=headers)

# ETag didn't match, so we should return the correct one for the user to try again
if request_object.status_code == 412:
return {
'ETag': request_object.headers['ETag'],
'value': request_object.json()
}

raise_detailed_error(request_object)
return request_object.json()


class Storage:
Expand Down