twitwi 0.19.2__py3-none-any.whl → 0.21.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.
twitwi/utils.py CHANGED
@@ -10,39 +10,59 @@ from functools import partial
10
10
  from datetime import datetime
11
11
 
12
12
  from twitwi.constants import (
13
- TWEET_DATETIME_FORMAT,
13
+ SOURCE_DATETIME_FORMAT,
14
+ SOURCE_DATETIME_FORMAT_V2,
15
+ SOURCE_DATETIME_FORMAT_V3,
14
16
  FORMATTED_TWEET_DATETIME_FORMAT,
15
- TWEET_DATETIME_FORMAT_V2,
17
+ FORMATTED_FULL_DATETIME_FORMAT,
16
18
  CANONICAL_URL_KWARGS,
17
19
  CANONICAL_HOSTNAME_KWARGS,
18
20
  PRE_SNOWFLAKE_LAST_TWEET_ID,
19
21
  OFFSET_TIMESTAMP,
20
22
  )
21
23
 
22
- UTC_TIMEZONE = timezone('UTC')
24
+ UTC_TIMEZONE = timezone("UTC")
23
25
 
24
- custom_normalize_url = partial(
25
- normalize_url,
26
- **CANONICAL_URL_KWARGS
27
- )
26
+ custom_normalize_url = partial(normalize_url, **CANONICAL_URL_KWARGS)
28
27
 
29
28
  custom_get_normalized_hostname = partial(
30
- get_normalized_hostname,
31
- **CANONICAL_HOSTNAME_KWARGS
29
+ get_normalized_hostname, **CANONICAL_HOSTNAME_KWARGS
32
30
  )
33
31
 
34
32
 
35
- def get_dates(date_str, locale=None, v2=False):
33
+ def get_collection_time():
34
+ return datetime.now().strftime(FORMATTED_FULL_DATETIME_FORMAT)
35
+
36
+
37
+ def get_dates(date_str, locale=None, source="v1"):
38
+ if source not in ["v1", "v2", "bluesky"]:
39
+ raise Exception("source should be one of v1, v2 or bluesky")
40
+
36
41
  if locale is None:
37
42
  locale = UTC_TIMEZONE
38
43
 
39
- parsed_datetime = datetime.strptime(date_str, TWEET_DATETIME_FORMAT_V2 if v2 else TWEET_DATETIME_FORMAT)
44
+ try:
45
+ parsed_datetime = datetime.strptime(
46
+ date_str,
47
+ SOURCE_DATETIME_FORMAT if source == "v1" else SOURCE_DATETIME_FORMAT_V2,
48
+ )
49
+ except ValueError as e:
50
+ if source == "bluesky":
51
+ parsed_datetime = datetime.strptime(date_str, SOURCE_DATETIME_FORMAT_V3)
52
+ else:
53
+ raise e
54
+
40
55
  utc_datetime = UTC_TIMEZONE.localize(parsed_datetime)
41
56
  locale_datetime = utc_datetime.astimezone(locale)
42
57
 
43
58
  return (
44
59
  int(utc_datetime.timestamp()),
45
- datetime.strftime(locale_datetime, FORMATTED_TWEET_DATETIME_FORMAT)
60
+ datetime.strftime(
61
+ locale_datetime,
62
+ FORMATTED_FULL_DATETIME_FORMAT
63
+ if source == "bluesky"
64
+ else FORMATTED_TWEET_DATETIME_FORMAT,
65
+ ),
46
66
  )
47
67
 
48
68
 
@@ -50,17 +70,21 @@ def validate_payload_v2(payload):
50
70
  if not isinstance(payload, dict):
51
71
  return False
52
72
 
53
- if 'data' not in payload:
54
- if 'meta' in payload and 'result_count' in payload['meta'] and payload['meta']['result_count'] == 0:
73
+ if "data" not in payload:
74
+ if (
75
+ "meta" in payload
76
+ and "result_count" in payload["meta"]
77
+ and payload["meta"]["result_count"] == 0
78
+ ):
55
79
  return True
56
80
  else:
57
81
  return False
58
82
 
59
- if not isinstance(payload['data'], list):
83
+ if not isinstance(payload["data"], list):
60
84
  return False
61
85
 
62
86
  # NOTE: not sure it cannot be absent altogether
