Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
C
caosdb-pylib
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container registry
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
caosdb
Software
caosdb-pylib
Commits
5f9ceca3
Verified
Commit
5f9ceca3
authored
4 years ago
by
Timm Fitschen
Browse files
Options
Downloads
Plain Diff
Merge branch 'f-fsm' into f-fsm-v0.2
parents
6307b58a
6a6686e5
No related branches found
No related tags found
1 merge request
!3
F fsm
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
CHANGELOG.md
+3
-1
3 additions, 1 deletion
CHANGELOG.md
src/caosdb/common/versioning.py
+76
-4
76 additions, 4 deletions
src/caosdb/common/versioning.py
unittests/test_versioning.py
+41
-3
41 additions, 3 deletions
unittests/test_versioning.py
with
120 additions
and
8 deletions
CHANGELOG.md
+
3
−
1
View file @
5f9ceca3
...
@@ -11,7 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
...
@@ -11,7 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
*
Entity State support (experimental, no StateModel support yet). See the
`caosdb.State`
class for
*
Entity State support (experimental, no StateModel support yet). See the
`caosdb.State`
class for
more information.
more information.
*
Versioning support (experimental).
*
Versioning support (experimental). The version db.Version class can
represents particular entity versions and also the complete history of an
entity.
### Changed ###
### Changed ###
...
...
This diff is collapsed.
Click to expand it.
src/caosdb/common/versioning.py
+
76
−
4
View file @
5f9ceca3
...
@@ -42,11 +42,19 @@ class Version():
...
@@ -42,11 +42,19 @@ class Version():
id : str, optional
id : str, optional
See attribute `id`. Default: None
See attribute `id`. Default: None
date : str, optional
date : str, optional
See attribute `data`. Default: None
See attribute `date`. Default: None
username : str, optional
See attribute `username`. Default: None
realm : str, optional
See attribute `realm`. Default: None
predecessors : list of Version, optional
predecessors : list of Version, optional
See attribute `predecessors`. Default: empty list.
See attribute `predecessors`. Default: empty list.
successors : list of Version, optional
successors : list of Version, optional
See attribute `successors`. Default: empty list.
See attribute `successors`. Default: empty list.
is_head : bool
See attribute `is_head`. Default: False
is_complete_history : bool
See attribute `is_complete_history`. Default: False
Attributes
Attributes
----------
----------
...
@@ -55,6 +63,10 @@ class Version():
...
@@ -55,6 +63,10 @@ class Version():
date : str
date : str
UTC Timestamp of the version, i.e. the date and time when the entity of
UTC Timestamp of the version, i.e. the date and time when the entity of
this version has been inserted or modified.
this version has been inserted or modified.
username : str
The username of the user who inserted or updated this version.
realm : str
The realm of the user who inserted or updated this version.
predecessors : list of Version
predecessors : list of Version
Predecessors are the older entity versions which have been modified
Predecessors are the older entity versions which have been modified
into this version. Usually, there is only one predecessor. However,
into this version. Usually, there is only one predecessor. However,
...
@@ -66,14 +78,64 @@ class Version():
...
@@ -66,14 +78,64 @@ class Version():
is only one successor. However, this API allows that a single entity
is only one successor. However, this API allows that a single entity
may co-exist in several versions (e.g. several proposals for the next
may co-exist in several versions (e.g. several proposals for the next
entity status). That would result in more than one successor.
entity status). That would result in more than one successor.
is_head : bool or string
If true, this indicates that this version is the HEAD if true.
Otherwise it is not known whether this is the head or not. Any string
matching
"
true
"
(case-insensitively) is regarded as True.
Nota bene: This property should typically be set if the server response
indicated that this is the head version.
is_complete_history : bool or string
If true, this indicates that this version contains the full version
history. That means, that the predecessors and successors have their
respective predecessors and successors attached as well and the tree is
completely available. Any string matching
"
true
"
(case-insensitively)
is regarded as True.
Nota bene: This property should typically be set if the server response
indicated that the full version history is included in its response.
"""
"""
# pylint: disable=redefined-builtin
# pylint: disable=redefined-builtin
def
__init__
(
self
,
id
=
None
,
date
=
None
,
predecessors
=
None
,
successors
=
None
):
def
__init__
(
self
,
id
=
None
,
date
=
None
,
username
=
None
,
realm
=
None
,
predecessors
=
None
,
successors
=
None
,
is_head
=
False
,
is_complete_history
=
False
):
"""
Typically the `predecessors` or `successors` should not
"
link back
"
to an existing Version
object.
"""
self
.
id
=
id
self
.
id
=
id
self
.
date
=
date
self
.
date
=
date
self
.
username
=
username
self
.
realm
=
realm
self
.
predecessors
=
predecessors
if
predecessors
is
not
None
else
[]
self
.
predecessors
=
predecessors
if
predecessors
is
not
None
else
[]
self
.
successors
=
successors
if
successors
is
not
None
else
[]
self
.
successors
=
successors
if
successors
is
not
None
else
[]
self
.
is_head
=
str
(
is_head
).
lower
()
==
"
true
"
self
.
is_complete_history
=
str
(
is_complete_history
).
lower
()
==
"
true
"
def
get_history
(
self
):
"""
Returns a flat list of Version instances representing the history
of the entity.
The list items are ordered by the relation between the versions,
starting with the oldest version.
The items in the list have no predecessors or successors attached.
Note: This method only returns reliable results if
`self.is_complete_history is True` and it will not retrieve the full
version history if it is not present.
Returns
-------
list of Version
"""
versions
=
[]
for
p
in
self
.
predecessors
:
# assuming that predecessors don't have any successors
versions
=
p
.
get_history
()
versions
.
append
(
Version
(
id
=
self
.
id
,
date
=
self
.
date
,
username
=
self
.
username
,
realm
=
self
.
realm
))
for
s
in
self
.
successors
:
# assuming that successors don't have any predecessors
versions
.
extend
(
s
.
get_history
())
return
versions
def
to_xml
(
self
,
tag
=
"
Version
"
):
def
to_xml
(
self
,
tag
=
"
Version
"
):
"""
Serialize this version to xml.
"""
Serialize this version to xml.
...
@@ -99,9 +161,15 @@ class Version():
...
@@ -99,9 +161,15 @@ class Version():
xml
.
set
(
"
id
"
,
self
.
id
)
xml
.
set
(
"
id
"
,
self
.
id
)
if
self
.
date
is
not
None
:
if
self
.
date
is
not
None
:
xml
.
set
(
"
date
"
,
self
.
date
)
xml
.
set
(
"
date
"
,
self
.
date
)
if
self
.
username
is
not
None
:
xml
.
set
(
"
username
"
,
self
.
username
)
if
self
.
realm
is
not
None
:
xml
.
set
(
"
realm
"
,
self
.
realm
)
if
self
.
predecessors
is
not
None
:
if
self
.
predecessors
is
not
None
:
for
p
in
self
.
predecessors
:
for
p
in
self
.
predecessors
:
xml
.
append
(
p
.
to_xml
(
tag
=
"
Predecessor
"
))
xml
.
append
(
p
.
to_xml
(
tag
=
"
Predecessor
"
))
if
self
.
is_head
is
True
:
xml
.
set
(
"
head
"
,
"
true
"
)
if
self
.
successors
is
not
None
:
if
self
.
successors
is
not
None
:
for
s
in
self
.
successors
:
for
s
in
self
.
successors
:
xml
.
append
(
s
.
to_xml
(
tag
=
"
Successor
"
))
xml
.
append
(
s
.
to_xml
(
tag
=
"
Successor
"
))
...
@@ -122,8 +190,9 @@ class Version():
...
@@ -122,8 +190,9 @@ class Version():
Parameters
Parameters
----------
----------
xml : etree.Element
xml : etree.Element
A
'
Version
'
xml element, with
'
id
'
and
'
date
'
attributes and
A
'
Version
'
xml element, with
'
id
'
, possibly
'
date
'
, `username`,
'
Predecessor
'
and
'
Successor
'
child elements.
`realm`, and `head` attributes as well as
'
Predecessor
'
and
'
Successor
'
child elements.
Returns
Returns
-------
-------
...
@@ -133,6 +202,9 @@ class Version():
...
@@ -133,6 +202,9 @@ class Version():
predecessors
=
[
Version
.
from_xml
(
p
)
for
p
in
xml
if
p
.
tag
.
lower
()
==
"
predecessor
"
]
predecessors
=
[
Version
.
from_xml
(
p
)
for
p
in
xml
if
p
.
tag
.
lower
()
==
"
predecessor
"
]
successors
=
[
Version
.
from_xml
(
s
)
for
s
in
xml
if
s
.
tag
.
lower
()
==
"
successor
"
]
successors
=
[
Version
.
from_xml
(
s
)
for
s
in
xml
if
s
.
tag
.
lower
()
==
"
successor
"
]
return
Version
(
id
=
xml
.
get
(
"
id
"
),
date
=
xml
.
get
(
"
date
"
),
return
Version
(
id
=
xml
.
get
(
"
id
"
),
date
=
xml
.
get
(
"
date
"
),
is_head
=
xml
.
get
(
"
head
"
),
is_complete_history
=
xml
.
get
(
"
completeHistory
"
),
username
=
xml
.
get
(
"
username
"
),
realm
=
xml
.
get
(
"
realm
"
),
predecessors
=
predecessors
,
successors
=
successors
)
predecessors
=
predecessors
,
successors
=
successors
)
def
__hash__
(
self
):
def
__hash__
(
self
):
...
...
This diff is collapsed.
Click to expand it.
unittests/test_versioning.py
+
41
−
3
View file @
5f9ceca3
...
@@ -27,16 +27,21 @@ from caosdb import Record
...
@@ -27,16 +27,21 @@ from caosdb import Record
from
caosdb.common.utils
import
xml2str
from
caosdb.common.utils
import
xml2str
from
caosdb.common.versioning
import
Version
from
caosdb.common.versioning
import
Version
from
.test_property
import
testrecord
from
.test_property
import
testrecord
from
lxml
import
etree
def
test_constructor
():
def
test_constructor
():
v
=
Version
(
id
=
"
1234abcd
"
,
date
=
"
2020-01-01T20:15:00.000UTC
"
,
v
=
Version
(
id
=
"
1234abcd
"
,
date
=
"
2020-01-01T20:15:00.000UTC
"
,
username
=
"
testuser
"
,
realm
=
"
CaosDB
"
,
is_head
=
True
,
predecessors
=
[
Version
(
id
=
"
2345abdc
"
,
predecessors
=
[
Version
(
id
=
"
2345abdc
"
,
date
=
"
2020-01-01T20:00:00.000UTC
"
)],
date
=
"
2020-01-01T20:00:00.000UTC
"
)],
successors
=
[
Version
(
id
=
"
3465abdc
"
,
successors
=
[
Version
(
id
=
"
3465abdc
"
,
date
=
"
2020-01-01T20:30:00.000UTC
"
)])
date
=
"
2020-01-01T20:30:00.000UTC
"
)])
assert
v
.
id
==
"
1234abcd
"
assert
v
.
id
==
"
1234abcd
"
assert
v
.
date
==
"
2020-01-01T20:15:00.000UTC
"
assert
v
.
date
==
"
2020-01-01T20:15:00.000UTC
"
assert
v
.
username
==
"
testuser
"
assert
v
.
realm
==
"
CaosDB
"
assert
v
.
is_head
is
True
assert
isinstance
(
v
.
predecessors
,
list
)
assert
isinstance
(
v
.
predecessors
,
list
)
assert
isinstance
(
v
.
predecessors
[
0
],
Version
)
assert
isinstance
(
v
.
predecessors
[
0
],
Version
)
assert
isinstance
(
v
.
successors
,
list
)
assert
isinstance
(
v
.
successors
,
list
)
...
@@ -48,21 +53,27 @@ def test_constructor():
...
@@ -48,21 +53,27 @@ def test_constructor():
def
test_to_xml
():
def
test_to_xml
():
v
=
test_constructor
()
v
=
test_constructor
()
xmlstr
=
xml2str
(
v
.
to_xml
())
xmlstr
=
xml2str
(
v
.
to_xml
())
assert
xmlstr
==
(
'
<Version id=
"
{i}
"
date=
"
{d}
"
>
\n
'
assert
xmlstr
==
(
'
<Version id=
"
{i}
"
date=
"
{d}
"
username=
"
{u}
"
realm=
"
{r}
"'
'
head=
"
{h}
"
>
\n
'
'
<Predecessor id=
"
{pi}
"
date=
"
{pd}
"
/>
\n
'
'
<Predecessor id=
"
{pi}
"
date=
"
{pd}
"
/>
\n
'
'
<Successor id=
"
{si}
"
date=
"
{sd}
"
/>
\n
'
'
<Successor id=
"
{si}
"
date=
"
{sd}
"
/>
\n
'
'
</Version>
\n
'
).
format
(
i
=
v
.
id
,
d
=
v
.
date
,
'
</Version>
\n
'
).
format
(
i
=
v
.
id
,
d
=
v
.
date
,
u
=
v
.
username
,
r
=
v
.
realm
,
h
=
str
(
v
.
is_head
).
lower
(),
pi
=
v
.
predecessors
[
0
].
id
,
pi
=
v
.
predecessors
[
0
].
id
,
pd
=
v
.
predecessors
[
0
].
date
,
pd
=
v
.
predecessors
[
0
].
date
,
si
=
v
.
successors
[
0
].
id
,
si
=
v
.
successors
[
0
].
id
,
sd
=
v
.
successors
[
0
].
date
)
sd
=
v
.
successors
[
0
].
date
)
xmlstr2
=
xml2str
(
v
.
to_xml
(
tag
=
"
OtherVersionTag
"
))
xmlstr2
=
xml2str
(
v
.
to_xml
(
tag
=
"
OtherVersionTag
"
))
assert
xmlstr2
==
(
'
<OtherVersionTag id=
"
{i}
"
date=
"
{d}
"
>
\n
'
assert
xmlstr2
==
(
'
<OtherVersionTag id=
"
{i}
"
date=
"
{d}
"
username=
"
{u}
"
'
'
realm=
"
{r}
"
head=
"
{h}
"
>
\n
'
'
<Predecessor id=
"
{pi}
"
date=
"
{pd}
"
/>
\n
'
'
<Predecessor id=
"
{pi}
"
date=
"
{pd}
"
/>
\n
'
'
<Successor id=
"
{si}
"
date=
"
{sd}
"
/>
\n
'
'
<Successor id=
"
{si}
"
date=
"
{sd}
"
/>
\n
'
'
</OtherVersionTag>
\n
'
'
</OtherVersionTag>
\n
'
).
format
(
i
=
v
.
id
,
d
=
v
.
date
,
pi
=
v
.
predecessors
[
0
].
id
,
).
format
(
i
=
v
.
id
,
d
=
v
.
date
,
u
=
v
.
username
,
r
=
v
.
realm
,
h
=
str
(
v
.
is_head
).
lower
(),
pi
=
v
.
predecessors
[
0
].
id
,
pd
=
v
.
predecessors
[
0
].
date
,
pd
=
v
.
predecessors
[
0
].
date
,
si
=
v
.
successors
[
0
].
id
,
sd
=
v
.
successors
[
0
].
date
)
si
=
v
.
successors
[
0
].
id
,
sd
=
v
.
successors
[
0
].
date
)
...
@@ -142,3 +153,30 @@ def test_version_serialization():
...
@@ -142,3 +153,30 @@ def test_version_serialization():
# <Record><Version id="test-version" date="asdfsadf"/></Record>
# <Record><Version id="test-version" date="asdfsadf"/></Record>
assert
"
test-version
"
==
r
.
to_xml
().
xpath
(
"
/Record/Version/@id
"
)[
0
]
assert
"
test-version
"
==
r
.
to_xml
().
xpath
(
"
/Record/Version/@id
"
)[
0
]
assert
"
asdfsadf
"
==
r
.
to_xml
().
xpath
(
"
/Record/Version/@date
"
)[
0
]
assert
"
asdfsadf
"
==
r
.
to_xml
().
xpath
(
"
/Record/Version/@date
"
)[
0
]
def
test_get_history
():
xml_str
=
"""
<Version id=
"
vid6
"
username=
"
user1
"
realm=
"
Realm1
"
date=
"
date6
"
completeHistory=
"
true
"
>
<Predecessor id=
"
vid5
"
username=
"
user1
"
realm=
"
Realm1
"
date=
"
date5
"
>
<Predecessor id=
"
vid4
"
username=
"
user1
"
realm=
"
Realm1
"
date=
"
date4
"
>
<Predecessor id=
"
vid3
"
username=
"
user1
"
realm=
"
Realm1
"
date=
"
date3
"
>
<Predecessor id=
"
vid2
"
username=
"
user1
"
realm=
"
Realm1
"
date=
"
date2
"
>
<Predecessor id=
"
vid1
"
username=
"
user1
"
realm=
"
Realm1
"
date=
"
date1
"
/>
</Predecessor>
</Predecessor>
</Predecessor>
</Predecessor>
<Successor id=
"
vid7
"
username=
"
user1
"
realm=
"
Realm1
"
date=
"
date7
"
>
<Successor id=
"
vid8
"
username=
"
user1
"
realm=
"
Realm1
"
date=
"
date8
"
>
<Successor id=
"
vid9
"
username=
"
user1
"
realm=
"
Realm1
"
date=
"
date9
"
>
<Successor id=
"
vid10
"
username=
"
user1
"
realm=
"
Realm1
"
date=
"
date10
"
/>
</Successor>
</Successor>
</Successor>
</Version>
"""
version
=
Version
.
from_xml
(
etree
.
fromstring
(
xml_str
))
assert
version
.
is_complete_history
is
True
assert
version
.
get_history
()
==
[
Version
(
id
=
f
"
vid
{
i
+
1
}
"
,
date
=
f
"
date
{
i
+
1
}
"
,
username
=
"
user1
"
,
realm
=
"
Realm1
"
)
for
i
in
range
(
10
)]
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment