swift 2.23.2__py3-none-any.whl → 2.35.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. swift/__init__.py +29 -50
  2. swift/account/auditor.py +21 -118
  3. swift/account/backend.py +33 -28
  4. swift/account/reaper.py +37 -28
  5. swift/account/replicator.py +22 -0
  6. swift/account/server.py +60 -26
  7. swift/account/utils.py +28 -11
  8. swift-2.23.2.data/scripts/swift-account-audit → swift/cli/account_audit.py +23 -13
  9. swift-2.23.2.data/scripts/swift-config → swift/cli/config.py +2 -2
  10. swift/cli/container_deleter.py +5 -11
  11. swift-2.23.2.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +8 -7
  12. swift/cli/dispersion_report.py +10 -9
  13. swift-2.23.2.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +63 -21
  14. swift/cli/form_signature.py +3 -7
  15. swift-2.23.2.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +8 -2
  16. swift/cli/info.py +183 -29
  17. swift/cli/manage_shard_ranges.py +708 -37
  18. swift-2.23.2.data/scripts/swift-oldies → swift/cli/oldies.py +25 -14
  19. swift-2.23.2.data/scripts/swift-orphans → swift/cli/orphans.py +7 -3
  20. swift/cli/recon.py +196 -67
  21. swift-2.23.2.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +17 -20
  22. swift-2.23.2.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
  23. swift/cli/relinker.py +807 -126
  24. swift/cli/reload.py +135 -0
  25. swift/cli/ringbuilder.py +217 -20
  26. swift/cli/ringcomposer.py +0 -1
  27. swift/cli/shard-info.py +4 -3
  28. swift/common/base_storage_server.py +9 -20
  29. swift/common/bufferedhttp.py +48 -74
  30. swift/common/constraints.py +20 -15
  31. swift/common/container_sync_realms.py +9 -11
  32. swift/common/daemon.py +25 -8
  33. swift/common/db.py +198 -127
  34. swift/common/db_auditor.py +168 -0
  35. swift/common/db_replicator.py +95 -55
  36. swift/common/digest.py +141 -0
  37. swift/common/direct_client.py +144 -33
  38. swift/common/error_limiter.py +93 -0
  39. swift/common/exceptions.py +25 -1
  40. swift/common/header_key_dict.py +2 -9
  41. swift/common/http_protocol.py +373 -0
  42. swift/common/internal_client.py +129 -59
  43. swift/common/linkat.py +3 -4
  44. swift/common/manager.py +284 -67
  45. swift/common/memcached.py +396 -147
  46. swift/common/middleware/__init__.py +4 -0
  47. swift/common/middleware/account_quotas.py +211 -46
  48. swift/common/middleware/acl.py +3 -8
  49. swift/common/middleware/backend_ratelimit.py +230 -0
  50. swift/common/middleware/bulk.py +22 -34
  51. swift/common/middleware/catch_errors.py +1 -3
  52. swift/common/middleware/cname_lookup.py +6 -11
  53. swift/common/middleware/container_quotas.py +1 -1
  54. swift/common/middleware/container_sync.py +39 -17
  55. swift/common/middleware/copy.py +12 -0
  56. swift/common/middleware/crossdomain.py +22 -9
  57. swift/common/middleware/crypto/__init__.py +2 -1
  58. swift/common/middleware/crypto/crypto_utils.py +11 -15
  59. swift/common/middleware/crypto/decrypter.py +28 -11
  60. swift/common/middleware/crypto/encrypter.py +12 -17
  61. swift/common/middleware/crypto/keymaster.py +8 -15
  62. swift/common/middleware/crypto/kms_keymaster.py +2 -1
  63. swift/common/middleware/dlo.py +15 -11
  64. swift/common/middleware/domain_remap.py +5 -4
  65. swift/common/middleware/etag_quoter.py +128 -0
  66. swift/common/middleware/formpost.py +73 -70
  67. swift/common/middleware/gatekeeper.py +8 -1
  68. swift/common/middleware/keystoneauth.py +33 -3
  69. swift/common/middleware/list_endpoints.py +4 -4
  70. swift/common/middleware/listing_formats.py +85 -49
  71. swift/common/middleware/memcache.py +4 -81
  72. swift/common/middleware/name_check.py +3 -2
  73. swift/common/middleware/proxy_logging.py +160 -92
  74. swift/common/middleware/ratelimit.py +17 -10
  75. swift/common/middleware/read_only.py +6 -4
  76. swift/common/middleware/recon.py +59 -22
  77. swift/common/middleware/s3api/acl_handlers.py +25 -3
  78. swift/common/middleware/s3api/acl_utils.py +6 -1
  79. swift/common/middleware/s3api/controllers/__init__.py +6 -0
  80. swift/common/middleware/s3api/controllers/acl.py +3 -2
  81. swift/common/middleware/s3api/controllers/bucket.py +242 -137
  82. swift/common/middleware/s3api/controllers/logging.py +2 -2
  83. swift/common/middleware/s3api/controllers/multi_delete.py +43 -20
  84. swift/common/middleware/s3api/controllers/multi_upload.py +219 -133
  85. swift/common/middleware/s3api/controllers/obj.py +112 -8
  86. swift/common/middleware/s3api/controllers/object_lock.py +44 -0
  87. swift/common/middleware/s3api/controllers/s3_acl.py +2 -2
  88. swift/common/middleware/s3api/controllers/tagging.py +57 -0
  89. swift/common/middleware/s3api/controllers/versioning.py +36 -7
  90. swift/common/middleware/s3api/etree.py +22 -9
  91. swift/common/middleware/s3api/exception.py +0 -4
  92. swift/common/middleware/s3api/s3api.py +113 -41
  93. swift/common/middleware/s3api/s3request.py +384 -218
  94. swift/common/middleware/s3api/s3response.py +126 -23
  95. swift/common/middleware/s3api/s3token.py +16 -17
  96. swift/common/middleware/s3api/schema/delete.rng +1 -1
  97. swift/common/middleware/s3api/subresource.py +7 -10
  98. swift/common/middleware/s3api/utils.py +27 -10
  99. swift/common/middleware/slo.py +665 -358
  100. swift/common/middleware/staticweb.py +64 -37
  101. swift/common/middleware/symlink.py +52 -19
  102. swift/common/middleware/tempauth.py +76 -58
  103. swift/common/middleware/tempurl.py +192 -174
  104. swift/common/middleware/versioned_writes/__init__.py +51 -0
  105. swift/common/middleware/{versioned_writes.py → versioned_writes/legacy.py} +27 -26
  106. swift/common/middleware/versioned_writes/object_versioning.py +1482 -0
  107. swift/common/middleware/x_profile/exceptions.py +1 -4
  108. swift/common/middleware/x_profile/html_viewer.py +18 -19
  109. swift/common/middleware/x_profile/profile_model.py +1 -2
  110. swift/common/middleware/xprofile.py +10 -10
  111. swift-2.23.2.data/scripts/swift-container-server → swift/common/recon.py +13 -8
  112. swift/common/registry.py +147 -0
  113. swift/common/request_helpers.py +324 -57
  114. swift/common/ring/builder.py +67 -25
  115. swift/common/ring/composite_builder.py +1 -1
  116. swift/common/ring/ring.py +177 -51
  117. swift/common/ring/utils.py +1 -1
  118. swift/common/splice.py +10 -6
  119. swift/common/statsd_client.py +205 -0
  120. swift/common/storage_policy.py +49 -44
  121. swift/common/swob.py +86 -102
  122. swift/common/{utils.py → utils/__init__.py} +2191 -2762
  123. swift/common/utils/base.py +131 -0
  124. swift/common/utils/config.py +433 -0
  125. swift/common/utils/ipaddrs.py +256 -0
  126. swift/common/utils/libc.py +345 -0
  127. swift/common/utils/logs.py +859 -0
  128. swift/common/utils/timestamp.py +412 -0
  129. swift/common/wsgi.py +555 -536
  130. swift/container/auditor.py +14 -100
  131. swift/container/backend.py +552 -227
  132. swift/container/reconciler.py +126 -37
  133. swift/container/replicator.py +96 -22
  134. swift/container/server.py +397 -176
  135. swift/container/sharder.py +1580 -639
  136. swift/container/sync.py +94 -88
  137. swift/container/updater.py +53 -32
  138. swift/obj/auditor.py +153 -35
  139. swift/obj/diskfile.py +466 -217
  140. swift/obj/expirer.py +406 -124
  141. swift/obj/mem_diskfile.py +7 -4
  142. swift/obj/mem_server.py +1 -0
  143. swift/obj/reconstructor.py +523 -262
  144. swift/obj/replicator.py +249 -188
  145. swift/obj/server.py +213 -122
  146. swift/obj/ssync_receiver.py +145 -85
  147. swift/obj/ssync_sender.py +113 -54
  148. swift/obj/updater.py +653 -139
  149. swift/obj/watchers/__init__.py +0 -0
  150. swift/obj/watchers/dark_data.py +213 -0
  151. swift/proxy/controllers/account.py +11 -11
  152. swift/proxy/controllers/base.py +848 -604
  153. swift/proxy/controllers/container.py +452 -86
  154. swift/proxy/controllers/info.py +3 -2
  155. swift/proxy/controllers/obj.py +1009 -490
  156. swift/proxy/server.py +185 -112
  157. swift-2.35.0.dist-info/AUTHORS +501 -0
  158. swift-2.35.0.dist-info/LICENSE +202 -0
  159. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/METADATA +52 -61
  160. swift-2.35.0.dist-info/RECORD +201 -0
  161. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/WHEEL +1 -1
  162. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/entry_points.txt +43 -0
  163. swift-2.35.0.dist-info/pbr.json +1 -0
  164. swift/locale/de/LC_MESSAGES/swift.po +0 -1216
  165. swift/locale/en_GB/LC_MESSAGES/swift.po +0 -1207
  166. swift/locale/es/LC_MESSAGES/swift.po +0 -1085
  167. swift/locale/fr/LC_MESSAGES/swift.po +0 -909
  168. swift/locale/it/LC_MESSAGES/swift.po +0 -894
  169. swift/locale/ja/LC_MESSAGES/swift.po +0 -965
  170. swift/locale/ko_KR/LC_MESSAGES/swift.po +0 -964
  171. swift/locale/pt_BR/LC_MESSAGES/swift.po +0 -881
  172. swift/locale/ru/LC_MESSAGES/swift.po +0 -891
  173. swift/locale/tr_TR/LC_MESSAGES/swift.po +0 -832
  174. swift/locale/zh_CN/LC_MESSAGES/swift.po +0 -833
  175. swift/locale/zh_TW/LC_MESSAGES/swift.po +0 -838
  176. swift-2.23.2.data/scripts/swift-account-auditor +0 -23
  177. swift-2.23.2.data/scripts/swift-account-info +0 -51
  178. swift-2.23.2.data/scripts/swift-account-reaper +0 -23
  179. swift-2.23.2.data/scripts/swift-account-replicator +0 -34
  180. swift-2.23.2.data/scripts/swift-account-server +0 -23
  181. swift-2.23.2.data/scripts/swift-container-auditor +0 -23
  182. swift-2.23.2.data/scripts/swift-container-info +0 -51
  183. swift-2.23.2.data/scripts/swift-container-reconciler +0 -21
  184. swift-2.23.2.data/scripts/swift-container-replicator +0 -34
  185. swift-2.23.2.data/scripts/swift-container-sharder +0 -33
  186. swift-2.23.2.data/scripts/swift-container-sync +0 -23
  187. swift-2.23.2.data/scripts/swift-container-updater +0 -23
  188. swift-2.23.2.data/scripts/swift-dispersion-report +0 -24
  189. swift-2.23.2.data/scripts/swift-form-signature +0 -20
  190. swift-2.23.2.data/scripts/swift-init +0 -119
  191. swift-2.23.2.data/scripts/swift-object-auditor +0 -29
  192. swift-2.23.2.data/scripts/swift-object-expirer +0 -33
  193. swift-2.23.2.data/scripts/swift-object-info +0 -60
  194. swift-2.23.2.data/scripts/swift-object-reconstructor +0 -33
  195. swift-2.23.2.data/scripts/swift-object-relinker +0 -41
  196. swift-2.23.2.data/scripts/swift-object-replicator +0 -37
  197. swift-2.23.2.data/scripts/swift-object-server +0 -27
  198. swift-2.23.2.data/scripts/swift-object-updater +0 -23
  199. swift-2.23.2.data/scripts/swift-proxy-server +0 -23
  200. swift-2.23.2.data/scripts/swift-recon +0 -24
  201. swift-2.23.2.data/scripts/swift-ring-builder +0 -24
  202. swift-2.23.2.data/scripts/swift-ring-builder-analyzer +0 -22
  203. swift-2.23.2.data/scripts/swift-ring-composer +0 -22
  204. swift-2.23.2.dist-info/DESCRIPTION.rst +0 -166
  205. swift-2.23.2.dist-info/RECORD +0 -220
  206. swift-2.23.2.dist-info/metadata.json +0 -1
  207. swift-2.23.2.dist-info/pbr.json +0 -1
  208. {swift-2.23.2.dist-info → swift-2.35.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,412 @@
1
+ # Copyright (c) 2010-2023 OpenStack Foundation
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
+ # implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Timestamp-related functions for use with Swift."""
17
+
18
+ import datetime
19
+ import functools
20
+ import math
21
+ import sys
22
+ import time
23
+
24
+
25
+ NORMAL_FORMAT = "%016.05f"
26
+ INTERNAL_FORMAT = NORMAL_FORMAT + '_%016x'
27
+ SHORT_FORMAT = NORMAL_FORMAT + '_%x'
28
+ MAX_OFFSET = (16 ** 16) - 1
29
+ PRECISION = 1e-5
30
+ # Setting this to True will cause the internal format to always display
31
+ # extended digits - even when the value is equivalent to the normalized form.
32
+ # This isn't ideal during an upgrade when some servers might not understand
33
+ # the new time format - but flipping it to True works great for testing.
34
+ FORCE_INTERNAL = False # or True
35
+
36
+
37
+ @functools.total_ordering
38
+ class Timestamp(object):
39
+ """
40
+ Internal Representation of Swift Time.
41
+
42
+ The normalized form of the X-Timestamp header looks like a float
43
+ with a fixed width to ensure stable string sorting - normalized
44
+ timestamps look like "1402464677.04188"
45
+
46
+ To support overwrites of existing data without modifying the original
47
+ timestamp but still maintain consistency a second internal offset vector
48
+ is append to the normalized timestamp form which compares and sorts
49
+ greater than the fixed width float format but less than a newer timestamp.
50
+ The internalized format of timestamps looks like
51
+ "1402464677.04188_0000000000000000" - the portion after the underscore is
52
+ the offset and is a formatted hexadecimal integer.
53
+
54
+ The internalized form is not exposed to clients in responses from
55
+ Swift. Normal client operations will not create a timestamp with an
56
+ offset.
57
+
58
+ The Timestamp class in common.utils supports internalized and
59
+ normalized formatting of timestamps and also comparison of timestamp
60
+ values. When the offset value of a Timestamp is 0 - it's considered
61
+ insignificant and need not be represented in the string format; to
62
+ support backwards compatibility during a Swift upgrade the
63
+ internalized and normalized form of a Timestamp with an
64
+ insignificant offset are identical. When a timestamp includes an
65
+ offset it will always be represented in the internalized form, but
66
+ is still excluded from the normalized form. Timestamps with an
67
+ equivalent timestamp portion (the float part) will compare and order
68
+ by their offset. Timestamps with a greater timestamp portion will
69
+ always compare and order greater than a Timestamp with a lesser
70
+ timestamp regardless of it's offset. String comparison and ordering
71
+ is guaranteed for the internalized string format, and is backwards
72
+ compatible for normalized timestamps which do not include an offset.
73
+ """
74
+
75
+ def __init__(self, timestamp, offset=0, delta=0, check_bounds=True):
76
+ """
77
+ Create a new Timestamp.
78
+
79
+ :param timestamp: time in seconds since the Epoch, may be any of:
80
+
81
+ * a float or integer
82
+ * normalized/internalized string
83
+ * another instance of this class (offset is preserved)
84
+
85
+ :param offset: the second internal offset vector, an int
86
+ :param delta: deca-microsecond difference from the base timestamp
87
+ param, an int
88
+ """
89
+ if isinstance(timestamp, bytes):
90
+ timestamp = timestamp.decode('ascii')
91
+ if isinstance(timestamp, str):
92
+ base, base_offset = timestamp.partition('_')[::2]
93
+ self.timestamp = float(base)
94
+ if '_' in base_offset:
95
+ raise ValueError('invalid literal for int() with base 16: '
96
+ '%r' % base_offset)
97
+ if base_offset:
98
+ self.offset = int(base_offset, 16)
99
+ else:
100
+ self.offset = 0
101
+ else:
102
+ self.timestamp = float(timestamp)
103
+ self.offset = getattr(timestamp, 'offset', 0)
104
+ # increment offset
105
+ if offset >= 0:
106
+ self.offset += offset
107
+ else:
108
+ raise ValueError('offset must be non-negative')
109
+ if self.offset > MAX_OFFSET:
110
+ raise ValueError('offset must be smaller than %d' % MAX_OFFSET)
111
+ self.raw = int(round(self.timestamp / PRECISION))
112
+ # add delta
113
+ if delta:
114
+ self.raw = self.raw + delta
115
+ if self.raw <= 0:
116
+ raise ValueError(
117
+ 'delta must be greater than %d' % (-1 * self.raw))
118
+ self.timestamp = float(self.raw * PRECISION)
119
+ if check_bounds:
120
+ if self.timestamp < 0:
121
+ raise ValueError('timestamp cannot be negative')
122
+ if self.timestamp >= 10000000000:
123
+ raise ValueError('timestamp too large')
124
+
125
+ @classmethod
126
+ def now(cls, offset=0, delta=0):
127
+ return cls(time.time(), offset=offset, delta=delta)
128
+
129
+ def __repr__(self):
130
+ return INTERNAL_FORMAT % (self.timestamp, self.offset)
131
+
132
+ def __str__(self):
133
+ raise TypeError('You must specify which string format is required')
134
+
135
+ def __float__(self):
136
+ return self.timestamp
137
+
138
+ def __int__(self):
139
+ return int(self.timestamp)
140
+
141
+ def __bool__(self):
142
+ return bool(self.timestamp or self.offset)
143
+
144
+ @property
145
+ def normal(self):
146
+ return NORMAL_FORMAT % self.timestamp
147
+
148
+ @property
149
+ def internal(self):
150
+ if self.offset or FORCE_INTERNAL:
151
+ return INTERNAL_FORMAT % (self.timestamp, self.offset)
152
+ else:
153
+ return self.normal
154
+
155
+ @property
156
+ def short(self):
157
+ if self.offset or FORCE_INTERNAL:
158
+ return SHORT_FORMAT % (self.timestamp, self.offset)
159
+ else:
160
+ return self.normal
161
+
162
+ @property
163
+ def isoformat(self):
164
+ """
165
+ Get an isoformat string representation of the 'normal' part of the
166
+ Timestamp with microsecond precision and no trailing timezone, for
167
+ example::
168
+
169
+ 1970-01-01T00:00:00.000000
170
+
171
+ :return: an isoformat string
172
+ """
173
+ t = float(self.normal)
174
+ # On Python 3, round manually using ROUND_HALF_EVEN rounding
175
+ # method, to use the same rounding method than Python 2. Python 3
176
+ # used a different rounding method, but Python 3.4.4 and 3.5.1 use
177
+ # again ROUND_HALF_EVEN as Python 2.
178
+ # See https://bugs.python.org/issue23517
179
+ frac, t = math.modf(t)
180
+ us = round(frac * 1e6)
181
+ if us >= 1000000:
182
+ t += 1
183
+ us -= 1000000
184
+ elif us < 0:
185
+ t -= 1
186
+ us += 1000000
187
+ dt = datetime.datetime.fromtimestamp(t, UTC)
188
+ dt = dt.replace(microsecond=us)
189
+
190
+ isoformat = dt.isoformat()
191
+ # need to drop tzinfo
192
+ isoformat = isoformat[:isoformat.index('+')]
193
+ # python isoformat() doesn't include msecs when zero
194
+ if len(isoformat) < len("1970-01-01T00:00:00.000000"):
195
+ isoformat += ".000000"
196
+ return isoformat
197
+
198
+ @classmethod
199
+ def from_isoformat(cls, date_string):
200
+ """
201
+ Parse an isoformat string representation of time to a Timestamp object.
202
+
203
+ :param date_string: a string formatted as per an Timestamp.isoformat
204
+ property.
205
+ :return: an instance of this class.
206
+ """
207
+ start = datetime.datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S.%f")
208
+ delta = start - EPOCH
209
+ # This calculation is based on Python 2.7's Modules/datetimemodule.c,
210
+ # function delta_to_microseconds(), but written in Python.
211
+ return cls(delta.total_seconds())
212
+
213
+ def ceil(self):
214
+ """
215
+ Return the 'normal' part of the timestamp rounded up to the nearest
216
+ integer number of seconds.
217
+
218
+ This value should be used whenever the second-precision Last-Modified
219
+ time of a resource is required.
220
+
221
+ :return: a float value with second precision.
222
+ """
223
+ return math.ceil(float(self))
224
+
225
+ def __eq__(self, other):
226
+ if other is None:
227
+ return False
228
+ if not isinstance(other, Timestamp):
229
+ try:
230
+ other = Timestamp(other, check_bounds=False)
231
+ except ValueError:
232
+ return False
233
+ return self.internal == other.internal
234
+
235
+ def __ne__(self, other):
236
+ return not (self == other)
237
+
238
+ def __lt__(self, other):
239
+ if other is None:
240
+ return False
241
+ if not isinstance(other, Timestamp):
242
+ other = Timestamp(other, check_bounds=False)
243
+ if other.timestamp < 0:
244
+ return False
245
+ if other.timestamp >= 10000000000:
246
+ return True
247
+ return self.internal < other.internal
248
+
249
+ def __hash__(self):
250
+ return hash(self.internal)
251
+
252
+ def __invert__(self):
253
+ if self.offset:
254
+ raise ValueError('Cannot invert timestamps with offsets')
255
+ return Timestamp((999999999999999 - self.raw) * PRECISION)
256
+
257
+
258
+ def encode_timestamps(t1, t2=None, t3=None, explicit=False):
259
+ """
260
+ Encode up to three timestamps into a string. Unlike a Timestamp object, the
261
+ encoded string does NOT used fixed width fields and consequently no
262
+ relative chronology of the timestamps can be inferred from lexicographic
263
+ sorting of encoded timestamp strings.
264
+
265
+ The format of the encoded string is:
266
+ <t1>[<+/-><t2 - t1>[<+/-><t3 - t2>]]
267
+
268
+ i.e. if t1 = t2 = t3 then just the string representation of t1 is returned,
269
+ otherwise the time offsets for t2 and t3 are appended. If explicit is True
270
+ then the offsets for t2 and t3 are always appended even if zero.
271
+
272
+ Note: any offset value in t1 will be preserved, but offsets on t2 and t3
273
+ are not preserved. In the anticipated use cases for this method (and the
274
+ inverse decode_timestamps method) the timestamps passed as t2 and t3 are
275
+ not expected to have offsets as they will be timestamps associated with a
276
+ POST request. In the case where the encoding is used in a container objects
277
+ table row, t1 could be the PUT or DELETE time but t2 and t3 represent the
278
+ content type and metadata times (if different from the data file) i.e.
279
+ correspond to POST timestamps. In the case where the encoded form is used
280
+ in a .meta file name, t1 and t2 both correspond to POST timestamps.
281
+ """
282
+ form = '{0}'
283
+ values = [t1.short]
284
+ if t2 is not None:
285
+ t2_t1_delta = t2.raw - t1.raw
286
+ explicit = explicit or (t2_t1_delta != 0)
287
+ values.append(t2_t1_delta)
288
+ if t3 is not None:
289
+ t3_t2_delta = t3.raw - t2.raw
290
+ explicit = explicit or (t3_t2_delta != 0)
291
+ values.append(t3_t2_delta)
292
+ if explicit:
293
+ form += '{1:+x}'
294
+ if t3 is not None:
295
+ form += '{2:+x}'
296
+ return form.format(*values)
297
+
298
+
299
+ def decode_timestamps(encoded, explicit=False):
300
+ """
301
+ Parses a string of the form generated by encode_timestamps and returns
302
+ a tuple of the three component timestamps. If explicit is False, component
303
+ timestamps that are not explicitly encoded will be assumed to have zero
304
+ delta from the previous component and therefore take the value of the
305
+ previous component. If explicit is True, component timestamps that are
306
+ not explicitly encoded will be returned with value None.
307
+ """
308
+ # TODO: some tests, e.g. in test_replicator, put float timestamps values
309
+ # into container db's, hence this defensive check, but in real world
310
+ # this may never happen.
311
+ if not isinstance(encoded, str):
312
+ ts = Timestamp(encoded)
313
+ return ts, ts, ts
314
+
315
+ parts = []
316
+ signs = []
317
+ pos_parts = encoded.split('+')
318
+ for part in pos_parts:
319
+ # parse time components and their signs
320
+ # e.g. x-y+z --> parts = [x, y, z] and signs = [+1, -1, +1]
321
+ neg_parts = part.split('-')
322
+ parts = parts + neg_parts
323
+ signs = signs + [1] + [-1] * (len(neg_parts) - 1)
324
+ t1 = Timestamp(parts[0])
325
+ t2 = t3 = None
326
+ if len(parts) > 1:
327
+ t2 = t1
328
+ delta = signs[1] * int(parts[1], 16)
329
+ # if delta = 0 we want t2 = t3 = t1 in order to
330
+ # preserve any offset in t1 - only construct a distinct
331
+ # timestamp if there is a non-zero delta.
332
+ if delta:
333
+ t2 = Timestamp((t1.raw + delta) * PRECISION)
334
+ elif not explicit:
335
+ t2 = t1
336
+ if len(parts) > 2:
337
+ t3 = t2
338
+ delta = signs[2] * int(parts[2], 16)
339
+ if delta:
340
+ t3 = Timestamp((t2.raw + delta) * PRECISION)
341
+ elif not explicit:
342
+ t3 = t2
343
+ return t1, t2, t3
344
+
345
+
346
+ def normalize_timestamp(timestamp):
347
+ """
348
+ Format a timestamp (string or numeric) into a standardized
349
+ xxxxxxxxxx.xxxxx (10.5) format.
350
+
351
+ Note that timestamps using values greater than or equal to November 20th,
352
+ 2286 at 17:46 UTC will use 11 digits to represent the number of
353
+ seconds.
354
+
355
+ :param timestamp: unix timestamp
356
+ :returns: normalized timestamp as a string
357
+ """
358
+ return Timestamp(timestamp).normal
359
+
360
+
361
+ EPOCH = datetime.datetime(1970, 1, 1)
362
+
363
+
364
+ def last_modified_date_to_timestamp(last_modified_date_str):
365
+ """
366
+ Convert a last modified date (like you'd get from a container listing,
367
+ e.g. 2014-02-28T23:22:36.698390) to a float.
368
+ """
369
+ return Timestamp.from_isoformat(last_modified_date_str)
370
+
371
+
372
+ def normalize_delete_at_timestamp(timestamp, high_precision=False):
373
+ """
374
+ Format a timestamp (string or numeric) into a standardized
375
+ xxxxxxxxxx (10) or xxxxxxxxxx.xxxxx (10.5) format.
376
+
377
+ Note that timestamps less than 0000000000 are raised to
378
+ 0000000000 and values greater than November 20th, 2286 at
379
+ 17:46:39 UTC will be capped at that date and time, resulting in
380
+ no return value exceeding 9999999999.99999 (or 9999999999 if
381
+ using low-precision).
382
+
383
+ This cap is because the expirer is already working through a
384
+ sorted list of strings that were all a length of 10. Adding
385
+ another digit would mess up the sort and cause the expirer to
386
+ break from processing early. By 2286, this problem will need to
387
+ be fixed, probably by creating an additional .expiring_objects
388
+ account to work from with 11 (or more) digit container names.
389
+
390
+ :param timestamp: unix timestamp
391
+ :returns: normalized timestamp as a string
392
+ """
393
+ fmt = '%016.5f' if high_precision else '%010d'
394
+ return fmt % min(max(0, float(timestamp)), 9999999999.99999)
395
+
396
+
397
+ if sys.version_info < (3, 11):
398
+ class _UTC(datetime.tzinfo):
399
+ """
400
+ A tzinfo class for datetimes that returns a 0 timedelta (UTC time)
401
+ """
402
+
403
+ def dst(self, dt):
404
+ return datetime.timedelta(0)
405
+ utcoffset = dst
406
+
407
+ def tzname(self, dt):
408
+ return 'UTC'
409
+
410
+ UTC = _UTC()
411
+ else:
412
+ from datetime import UTC