63
- if 'includes' not in payload or not isinstance(payload['includes'], dict):
87
+ if "includes" not in payload or not isinstance(payload["includes"], dict):
64
88
  return False
65
89
 
66
90
  return True
@@ -84,4 +108,7 @@ def get_dates_from_id(tweet_id, locale=None):
84
108
 
85
109
  locale_datetime = datetime.fromtimestamp(timestamp, locale)
86
110
 
87
- return (timestamp, datetime.strftime(locale_datetime, FORMATTED_TWEET_DATETIME_FORMAT))
111
+ return (
112
+ timestamp,
113
+ datetime.strftime(locale_datetime, FORMATTED_TWEET_DATETIME_FORMAT),
114
+ )
@@ -0,0 +1,435 @@
1
+ Metadata-Version: 2.4
2
+ Name: twitwi
3
+ Version: 0.21.0
4
+ Summary: A collection of Twitter-related helper functions for python.
5
+ Home-page: http://github.com/medialab/twitwi
6
+ Author: Béatrice Mazoyer, Guillaume Plique, Benjamin Ooghe-Tabanou
7
+ Author-email: guillaume.plique@sciencespo.fr
8
+ License: MIT
9
+ Keywords: twitter
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE.txt
13
+ Requires-Dist: pytz>=2019.3
14
+ Requires-Dist: ural>=0.31.1
15
+ Dynamic: author
16
+ Dynamic: author-email
17
+ Dynamic: description
18
+ Dynamic: description-content-type
19
+ Dynamic: home-page
20
+ Dynamic: keywords
21
+ Dynamic: license
22
+ Dynamic: license-file
23
+ Dynamic: requires-dist
24
+ Dynamic: requires-python
25
+ Dynamic: summary
26
+
27
+ [![Build Status](https://github.com/medialab/twitwi/workflows/Tests/badge.svg)](https://github.com/medialab/twitwi/actions)
28
+
29
+ # Twitwi
30
+
31
+ A collection of Twitter & Bluesky related helper functions for Python intended to preprocess JSON payloads from the platforms' APIs and return "normalized" flat versions in order to cleanup and optimize some fields before storing them into consistent tabular formats.
32
+
33
+ A description of the "normalized" fields [for Twitter is available here](twitwi/constants.py) and [for Bluesky there](twitwi/bluesky/types.py).
34
+
35
+ It also provides a few convenient tools to anonymize tweets data, convert it into other formats, or extract datetime/timestamp from Twitter IDs (without querying it to Twitter).
36
+
37
+ ## Installation
38
+
39
+ You can install `twitwi` with pip with the following command:
40
+
41
+ ```
42
+ pip install twitwi
43
+ ```
44
+
45
+ ## Requirements
46
+
47
+ - Python 3.8 +
48
+ - [pytz](https://pypi.org/project/pytz/) (for timezones handling)
49
+ - [ural](https://github.com/medialab/ural?tab=readme-ov-file#ural) (for urls cleaning)
50
+
51
+ ## Usage
52
+
53
+ **Bluesky (within `twitwi.bluesky`)**
54
+
55
+ *Normalization functions*
56
+
57
+ * [normalize_profile](#normalize_profile)
58
+ * [normalize_post](#normalize_post)
59
+
60
+ *Formatting functions*
61
+
62
+ * [transform_profile_into_csv_dict](#transform_profile_into_csv_dict)
63
+ * [transform_post_into_csv_dict](#transform_post_into_csv_dict)
64
+ * [format_profile_as_csv_row](#format_profile_as_csv_row)
65
+ * [format_post_as_csv_row](#format_post_as_csv_row)
66
+
67
+ *Useful constants (under `twitwi.bluesky.constants`)*
68
+
69
+ * [PROFILE_FIELDS](#profile_fields)
70
+ * [POST_FIELDS](#post_fields)
71
+
72
+ *Examples*
73
+
74
+ ```python
75
+
76
+ # Working with Blueksy posts payloads coming directly from the API, either from clasical payloads,
77
+ # for instance via the routes app.bsky.feed.getPosts https://docs.bsky.app/docs/api/app-bsky-feed-get-posts
78
+ # or app.bsky.feed.searchPosts https://docs.bsky.app/docs/api/app-bsky-feed-search-posts
79
+ # or from feeds payloads, such as the route app.bsky.feed.getAuthorFeed https://docs.bsky.app/docs/api/app-bsky-feed-get-author-feed
80
+
81
+ from twitwi.bluesky import normalize_post
82
+
83
+ normalized_posts = []
84
+ for post_data in posts_payload_from_API:
85
+ normalized_posts.append(normalize_post(post_data))
86
+
87
+ # Or to convert found datetimes into a local chosen timezone
88
+ from pytz import timezone
89
+ paris_tz = timezone('Europe/Paris')
90
+ normalized_posts.append(normalize_post(post_data, locale=paris_tz))
91
+
92
+ # Or to also produce full metadata for other posts embedded such as quotes or posts answered to:
93
+ normalized_posts += normalize_post(post_data, extract_referenced_posts=True) # Returns a list of dicts
94
+
95
+ # Then, saving normalized profiles into a CSV using DictWriter:
96
+
97
+ from csv import DictWriter
98
+ from twitwi.bluesky.constants import POST_FIELDS
99
+ from twitwi.bluesky import transform_post_into_csv_dict
100
+
101
+ with open("normalized_bluesky_posts.csv", "w") as f:
102
+ w = csv.DictWriter(f, fieldnames=POST_FIELDS)
103
+ w.writeheader()
104
+ for p in normalized_posts:
105
+ transform_post_into_csv_dict(p) # The function returns nothing, p has been mutated
106
+ w.writerow(p)
107
+
108
+ # Or using the basic CSV writer:
109
+
110
+ from csv import writer
111
+ from twitwi.bluesky import format_post_as_csv_row
112
+
113
+ with open("normalized_bluesky_posts.csv", "w") as f:
114
+ w = csv.writer(f)
115
+ w.writerow(POST_FIELDS)
116
+ for p in normalized_posts:
117
+ w.writerow(format_post_as_csv_row(p))
118
+
119
+
120
+ # Similarily, working with Bluesky user profiles coming directly from the API
121
+ # for instance via the route app.bsky.actor.getProfiles https://docs.bsky.app/docs/api/app-bsky-actor-get-profiles
122
+
123
+ from twitwi.bluesky import normalize_profile
124
+
125
+ normalized_profiles = []
126
+ for profile_data in profiles_payload_from_API:
127
+ normalized_profiles.append(normalize_profile(profile_data))
128
+
129
+ # Or in the local timezone
130
+ normalized_profiles.append(normalize_profile(profile_payload, locale=paris_tz))
131
+
132
+ # Then saving as a CSV with csv.writer or csv.DictWriter similarily:
133
+
134
+ from twitwi.bluesky.constants import PROFILE_FIELDS
135
+ from twitwi.bluesky import transform_profile_into_csv_dict, format_profile_as_csv_row
136
+
137
+ with open("normalized_bluesky_profiles.csv", "w") as f:
138
+ w = csv.DictWriter(f, fieldnames=PROFILE_FIELDS)
139
+ w.writeheader()
140
+ for p in normalized_profiles:
141
+ transform_profile_into_csv_dict(p) # The function returns nothing, p has been mutated
142
+ w.writerow(p)
143
+
144
+ with open("normalized_bluesky_profiles.csv", "w") as f:
145
+ w = csv.writer(f)
146
+ w.writerow(PROFILE_FIELDS)
147
+ for p in normalized_profiles:
148
+ w.writerow(format_profile_as_csv_row(p))
149
+ ```
150
+
151
+
152
+ **Twitter (within `twitwi`)**
153
+
154
+ *Normalization functions*
155
+
156
+ * [normalize_user](#normalize_user)
157
+ * [normalize_tweet](#normalize_tweet)
158
+ * [normalize_tweets_payload_v2](#normalize_tweets_payload_v2)
159
+
160
+ *Formatting functions*
161
+
162
+ * [transform_user_into_csv_dict](#transform_user_into_csv_dict)
163
+ * [transform_tweet_into_csv_dict](#transform_tweet_into_csv_dict)
164
+ * [format_user_as_csv_row](#format_user_as_csv_row)
165
+ * [format_tweet_as_csv_row](#format_tweet_as_csv_row)
166
+ * [apply_tcat_format](#apply_tcat_format)
167
+
168
+ *Useful constants (under `twitwi.constants`)*
169
+
170
+ * [USER_FIELDS](#user_fields)
171
+ * [TWEET_FIELDS](#tweet_fields)
172
+
173
+ *Extra functions*
174
+
175
+ * [anonymize_normalized_tweet](#anonymize_normalized_tweet)
176
+ * [get_timestamp_from_id](#get_timestamp_from_id)
177
+ * [get_dates_from_id](#get_dates_from_id)
178
+
179
+
180
+ ### normalize_profile
181
+
182
+ Function taking a nested dict describing a user profile from Bluesky's JSON payload and returning a flat "normalized" dict composed of all [PROFILE_FIELDS](#profile_fields) keys.
183
+
184
+ Will return datetimes as UTC but can take an optional second `locale` argument as a [`pytz`](https://pypi.org/project/pytz/) string timezone.
185
+
186
+ *Arguments*
187
+
188
+ * **data** *(dict)*: user profile data payload coming from Bluesky API.
189
+ * **locale** *(pytz.timezone as str, optional)*: timezone used to convert dates. If not given, will default to UTC.
190
+
191
+ ### normalize_post
192
+
193
+ Function taking a nested dict describing a post from Bluesky's JSON payload and returning a flat "normalized" dict composed of all [POST_FIELDS](#post_fields) keys.
194
+
195
+ Will return datetimes as UTC but can take an optional last `locale` argument as a `pytz` string timezone.
196
+
197
+ When setting `extract_referenced_posts` to `True` it will instead return a list of dicts including the desired post as well as each referenced ones such as quoted posts and potentially parent posts of a conversation when the data comes from a Bluesky `feed` payload.
198
+
199
+ *Arguments*
200
+
201
+ * **payload** *(dict)*: post or feed data payload coming from Bluesky API.
202
+ * **locale** *(pytz.timezone as str, optional)*: timezone used to convert dates. If not given, will default to UTC.
203
+ * **extract_referenced_posts** *(bool, optional)*: whether to return in the output, in addition to the post to be normalized, also normalized data for each other referenced posts found in the payload data (including potentially other quoted posts as well as the parent and root posts of a thread if the post comes as an answer to another one). If `False`, the function will return a `dict`, if `True` a `list` of `dict`. Defaults to `False`.
204
+ * **collection_source** *(string, optional)*: An optional information to add within the `collected_via` field of the normalized post to indicate whence it was collected.
205
+
206
+ ### transform_profile_into_csv_dict
207
+
208
+ Function transforming (i.e. mutating, so beware) a given normalized Bluesky profile into a suitable dict able to be written by a `csv.DictWriter` as a row.
209
+
210
+ Will convert list elements of the normalized data into a string with all elements separated by the `|` character, which can be changed using an optional `plural_separator` argument.
211
+
212
+ ### transform_post_into_csv_dict
213
+
214
+ Function transforming (i.e. mutating, so beware) a given normalized Bluesky post into a suitable dict able to be written by a `csv.DictWriter` as a row.
215
+
216
+ Will convert list elements of the normalized data into a string with all elements separated by the `|` character, which can be changed using an optional `plural_separator` argument.
217
+
218
+ ### format_profile_as_csv_row
219
+
220
+ Function formatting the given normalized Bluesky profile as a list able to be written by a `csv.writer` as a row in the order of [PROFILE_FIELDS](#profile_fields) (which can therefore be used as header row of the CSV).
221
+
222
+ Will convert list elements of the normalized data into a string with all elements separated by the `|` character, which can be changed using an optional `plural_separator` argument.
223
+
224
+ ### format_post_as_csv_row
225
+
226
+ Function formatting the given normalized tBluesky post as a list able to be written by a `csv.writer` as a row in the order of [POST_FIELDS](#post_fields) (which can therefore be used as header row of the CSV).
227
+
228
+ Will convert list elements of the normalized data into a string with all elements separated by the `|` character, which can be changed using an optional `plural_separator` argument.
229
+
230
+ ### PROFILE_FIELDS
231
+
232
+ List of a Bluesky user profile's normalized field names. Useful to declare headers with csv writers.
233
+
234
+ ### POST_FIELDS
235
+
236
+ List of a Bluesky post's normalized field names. Useful to declare headers with csv writers.
237
+
238
+ ### normalize_user
239
+
240
+ Function taking a nested dict describing a user from Twitter's JSON payload and returning a flat "normalized" dict composed of all [USER_FIELDS](#user_fields) keys.
241
+
242
+ Will return datetimes as UTC but can take an optional second `locale` argument as a [`pytz`](https://pypi.org/project/pytz/) string timezone.
243
+
244
+ *Arguments*
245
+
246
+ * **data** *(dict)*: user profile data payload coming from Twitter API v1.1 or v2.
247
+ * **locale** *(pytz.timezone as str, optional)*: timezone used to convert dates. If not given, will default to UTC.
248
+ * **pure** *(bool, optional)*: whether to allow the function to mutate its original `data` argument. Defaults to `True`.
249
+
250
+ ### normalize_tweet
251
+
252
+ Function taking a nested dict describing a tweet from Twitter's JSON payload (API v1.1) and returning a flat "normalized" dict composed of all [TWEET_FIELDS](#tweet_fields) keys.
253
+
254
+ Will return datetimes as UTC but can take an optional last `locale` argument as a `pytz` string timezone.
255
+
256
+ When setting `extract_referenced_posts` to `True` it will instead return a list of dicts including the desired tweet as well as each referenced ones such as quoted or retweeted tweets.
257
+
258
+ *Arguments*
259
+
260
+ * **tweet** *(dict)*: tweet data payload coming from Twitter API v1.1.
261
+ * **locale** *(pytz.timezone as str, optional)*: timezone used to convert dates. If not given, will default to UTC.
262
+ * **extract_referenced_posts** *(bool, optional)*: whether to return in the output, in addition to the tweet to be normalized, also normalized data for each other referenced tweets found in the payload data (including retweeted and quoted tweets). If `False`, the function will return a `dict`, if `True` a `list` of `dict`. Defaults to `False`.
263
+ * **collection_source** *(string, optional)*: An optional information to add within the `collected_via` field of the normalized tweet to indicate whence it was collected.
264
+
265
+ ### normalize_tweets_payload_v2
266
+
267
+ Function taking an entire tweets JSON payload from Twitter API v2 and returning a list of all contained tweets formatted as flat "normalized" dicts composed of all [TWEET_FIELDS](#tweet_fields) keys.
268
+
269
+ Will return datetimes as UTC but can take an optional last `locale` argument as a `pytz` string timezone.
270
+
271
+ When setting `extract_referenced_posts` to `True` it will instead return a list of dicts including the desired tweets as well as each referenced ones such as quoted or retweeted tweets.
272
+
273
+ *Arguments*
274
+
275
+ * **payload** *(dict)*: tweets data payload coming from Twitter API v2.
276
+ * **locale** *(pytz.timezone, optional)*: timezone used to convert dates. If not given, will default to UTC.
277
+ * **extract_referenced_tweets** *(bool, optional)*: whether to return in the output, in addition to the tweet to be normalized, also normalized data for each other referenced tweets found in the payload data (including retweeted and quoted tweets).
278
+ * **collection_source** *(string, optional)*: An optional information to add within the `collected_via` field of the normalized tweet to indicate whence it was collected.
279
+
280
+ ```python
281
+ from twitwi import normalize_tweets_payload_v2
282
+
283
+ # Normalizing an entire tweets payload to extract a list of tweets
284
+ normalize_tweets_payload_v2(payload)
285
+
286
+ # Normalizing an entire tweets payload to extract a list of tweets
287
+ # as well as the referenced tweets (quoted, retweeted, etc.)
288
+ normalize_tweets_payload_v2(payload, extract_referenced_tweets=True)
289
+
290
+ # Converting found dates to a chosen timezone
291
+ from pytz import timezone
292
+ paris_tz = timezone('Europe/Paris')
293
+
294
+ normalize_tweets_payload_v2(payload, locale=paris_tz)
295
+ ```
296
+
297
+ ### transform_user_into_csv_dict
298
+
299
+ Function transforming (i.e. mutating, so beware) a given normalized Twitter user into a suitable dict able to be written by a `csv.DictWriter` as a row.
300
+ Will convert list elements of the normalized data into a string with all elements separated by the `|` character, which can be changed using an optional `plural_separator` argument.
301
+
302
+ ```python
303
+ from twitwi import transform_user_into_csv_dict
304
+
305
+ # The function returns nothing, `normalized_user` has been mutated
306
+ transform_user_into_csv_dict(normalized_user)
307
+ ```
308
+
309
+ ### transform_tweet_into_csv_dict
310
+
311
+ Function transforming (i.e. mutating, so beware) a given normalized tweet into a suitable dict able to be written by a `csv.DictWriter` as a row.
312
+
313
+ Will convert list elements of the normalized data into a string with all elements separated by the `|` character, which can be changed using an optional `plural_separator` argument.
314
+
315
+ ```python
316
+ from twitwi import transform_tweet_into_csv_dict
317
+
318
+ # The function returns nothing, `normalized_tweet` has been mutated
319
+ transform_tweet_into_csv_dict(normalized_tweet)
320
+ ```
321
+
322
+ ### format_user_as_csv_row
323
+
324
+ Function formatting the given normalized Twitter user as a list able to be written by a `csv.writer` as a row.
325
+
326
+ Will convert list elements of the normalized data into a string with all elements separated by the `|` character, which can be changed using an optional `plural_separator` argument.
327
+
328
+ ```python
329
+ from twitwi import format_user_as_csv_row
330
+
331
+ row = format_user_as_csv_row(normalized_user)
332
+ ```
333
+
334
+ ### format_tweet_as_csv_row
335
+
336
+ Function formatting the given normalized tweet as a list able to be written by a `csv.writer` as a row.
337
+
338
+ Will convert list elements of the normalized data into a string with all elements separated by the `|` character, which can be changed using an optional `plural_separator` argument.
339
+
340
+ ```python
341
+ from twitwi import format_tweet_as_csv_row
342
+
343
+ row = format_tweet_as_csv_row(normalized_tweet)
344
+ ```
345
+
346
+ ### apply_tcat_format
347
+
348
+ Function taking a normalized tweet and returning a new dict with keys adjusted to correspond to [DMI's TCAT](https://github.com/digitalmethodsinitiative/dmi-tcat) format.
349
+
350
+ ```python
351
+ from twitwi import apply_tcat_format
352
+
353
+ tweet_tcat = apply_tcat_format(normalized_tweet)
354
+ ```
355
+
356
+ ### USER_FIELDS
357
+
358
+ List of a Twitter user profile's field names. Useful to declare headers with csv writers:
359
+
360
+ ```python
361
+ from twitwi.constants import USER_FIELDS
362
+
363
+ # Using csv.writer
364
+ w = csv.writer(f)
365
+ w.writerow(USER_FIELDS)
366
+
367
+ # Using csv.DictWriter
368
+ w = csv.DictWriter(f, fieldnames=USER_FIELDS)
369
+ w.writeheader()
370
+ ```
371
+
372
+ ### TWEET_FIELDS
373
+
374
+ List of a tweet's field names. Useful to declare headers with csv writers:
375
+
376
+ ```python
377
+ from twitwi.constants import TWEET_FIELDS
378
+
379
+ # Using csv.writer
380
+ w = csv.writer(f)
381
+ w.writerow(TWEET_FIELDS)
382
+
383
+ # Using csv.DictWriter
384
+ w = csv.DictWriter(f, fieldnames=TWEET_FIELDS)
385
+ w.writeheader()
386
+ ```
387
+
388
+ ### anonymize_normalized_tweet
389
+
390
+ Function taking a normalized tweet and mutating it by editing the text and removing all metadata related to the tweet's author user.
391
+
392
+ Note that the tweet's ID as well as other users screennames mentioned in the tweet are kept and could require extra processing depending on the use cases.
393
+
394
+ ```python
395
+ from twitwi import anonymize_normalized_tweet
396
+
397
+ # The function returns nothing, `normalized_tweet` has been mutated
398
+ anonymize_normalized_tweet(normalized_tweet)
399
+ ```
400
+
401
+ ### get_timestamp_from_id
402
+
403
+ Function taking a tweet ID and producing from it the UTC UNIX timestamp of when the tweet was posted.
404
+
405
+ This relies on the Snowflake format used by Twitter to generate tweets IDs, which builds IDs on top of the actual timestamp when a tweet was submitted.
406
+
407
+ Will only work for tweets with an ID greater than 29700859247, which is the first ID from which the Snowflake algorithm was implemented.
408
+
409
+ ```python
410
+ from twitwi import get_timestamp_from_id
411
+
412
+ timestamp = get_timestamp_from_id(tweet_ID)
413
+ ```
414
+
415
+ ### get_dates_from_id
416
+
417
+ Function taking a tweet ID and producing from it the datetime when the tweet was posted.
418
+
419
+ This relies on the Snowflake format used by Twitter to generate tweets IDs, which builds IDs on top of the actual timestamp when a tweet was submitted.
420
+
421
+ Will only work for tweets with an ID greater than 29700859247, which is the first ID from which the Snowflake algorithm was implemented.
422
+
423
+ The function can also take an optional `locale` argument as a [`pytz`](https://pypi.org/project/pytz/) string timezone.
424
+
425
+ ```python
426
+ from twitwi import get_dates_from_id
427
+
428
+ date_time = get_dates_from_id(tweet_ID)
429
+
430
+ # Or converting to a chosen timezone
431
+ from pytz import timezone
432
+ paris_tz = timezone('Europe/Paris')
433
+
434
+ date_time = get_dates_from_id(tweet_ID, locale=paris_tz)
435
+ ```
@@ -0,0 +1,22 @@
1
+ test/bluesky/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ test/bluesky/formatters_test.py,sha256=PUEFGdp6fUpNhiWaoEC1-CXfX9L3jPDYcAjoOZ_DiXQ,3296
3
+ test/bluesky/normalizers_test.py,sha256=hYIAM53RYHxxw1YsJ9kJMn8TljnttpczqRpYHyZezAo,4281
4
+ twitwi/__init__.py,sha256=y0bAx9gE3THtlWE1YpXDIhGwqJ5_I8DCStWyyiiXJkw,1095
5
+ twitwi/anonymizers.py,sha256=nkl6HL1BWLz00wJ060XSbqjN5JF8pvcpEPnRXt70TUY,1588
6
+ twitwi/constants.py,sha256=yQFgqe6yFyKcFTGKB2jBja5khGvPR0QZed_2AAl-MmM,16050
7
+ twitwi/exceptions.py,sha256=OCIDagu2ErDyOGWunRBCK3O62TnzFpIMQ9gS8l9EALQ,696
8
+ twitwi/formatters.py,sha256=yn14AsrGAUw8rShOnYJvoMbzdWpfTeSs0P0ZPNTwhLU,3142
9
+ twitwi/normalizers.py,sha256=CWUK-XwhcEjLDjWH_qb6E03WZKsbIcwiRAVUjwXKQho,28438
10
+ twitwi/utils.py,sha256=HutQFBA_FInOfodQCrCAppFYHsUMy5RihPMJTAZxpH4,3051
11
+ twitwi/bluesky/__init__.py,sha256=7ITVm1bWE9p0HCWcUs2iOxwKQXK8NEzrvp9hXaicrp0,445
12
+ twitwi/bluesky/constants.py,sha256=CSxNZGYlZvlZ0IMYpP8tVTO6AnzoTmGDFh185hs8n88,473
13
+ twitwi/bluesky/formatters.py,sha256=r8MKOpU8z024z9DQ8-tlKjzaB92T1Su2IjcFV5hjfXg,731
14
+ twitwi/bluesky/normalizers.py,sha256=FslQs73pOhfVYsGVZdUc4548htMAcduX8JfGyX36lrY,24415
15
+ twitwi/bluesky/types.py,sha256=PqWTDIy47I4OjapAiYBeNQBI3cuXgFg0eREXJ0VcUTI,12288
16
+ twitwi/bluesky/utils.py,sha256=1beoDdt4dtIJ6OPxNLSkyt4D3H5ZszKvZWsv1EYEzvs,3235
17
+ twitwi-0.21.0.dist-info/licenses/LICENSE.txt,sha256=Ddg_PcGnl0qd2167o2dheCjE_rCZJOoBxjJnJhhOpX4,1099
18
+ twitwi-0.21.0.dist-info/METADATA,sha256=AhyvMJK02sZDLnnJR8sXeiDI2ncynVwNqXKmKw1t57s,17979
19
+ twitwi-0.21.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ twitwi-0.21.0.dist-info/top_level.txt,sha256=TaKyGU7j_EVbP5KI0UD6qjbaKv2Qn0OrkfUQ29a04kg,12
21
+ twitwi-0.21.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
22
+ twitwi-0.21.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.2.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5