server/api: return snapshots to client
This commit is contained in:
parent
9a4b5a7dd3
commit
661c0248d2
11 changed files with 177 additions and 82 deletions
83
API.md
83
API.md
|
@ -142,10 +142,16 @@ data.
|
|||
|
||||
```json5
|
||||
{
|
||||
"tagCategory": <tag-category>
|
||||
"tagCategory": <tag-category>,
|
||||
"snapshots": [
|
||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>},
|
||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>},
|
||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>}
|
||||
]
|
||||
}
|
||||
```
|
||||
...where `<tag-category>` is a [tag category resource](#tag-category).
|
||||
...where `<tag-category>` is a [tag category resource](#tag-category), and
|
||||
`snapshots` contain its earlier versions.
|
||||
|
||||
- **Errors**
|
||||
|
||||
|
@ -178,10 +184,16 @@ data.
|
|||
|
||||
```json5
|
||||
{
|
||||
"tagCategory": <tag-category>
|
||||
"tagCategory": <tag-category>,
|
||||
"snapshots": [
|
||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>},
|
||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>},
|
||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>}
|
||||
]
|
||||
}
|
||||
```
|
||||
...where `<tag-category>` is a [tag category resource](#tag-category).
|
||||
...where `<tag-category>` is a [tag category resource](#tag-category), and
|
||||
`snapshots` contain its earlier versions.
|
||||
|
||||
- **Errors**
|
||||
|
||||
|
@ -207,10 +219,16 @@ data.
|
|||
|
||||
```json5
|
||||
{
|
||||
"tagCategory": <tag-category>
|
||||
"tagCategory": <tag-category>,
|
||||
"snapshots": [
|
||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>},
|
||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>},
|
||||
{"data": <tag-category-snapshot>, "time": <snapshot-time>}
|
||||
]
|
||||
}
|
||||
```
|
||||
...where `<tag-category>` is a [tag category resource](#tag-category).
|
||||
...where `<tag-category>` is a [tag category resource](#tag-category), and
|
||||
`snapshots` contain its earlier versions.
|
||||
|
||||
- **Errors**
|
||||
|
||||
|
@ -351,10 +369,16 @@ data.
|
|||
|
||||
```json5
|
||||
{
|
||||
"tag": <tag>
|
||||
"tag": <tag>,
|
||||
"snapshots": [
|
||||
{"data": <tag-snapshot>, "time": <snapshot-time>},
|
||||
{"data": <tag-snapshot>, "time": <snapshot-time>},
|
||||
{"data": <tag-snapshot>, "time": <snapshot-time>}
|
||||
]
|
||||
}
|
||||
```
|
||||
...where `<tag>` is a [tag resource](#tag).
|
||||
...where `<tag>` is a [tag resource](#tag), and `snapshots` contain its
|
||||
earlier versions.
|
||||
|
||||
- **Errors**
|
||||
|
||||
|
@ -399,10 +423,16 @@ data.
|
|||
|
||||
```json5
|
||||
{
|
||||
"tag": <tag>
|
||||
"tag": <tag>,
|
||||
"snapshots": [
|
||||
{"data": <tag-snapshot>, "time": <snapshot-time>},
|
||||
{"data": <tag-snapshot>, "time": <snapshot-time>},
|
||||
{"data": <tag-snapshot>, "time": <snapshot-time>}
|
||||
]
|
||||
}
|
||||
```
|
||||
...where `<tag>` is a [tag resource](#tag).
|
||||
...where `<tag>` is a [tag resource](#tag), and `snapshots` contain its
|
||||
earlier versions.
|
||||
|
||||
- **Errors**
|
||||
|
||||
|
@ -435,10 +465,16 @@ data.
|
|||
|
||||
```json5
|
||||
{
|
||||
"tag": <tag>
|
||||
"tag": <tag>,
|
||||
"snapshots": [
|
||||
{"data": <tag-snapshot>, "time": <snapshot-time>},
|
||||
{"data": <tag-snapshot>, "time": <snapshot-time>},
|
||||
{"data": <tag-snapshot>, "time": <snapshot-time>}
|
||||
]
|
||||
}
|
||||
```
|
||||
...where `<tag>` is a [tag resource](#tag).
|
||||
...where `<tag>` is a [tag resource](#tag), and `snapshots` contain its
|
||||
earlier versions.
|
||||
|
||||
- **Errors**
|
||||
|
||||
|
@ -768,7 +804,16 @@ data.
|
|||
```json5
|
||||
{
|
||||
"name": "character",
|
||||
"color": "#FF0000", // used to colorize certain tag types in the web client
|
||||
"color": "#FF0000" // used to colorize certain tag types in the web client
|
||||
}
|
||||
```
|
||||
|
||||
## Tag category snapshot
|
||||
|
||||
```json5
|
||||
{
|
||||
"name": "character",
|
||||
"color": "#FF0000"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -777,7 +822,7 @@ data.
|
|||
```json5
|
||||
{
|
||||
"names": ["tag1", "tag2", "tag3"],
|
||||
"category": "plain", // one of values controlled by server's configuration
|
||||
"category": "plain",
|
||||
"implications": ["implied-tag1", "implied-tag2", "implied-tag3"],
|
||||
"suggestions": ["suggested-tag1", "suggested-tag2", "suggested-tag3"],
|
||||
"creationTime": "2016-03-28T13:37:01.755461",
|
||||
|
@ -785,6 +830,16 @@ data.
|
|||
}
|
||||
```
|
||||
|
||||
## Tag snapshot
|
||||
|
||||
```json5
|
||||
{
|
||||
"names": ["tag1", "tag2", "tag3"],
|
||||
"category": "plain",
|
||||
"implications": ["imp1", "imp2", "imp3"],
|
||||
"suggestions": ["sug1", "sug2", "sug3"]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# Search
|
||||
|
|
|
@ -15,6 +15,12 @@ def _serialize_tag(tag):
|
|||
'lastEditTime': tag.last_edit_time,
|
||||
}
|
||||
|
||||
def _serialize_tag_with_details(tag):
|
||||
return {
|
||||
'tag': _serialize_tag(tag),
|
||||
'snapshots': snapshots.get_data(tag),
|
||||
}
|
||||
|
||||
class TagListApi(BaseApi):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
@ -51,7 +57,7 @@ class TagListApi(BaseApi):
|
|||
snapshots.create(tag, ctx.user)
|
||||
ctx.session.commit()
|
||||
tags.export_to_json()
|
||||
return {'tag': _serialize_tag(tag)}
|
||||
return _serialize_tag_with_details(tag)
|
||||
|
||||
class TagDetailApi(BaseApi):
|
||||
def get(self, ctx, tag_name):
|
||||
|
@ -59,7 +65,7 @@ class TagDetailApi(BaseApi):
|
|||
tag = tags.get_tag_by_name(tag_name)
|
||||
if not tag:
|
||||
raise tags.TagNotFoundError('Tag %r not found.' % tag_name)
|
||||
return {'tag': _serialize_tag(tag)}
|
||||
return _serialize_tag_with_details(tag)
|
||||
|
||||
def put(self, ctx, tag_name):
|
||||
tag = tags.get_tag_by_name(tag_name)
|
||||
|
@ -86,7 +92,7 @@ class TagDetailApi(BaseApi):
|
|||
snapshots.modify(tag, ctx.user)
|
||||
ctx.session.commit()
|
||||
tags.export_to_json()
|
||||
return {'tag': _serialize_tag(tag)}
|
||||
return _serialize_tag_with_details(tag)
|
||||
|
||||
def delete(self, ctx, tag_name):
|
||||
tag = tags.get_tag_by_name(tag_name)
|
||||
|
|
|
@ -7,6 +7,12 @@ def _serialize_category(category):
|
|||
'color': category.color,
|
||||
}
|
||||
|
||||
def _serialize_category_with_details(category):
|
||||
return {
|
||||
'tagCategory': _serialize_category(category),
|
||||
'snapshots': snapshots.get_data(category),
|
||||
}
|
||||
|
||||
class TagCategoryListApi(BaseApi):
|
||||
def get(self, ctx):
|
||||
auth.verify_privilege(ctx.user, 'tag_categories:list')
|
||||
|
@ -26,7 +32,7 @@ class TagCategoryListApi(BaseApi):
|
|||
snapshots.create(category, ctx.user)
|
||||
ctx.session.commit()
|
||||
tags.export_to_json()
|
||||
return {'tagCategory': _serialize_category(category)}
|
||||
return _serialize_category_with_details(category)
|
||||
|
||||
class TagCategoryDetailApi(BaseApi):
|
||||
def get(self, ctx, category_name):
|
||||
|
@ -35,7 +41,7 @@ class TagCategoryDetailApi(BaseApi):
|
|||
if not category:
|
||||
raise tag_categories.TagCategoryNotFoundError(
|
||||
'Tag category %r not found.' % category_name)
|
||||
return {'tagCategory': _serialize_category(category)}
|
||||
return _serialize_category_with_details(category)
|
||||
|
||||
def put(self, ctx, category_name):
|
||||
category = tag_categories.get_category_by_name(category_name)
|
||||
|
@ -53,7 +59,7 @@ class TagCategoryDetailApi(BaseApi):
|
|||
snapshots.modify(category, ctx.user)
|
||||
ctx.session.commit()
|
||||
tags.export_to_json()
|
||||
return {'tagCategory': _serialize_category(category)}
|
||||
return _serialize_category_with_details(category)
|
||||
|
||||
def delete(self, ctx, category_name):
|
||||
category = tag_categories.get_category_by_name(category_name)
|
||||
|
|
|
@ -22,9 +22,8 @@ def test_creating_category(test_ctx):
|
|||
test_ctx.context_factory(
|
||||
input={'name': 'meta', 'color': 'black'},
|
||||
user=test_ctx.user_factory(rank='regular_user')))
|
||||
assert result == {
|
||||
'tagCategory': {'name': 'meta', 'color': 'black'},
|
||||
}
|
||||
assert result['tagCategory'] == {'name': 'meta', 'color': 'black'}
|
||||
assert len(result['snapshots']) == 1
|
||||
category = db.session.query(db.TagCategory).one()
|
||||
assert category.name == 'meta'
|
||||
assert category.color == 'black'
|
||||
|
|
|
@ -43,7 +43,8 @@ def test_retrieving_single(test_ctx):
|
|||
'tagCategory': {
|
||||
'name': 'cat',
|
||||
'color': 'dummy',
|
||||
}
|
||||
},
|
||||
'snapshots': [],
|
||||
}
|
||||
|
||||
def test_trying_to_retrieve_single_non_existing(test_ctx):
|
||||
|
|
|
@ -38,12 +38,11 @@ def test_simple_updating(test_ctx):
|
|||
},
|
||||
user=test_ctx.user_factory(rank='regular_user')),
|
||||
'name')
|
||||
assert result == {
|
||||
'tagCategory': {
|
||||
'name': 'changed',
|
||||
'color': 'white',
|
||||
}
|
||||
assert result['tagCategory'] == {
|
||||
'name': 'changed',
|
||||
'color': 'white',
|
||||
}
|
||||
assert len(result['snapshots']) == 1
|
||||
assert tag_categories.get_category_by_name('name') is None
|
||||
category = tag_categories.get_category_by_name('changed')
|
||||
assert category is not None
|
||||
|
|
|
@ -45,16 +45,15 @@ def test_creating_simple_tags(test_ctx, fake_datetime):
|
|||
'implications': [],
|
||||
},
|
||||
user=test_ctx.user_factory(rank='regular_user')))
|
||||
assert result == {
|
||||
'tag': {
|
||||
'names': ['tag1', 'tag2'],
|
||||
'category': 'meta',
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
'creationTime': datetime.datetime(1997, 12, 1),
|
||||
'lastEditTime': None,
|
||||
}
|
||||
assert result['tag'] == {
|
||||
'names': ['tag1', 'tag2'],
|
||||
'category': 'meta',
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
'creationTime': datetime.datetime(1997, 12, 1),
|
||||
'lastEditTime': None,
|
||||
}
|
||||
assert len(result['snapshots']) == 1
|
||||
tag = get_tag('tag1')
|
||||
assert [tag_name.name for tag_name in tag.names] == ['tag1', 'tag2']
|
||||
assert tag.category.name == 'meta'
|
||||
|
|
|
@ -58,7 +58,8 @@ def test_retrieving_single(test_ctx):
|
|||
'lastEditTime': None,
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
}
|
||||
},
|
||||
'snapshots': [],
|
||||
}
|
||||
|
||||
def test_trying_to_retrieve_single_non_existing(test_ctx):
|
||||
|
|
|
@ -52,16 +52,15 @@ def test_simple_updating(test_ctx, fake_datetime):
|
|||
},
|
||||
user=test_ctx.user_factory(rank='regular_user')),
|
||||
'tag1')
|
||||
assert result == {
|
||||
'tag': {
|
||||
'names': ['tag3'],
|
||||
'category': 'character',
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
'creationTime': datetime.datetime(1996, 1, 1),
|
||||
'lastEditTime': datetime.datetime(1997, 12, 1),
|
||||
}
|
||||
assert result['tag'] == {
|
||||
'names': ['tag3'],
|
||||
'category': 'character',
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
'creationTime': datetime.datetime(1996, 1, 1),
|
||||
'lastEditTime': datetime.datetime(1997, 12, 1),
|
||||
}
|
||||
assert len(result['snapshots']) == 1
|
||||
assert get_tag('tag1') is None
|
||||
assert get_tag('tag2') is None
|
||||
tag = get_tag('tag3')
|
||||
|
|
|
@ -7,7 +7,9 @@ def test_serializing_tag(tag_factory):
|
|||
tag = tag_factory(names=['main_name', 'alias'], category_name='dummy')
|
||||
assert snapshots.get_tag_snapshot(tag) == {
|
||||
'names': ['main_name', 'alias'],
|
||||
'category': 'dummy'
|
||||
'category': 'dummy',
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
}
|
||||
|
||||
tag = tag_factory(names=['main_name', 'alias'], category_name='dummy')
|
||||
|
@ -39,10 +41,8 @@ def test_merging_modification_to_creation(tag_factory, user_factory):
|
|||
db.session.add_all([tag, user])
|
||||
db.session.flush()
|
||||
snapshots.create(tag, user)
|
||||
db.session.flush()
|
||||
tag.names = [db.TagName('changed')]
|
||||
snapshots.modify(tag, user)
|
||||
db.session.flush()
|
||||
results = db.session.query(db.Snapshot).all()
|
||||
assert len(results) == 1
|
||||
assert results[0].operation == db.Snapshot.OPERATION_CREATED
|
||||
|
@ -55,15 +55,12 @@ def test_merging_modifications(fake_datetime, tag_factory, user_factory):
|
|||
db.session.flush()
|
||||
with fake_datetime('13:00:00'):
|
||||
snapshots.create(tag, user)
|
||||
db.session.flush()
|
||||
tag.names = [db.TagName('changed')]
|
||||
with fake_datetime('14:00:00'):
|
||||
snapshots.modify(tag, user)
|
||||
db.session.flush()
|
||||
tag.names = [db.TagName('changed again')]
|
||||
with fake_datetime('14:00:01'):
|
||||
snapshots.modify(tag, user)
|
||||
db.session.flush()
|
||||
results = db.session.query(db.Snapshot).all()
|
||||
assert len(results) == 2
|
||||
assert results[0].operation == db.Snapshot.OPERATION_CREATED
|
||||
|
@ -79,10 +76,8 @@ def test_not_adding_snapshot_if_data_doesnt_change(
|
|||
db.session.flush()
|
||||
with fake_datetime('13:00:00'):
|
||||
snapshots.create(tag, user)
|
||||
db.session.flush()
|
||||
with fake_datetime('14:00:00'):
|
||||
snapshots.modify(tag, user)
|
||||
db.session.flush()
|
||||
results = db.session.query(db.Snapshot).all()
|
||||
assert len(results) == 1
|
||||
assert results[0].operation == db.Snapshot.OPERATION_CREATED
|
||||
|
@ -96,11 +91,9 @@ def test_not_merging_due_to_time_difference(
|
|||
db.session.flush()
|
||||
with fake_datetime('13:00:00'):
|
||||
snapshots.create(tag, user)
|
||||
db.session.flush()
|
||||
tag.names = [db.TagName('changed')]
|
||||
with fake_datetime('13:10:01'):
|
||||
snapshots.modify(tag, user)
|
||||
db.session.flush()
|
||||
assert db.session.query(db.Snapshot).count() == 2
|
||||
|
||||
def test_not_merging_operations_by_different_users(
|
||||
|
@ -111,10 +104,8 @@ def test_not_merging_operations_by_different_users(
|
|||
db.session.flush()
|
||||
with fake_datetime('13:00:00'):
|
||||
snapshots.create(tag, user1)
|
||||
db.session.flush()
|
||||
tag.names = [db.TagName('changed')]
|
||||
snapshots.modify(tag, user2)
|
||||
db.session.flush()
|
||||
assert db.session.query(db.Snapshot).count() == 2
|
||||
|
||||
def test_merging_resets_merging_time_window(
|
||||
|
@ -125,15 +116,12 @@ def test_merging_resets_merging_time_window(
|
|||
db.session.flush()
|
||||
with fake_datetime('13:00:00'):
|
||||
snapshots.create(tag, user)
|
||||
db.session.flush()
|
||||
tag.names = [db.TagName('changed')]
|
||||
with fake_datetime('13:09:59'):
|
||||
snapshots.modify(tag, user)
|
||||
db.session.flush()
|
||||
tag.names = [db.TagName('changed again')]
|
||||
with fake_datetime('13:19:59'):
|
||||
snapshots.modify(tag, user)
|
||||
db.session.flush()
|
||||
results = db.session.query(db.Snapshot).all()
|
||||
assert len(results) == 1
|
||||
assert results[0].data['names'] == ['changed again']
|
||||
|
@ -148,22 +136,24 @@ def test_merging_deletion_to_modification_or_creation(
|
|||
db.session.flush()
|
||||
with fake_datetime('13:00:00'):
|
||||
initial_operation(tag, user)
|
||||
db.session.flush()
|
||||
tag.names = [db.TagName('changed')]
|
||||
with fake_datetime('14:00:00'):
|
||||
snapshots.modify(tag, user)
|
||||
db.session.flush()
|
||||
tag.names = [db.TagName('changed again')]
|
||||
with fake_datetime('14:00:01'):
|
||||
snapshots.delete(tag, user)
|
||||
db.session.flush()
|
||||
assert db.session.query(db.Snapshot).count() == 2
|
||||
results = db.session \
|
||||
.query(db.Snapshot) \
|
||||
.order_by(db.Snapshot.snapshot_id.asc()) \
|
||||
.all()
|
||||
assert results[1].operation == db.Snapshot.OPERATION_DELETED
|
||||
assert results[1].data == {'names': ['changed again'], 'category': 'dummy'}
|
||||
assert results[1].data == {
|
||||
'names': ['changed again'],
|
||||
'category': 'dummy',
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
}
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'expected_operation', [snapshots.create, snapshots.modify])
|
||||
|
@ -175,13 +165,41 @@ def test_merging_deletion_all_the_way_deletes_all_snapshots(
|
|||
db.session.flush()
|
||||
with fake_datetime('13:00:00'):
|
||||
snapshots.create(tag, user)
|
||||
db.session.flush()
|
||||
tag.names = [db.TagName('changed')]
|
||||
with fake_datetime('13:00:01'):
|
||||
snapshots.modify(tag, user)
|
||||
db.session.flush()
|
||||
tag.names = [db.TagName('changed again')]
|
||||
with fake_datetime('13:00:02'):
|
||||
snapshots.delete(tag, user)
|
||||
db.session.flush()
|
||||
assert db.session.query(db.Snapshot).count() == 0
|
||||
|
||||
def test_get_data(fake_datetime, tag_factory, user_factory):
|
||||
tag = tag_factory(names=['dummy'])
|
||||
user = user_factory()
|
||||
db.session.add_all([tag, user])
|
||||
db.session.flush()
|
||||
with fake_datetime('2016-04-19 13:00:00'):
|
||||
snapshots.create(tag, user)
|
||||
tag.names = [db.TagName('changed')]
|
||||
with fake_datetime('2016-04-19 13:10:01'):
|
||||
snapshots.modify(tag, user)
|
||||
assert snapshots.get_data(tag) == [
|
||||
{
|
||||
'time': datetime.datetime(2016, 4, 19, 13, 10, 1),
|
||||
'data': {
|
||||
'names': ['changed'],
|
||||
'category': 'dummy',
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
},
|
||||
},
|
||||
{
|
||||
'time': datetime.datetime(2016, 4, 19, 13, 0, 0),
|
||||
'data': {
|
||||
'names': ['dummy'],
|
||||
'category': 'dummy',
|
||||
'suggestions': [],
|
||||
'implications': [],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -5,12 +5,10 @@ from szurubooru import db
|
|||
def get_tag_snapshot(tag):
|
||||
ret = {
|
||||
'names': [tag_name.name for tag_name in tag.names],
|
||||
'category': tag.category.name
|
||||
'category': tag.category.name,
|
||||
'suggestions': sorted(rel.first_name for rel in tag.suggestions),
|
||||
'implications': sorted(rel.first_name for rel in tag.implications),
|
||||
}
|
||||
if tag.suggestions:
|
||||
ret['suggestions'] = sorted(rel.first_name for rel in tag.suggestions)
|
||||
if tag.implications:
|
||||
ret['implications'] = sorted(rel.first_name for rel in tag.implications)
|
||||
return ret
|
||||
|
||||
def get_tag_category_snapshot(category):
|
||||
|
@ -25,13 +23,32 @@ serializers = {
|
|||
'tag_category': get_tag_category_snapshot,
|
||||
}
|
||||
|
||||
def save(operation, entity, auth_user):
|
||||
def get_resource_info(entity):
|
||||
table_name = entity.__table__.name
|
||||
primary_key = inspect(entity).identity
|
||||
assert table_name in serializers
|
||||
assert primary_key is not None
|
||||
assert len(primary_key) == 1
|
||||
primary_key = primary_key[0]
|
||||
return (table_name, primary_key)
|
||||
|
||||
def get_snapshots(entity):
|
||||
table_name, primary_key = get_resource_info(entity)
|
||||
return db.session \
|
||||
.query(db.Snapshot) \
|
||||
.filter(db.Snapshot.resource_type == table_name) \
|
||||
.filter(db.Snapshot.resource_id == primary_key) \
|
||||
.order_by(db.Snapshot.creation_time.desc()) \
|
||||
.all()
|
||||
|
||||
def get_data(entity):
|
||||
ret = []
|
||||
for snapshot in get_snapshots(entity):
|
||||
ret.append({'data': snapshot.data, 'time': snapshot.creation_time})
|
||||
return ret
|
||||
|
||||
def save(operation, entity, auth_user):
|
||||
table_name, primary_key = get_resource_info(entity)
|
||||
now = datetime.datetime.now()
|
||||
|
||||
snapshot = db.Snapshot()
|
||||
|
@ -42,12 +59,7 @@ def save(operation, entity, auth_user):
|
|||
snapshot.data = serializers[table_name](entity)
|
||||
snapshot.user = auth_user
|
||||
|
||||
earlier_snapshots = db.session \
|
||||
.query(db.Snapshot) \
|
||||
.filter(db.Snapshot.resource_type == table_name) \
|
||||
.filter(db.Snapshot.resource_id == primary_key) \
|
||||
.order_by(db.Snapshot.creation_time.desc()) \
|
||||
.all()
|
||||
earlier_snapshots = get_snapshots(entity)
|
||||
|
||||
delta = datetime.timedelta(minutes=10)
|
||||
snapshots_left = len(earlier_snapshots)
|
||||
|
|
Loading…
Reference in a new issue