Skip to content

Commit e514fbc

Browse files
committed
SDK-1589: Update Attibute Issuance Expiry Date format to be milliseconds, not microseconds; update version
1 parent 26f8c4f commit e514fbc

File tree

4 files changed

+111
-9
lines changed

4 files changed

+111
-9
lines changed

sonar-project.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ sonar.host.url = https://sonarcloud.io
22
sonar.organization = getyoti
33
sonar.projectKey = getyoti:python
44
sonar.projectName = Python SDK
5-
sonar.projectVersion = 2.12.0
5+
sonar.projectVersion = 2.12.1
66
sonar.exclusions = yoti_python_sdk/tests/**,examples/**,yoti_python_sdk/protobuf/**/*
77

88
sonar.python.pylint.reportPath = coverage.out
Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,46 @@
11
# -*- coding: utf-8 -*-
22
from __future__ import unicode_literals
3+
34
import copy
45

6+
import pytz
7+
58

69
class ThirdPartyAttributeExtension(object):
710
THIRDPARTY_ATTRIBUTE = "THIRD_PARTY_ATTRIBUTE"
811

912
def __init__(self):
10-
self.__extension = {}
11-
self.__extension["type"] = self.THIRDPARTY_ATTRIBUTE
12-
self.__extension["content"] = {"expiry_date": None, "definitions": []}
13+
self.__extension = {
14+
"type": self.THIRDPARTY_ATTRIBUTE,
15+
"content": {"expiry_date": None, "definitions": []},
16+
}
1317

1418
def with_expiry_date(self, expiry_date):
15-
self.__extension["content"]["expiry_date"] = expiry_date.isoformat()
19+
"""
20+
:param expiry_date: Expiry date for the attribute. If no timezone info is provided, UTC will be used.
21+
:type expiry_date: datetime
22+
"""
23+
if expiry_date.tzinfo is None:
24+
expiry_date = expiry_date.replace(tzinfo=pytz.UTC)
25+
26+
utc_time = expiry_date.astimezone(pytz.utc)
27+
rfc_3339_milliseconds = utc_time.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
28+
self.__extension["content"]["expiry_date"] = rfc_3339_milliseconds + "Z"
1629
return self
1730

1831
def with_definitions(self, *names):
32+
"""
33+
:param names: attribute definitions
34+
:type names: str or list[str]
35+
"""
1936
self.__extension["content"]["definitions"].extend([{"name": s} for s in names])
2037
return self
2138

2239
def build(self):
40+
"""
41+
Builds the object
42+
43+
:return: the third party attribute
44+
:rtype: ThirdPartyAttributeExtension
45+
"""
2346
return copy.deepcopy(self.__extension)

yoti_python_sdk/tests/dynamic_sharing_service/extension/test_third_party_attribute_extension.py

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
from __future__ import unicode_literals
33

44
from datetime import datetime
5+
6+
import pytest
7+
import pytz
8+
59
from yoti_python_sdk.dynamic_sharing_service.extension.third_party_attribute_extension import (
610
ThirdPartyAttributeExtension,
711
)
@@ -19,7 +23,7 @@ def test_should_create_extension():
1923
)
2024

2125
assert extension["type"] == ThirdPartyAttributeExtension.THIRDPARTY_ATTRIBUTE
22-
assert extension["content"]["expiry_date"] == "2019-10-30T12:10:09.458000"
26+
assert extension["content"]["expiry_date"] == "2019-10-30T12:10:09.458Z"
2327
assert extension["content"]["definitions"][0]["name"] == DEFINITION
2428

2529

@@ -38,7 +42,7 @@ def test_with_definition_should_add_to_list():
3842
)
3943

4044
assert extension["type"] == ThirdPartyAttributeExtension.THIRDPARTY_ATTRIBUTE
41-
assert extension["content"]["expiry_date"] == "2019-10-30T12:10:09.458000"
45+
assert extension["content"]["expiry_date"] == "2019-10-30T12:10:09.458Z"
4246

