Skip to content

Commit 7ab1470

Browse files
committed
address issue with WorkflowStatus in historical revisions.
version 0.5.2 posted for fix above. added example code for history summaries. updated documentation for get_revision_by_number call. Signed-off-by: Neal Ensor <ensorn@osti.gov>
1 parent 9a9cfc2 commit 7ab1470

8 files changed

Lines changed: 162 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,6 @@
143143
- Adding "examples" to repository containing several sample code examples
144144
- **[Breaking change]** Removed field date_released; replaced by date_first_released and date_last_released
145145
- Fix issue with unexpected list values
146+
147+
## 0.5.2 - 8/8/2025
148+
- Fix issue with revision history WorkflowStatus (github #3)

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- [Seeing Validation Errors on Exception](#seeing-validation-errors-on-exception)
1515
- [Processing Status of Submitted Records](#processing-status-of-submitted-records)
1616
- [View Revision History](#view-revision-history)
17+
- [VIew Record at Revision](#view-record-at-revision)
1718
- [Adding Media to Record](#adding-media-to-record)
1819
- [Removing Metadata Records](#removing-record)
1920
- [Removing Media from a Record](#removing-media-from-a-record)
@@ -231,6 +232,25 @@ oldest_revision = revision_history[-1]
231232

232233
Various components of record history are available in each revision summary, such as the `date_valid_start` and `date_valid_end`, `workflow_status`, etc. The dates indicate the time frame during which that revision was "current"; if `date_valid_end` is null, this indicates that particular revision is the current one.
233234

235+
#### View Record at Revision<a id="view-record-at-revision"></a>
236+
```python
237+
from elinkapi import Elink, NotFoundException, ForbiddenException
238+
239+
api = Elink(token = "__Your_API_Token__")
240+
241+
try:
242+
record = api.get_revision_by_number(4525922,2)
243+
244+
record.json()
245+
246+
print ("Revision valid from {} to {}".format(record.date_valid_start,
247+
record.date_valid_end if record.date_valid_end else "Present"))
248+
except NotFoundException:
249+
print ("Revision not on file.")
250+
except ForbiddenException:
251+
print ("Permission to view record denied.")
252+
```
253+
234254
#### Adding Media to Record<a id="adding-media-to-record"></a>
235255
```python
236256
from elinkapi import Elink

examples/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,22 @@ applicable. Demonstrates pulling in single OSTI ID record and use of the Record
4747

4848
`python details.py --id OSTIID`
4949

50+
### History
51+
52+
Obtain summary of historical revisions of a particular record at OSTI by its OSTI ID. Optionally, view some details for a provided revision
53+
number to see its state at that point in time.
54+
55+
`python history.py --id OSTIID [--revision REVISION]`
56+
57+
```
58+
usage: history [-h] -i ID [-r REVISION]
59+
60+
Summary of metadata revision history at OSTI.
61+
62+
options:
63+
-h, --help show this help message and exit
64+
-i ID, --id ID OSTI ID of record to view.
65+
-r REVISION, --revision REVISION
66+
Optional revision number for a particular revision. Omit for summary of all
67+
revisions.
68+
```

examples/details.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ def audit(logs):
66
"""
77
Print out details from the audit logs.
88
"""
9+
print ("Type".center(20,"_"), "Audit Date".center(30,"_"), "State".center(15,"_"))
10+
911
for log in logs:
10-
print (f"- {log.type} on {log.audit_date.strftime('%Y-%m-%d %H:%M:%S')}, state {log.status}")
11-
for message in log.messages:
12-
print (f" * {message}")
12+
print ("{0:20.20s} {1:30.30s} {2:15.15s}".format(log.type,
13+
log.audit_date.strftime("%Y-%m-%d %H:%M:%S %z"),
14+
log.status))
15+
for message in log.messages if log.messages else []:
16+
print (" - {message}")
1317

1418
def print_person(p):
1519
"""

examples/history.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from config import TOKEN, TARGET
2+
from elinkapi import Elink, NotFoundException, ForbiddenException, WorkflowStatus, ProductType, Identifier
3+
import argparse, sys
4+
5+
def print_person(p):
6+
"""
7+
Print details on a particular Person record.
8+
"""
9+
print (" * {0}, {1} {2} {3}".format(p.last_name,
10+
p.first_name,
11+
p.middle_name if p.middle_name else "",
12+
p.contributor_type if p.contributor_type else ""))
13+
14+
def print_organization(o):
15+
"""
16+
Print organization details.
17+
"""
18+
print (" * {0} {1}".format(o.name,
19+
o.contributor_type if o.contributor_type else ""))
20+
21+
def summarize(record):
22+
"""
23+
Display a brief summary of a given OSTI ID record.
24+
"""
25+
print (f"Details for record OSTI ID {record.osti_id}")
26+
print ("Revision {0} VALID FROM {1} TO {2}".format(record.revision,
27+
record.date_valid_start.strftime("%Y-%m-%d %H:%M:%S %z") if record.date_valid_start else "None",
28+
record.date_valid_end.strftime("%Y-%m-%d %H:%M:%S %z") if record.date_valid_end else "Present"))
29+
print ("")
30+
print (f"Title: {record.title}")
31+
print (f"Product type: {ProductType(record.product_type).name}")
32+
print ("Publication Date: {}".format(record.publication_date.strftime("%Y-%m-%d") if record.publication_date else "None Provided"))
33+
34+
if record.doi:
35+
print (f"DOI {record.doi}")
36+
37+
print ("")
38+
print (f"Workflow Status: {WorkflowStatus(record.workflow_status).name}")
39+
print ("")
40+
41+
print (f"Description:\n{record.description}")
42+
43+
print ("Persons:")
44+
45+
print ("- AUTHORS")
46+
for person in list(filter(lambda x: x.type=="AUTHOR", record.persons)):
47+
print_person(person)
48+
49+
print ("- CONTRIBUTORS")
50+
for person in list(filter(lambda c: c.type=="CONTRIBUTING", record.persons)):
51+
print_person(person)
52+
53+
print ("Organizations:")
54+
print ("- SPONSORING")
55+
for organization in list(filter(lambda x: x.type=="SPONSOR", record.organizations)):
56+
print_organization(organization)
57+
58+
print ("- RESEARCHING")
59+
for organization in list(filter(lambda x: x.type=="RESEARCHING", record.organizations)):
60+
print_organization(organization)
61+
print ("- CONTRIBUTING")
62+
for organization in list(filter(lambda x: x.type=="CONTRIBUTING", record.organizations)):
63+
print_organization(organization)
64+
65+
print ("Identifiers:")
66+
for identifier in record.identifiers:
67+
print ("- {0} ({1})".format(identifier.value, Identifier.Type(identifier.type).name))
68+
69+
def history(args):
70+
"""
71+
View a brief summary of a particular revision.
72+
"""
73+
parser = argparse.ArgumentParser("history", description="Summary of metadata revision history at OSTI.")
74+
parser.add_argument("-i", "--id", help="OSTI ID of record to view.", type=int, required=True)
75+
parser.add_argument("-r", "--revision", help="Optional revision number for a particular revision. Omit for summary of all revisions.", type=int)
76+
args = parser.parse_args()
77+
78+
# make a link to the API
79+
api = Elink(target = TARGET, token=TOKEN)
80+
81+
# provide all revisions summary unless particular revision provided.
82+
# OSTI ID is required, and handle exceptions if encountered.
83+
try:
84+
if args.revision:
85+
record = api.get_revision_by_number(args.id, args.revision)
86+
87+
summarize(record)
88+
else:
89+
summary = api.get_all_revisions(args.id)
90+
91+
print (f"Summary of revisions for OSTI ID {args.id}\n")
92+
print ("Rev#".center(5, "_"), "Date Valid From".center(30,"_"), "Date Valid To".center(30,"_"), "Status".center(20,"_"))
93+
94+
for rec in summary:
95+
print ("{0:5d} {1:30.30s} {2:30.30s} {3:20.20s}".format(rec.revision,
96+
rec.date_valid_start.strftime("%Y-%m-%d %H:%M:%S %z") if rec.date_valid_start else "None",
97+
rec.date_valid_end.strftime("%Y-%m-%d %H:%M:%S %z") if rec.date_valid_end else "Present",
98+
WorkflowStatus(rec.workflow_status).name))
99+
except NotFoundException:
100+
print (f"Record ID {args.id} is not on file, or revision not found.")
101+
except ForbiddenException:
102+
print ("Access to record denied.")
103+
104+
if __name__ == "__main__":
105+
history(sys.argv[1:])

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "elinkapi"
7-
version = "0.5.1"
7+
version = "0.5.2"
88
authors = [
99
{ name="Neal Ensor", email="ensorn@osti.gov" },
1010
{ name="Jacob Samar" }

src/elinkapi/record.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,8 @@ class RecordResponse(Record):
320320
date_submitted_to_osti_last: datetime = None
321321
date_released_first: datetime = None
322322
date_released_last: datetime = None
323+
date_valid_start: datetime = None
324+
date_valid_end: datetime = None
323325
sensitivity_flag: str = None
324326
hidden_flag: bool = False
325327
media: list[MediaInfo] = None

src/elinkapi/revision.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import List
33
from enum import Enum
44
import datetime
5+
from .record import WorkflowStatus
56

67
class Revision(BaseModel):
78
model_config = ConfigDict(validate_assignment=True)
@@ -15,6 +16,9 @@ class Revision(BaseModel):
1516
@field_validator("workflow_status")
1617
@classmethod
1718
def workflow_status_must_be_valid(cls, value: str) -> str:
18-
if value not in [type.value for type in cls.WorkflowStatus]:
19+
"""
20+
Require a valid value in workflow status.
21+
"""
22+
if value not in [type.value for type in WorkflowStatus]:
1923
raise ValueError("Unknown Workflow Status {}.".format(value))
2024
return value

0 commit comments

Comments
 (0)