4347
assert extension["content"]["definitions"][0]["name"] == DEFINITION1
4448
assert extension["content"]["definitions"][1]["name"] == DEFINITION2
@@ -58,7 +62,82 @@ def test_with_definition_should_add_multiple():
5862
)
5963

6064
assert extension["type"] == ThirdPartyAttributeExtension.THIRDPARTY_ATTRIBUTE
61-
assert extension["content"]["expiry_date"] == "2019-10-30T12:10:09.458000"
65+
assert extension["content"]["expiry_date"] == "2019-10-30T12:10:09.458Z"
6266

6367
assert extension["content"]["definitions"][0]["name"] == DEFINITION1
6468
assert extension["content"]["definitions"][1]["name"] == DEFINITION2
69+
70+
71+
@pytest.mark.parametrize(
72+
"expiry_date, expected_value",
73+
[
74+
(
75+
datetime(2051, 1, 13, 19, 50, 53, 1, tzinfo=pytz.utc),
76+
"2051-01-13T19:50:53.000Z",
77+
),
78+
(
79+
datetime(2026, 2, 2, 22, 4, 5, 123, tzinfo=pytz.utc),
80+
"2026-02-02T22:04:05.000Z",
81+
),
82+
(
83+
datetime(2051, 4, 13, 19, 50, 53, 999, tzinfo=pytz.utc),
84+
"2051-04-13T19:50:53.000Z",
85+
),
86+
(
87+
datetime(2026, 1, 31, 22, 4, 5, 1232, tzinfo=pytz.utc),
88+
"2026-01-31T22:04:05.001Z",
89+
),
90+
(
91+
datetime(2026, 1, 31, 22, 4, 5, 17777, tzinfo=pytz.utc),
92+
"2026-01-31T22:04:05.017Z",
93+
),
94+
(
95+
datetime(2019, 10, 30, 12, 10, 9, int(458e3), tzinfo=pytz.utc),
96+
"2019-10-30T12:10:09.458Z",
97+
),
98+
(
99+
datetime(2026, 1, 2, 22, 4, 5, 123456, tzinfo=pytz.utc),
100+
"2026-01-02T22:04:05.123Z",
101+
),
102+
],
103+
)
104+
def test_should_format_utc_expiry_dates_correctly(expiry_date, expected_value):
105+
DEFINITION = "some_value"
106+
107+
extension = (
108+
ThirdPartyAttributeExtension()
109+
.with_expiry_date(expiry_date)
110+
.with_definitions(DEFINITION)
111+
.build()
112+
)
113+
114+
assert extension["content"]["expiry_date"] == expected_value
115+
116+
117+
@pytest.mark.parametrize(
118+
"expiry_date, tz_name",
119+
[
120+
(datetime(2030, 6, 6, 8, 0, 0, 0), "US/Eastern",),
121+
(datetime(2030, 6, 6, 15, 0, 0, 0), "Europe/Moscow",),
122+
(datetime(2030, 6, 6, 7, 0, 0, 0), "America/Jamaica",),
123+
(datetime(2030, 6, 6, 23, 0, 0, 0), "Etc/GMT-11"),
124+
(datetime(2030, 6, 6, 7, 0, 0, 0), "Etc/GMT+5"),
125+
# In order to conform with the POSIX style, those zones beginning
126+
# with "Etc/GMT" have their sign reversed from what most people expect. In this style, zones west of GMT have
127+
# a positive sign and those east have a negative sign.
128+
],
129+
)
130+
def test_should_format_localized_expiry_dates(expiry_date, tz_name):
131+
DEFINITION = "some_value"
132+
133+
tz = pytz.timezone(tz_name)
134+
localized_expiry_date = tz.localize(expiry_date)
135+
136+
extension = (
137+
ThirdPartyAttributeExtension()
138+
.with_expiry_date(localized_expiry_date)
139+
.with_definitions(DEFINITION)
140+
.build()
141+
)
142+
143+
assert extension["content"]["expiry_date"] == "2030-06-06T12:00:00.000Z"

yoti_python_sdk/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# -*- coding: utf-8 -*-
2-
__version__ = "2.12.0"
2+
__version__ = "2.12.1"

0 commit comments

Comments
 (0)