vk-scripts 1.0.5__tar.gz → 1.1__tar.gz

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 (59) hide show
  1. {vk_scripts-1.0.5 → vk_scripts-1.1}/.github/workflows/ci.yml +4 -3
  2. vk_scripts-1.1/.pylintrc +8 -0
  3. {vk_scripts-1.0.5/vk_scripts.egg-info → vk_scripts-1.1}/PKG-INFO +2 -4
  4. {vk_scripts-1.0.5 → vk_scripts-1.1}/README.md +0 -2
  5. {vk_scripts-1.0.5 → vk_scripts-1.1}/docs/mutuals.md +6 -11
  6. {vk_scripts-1.0.5 → vk_scripts-1.1}/docs/sessions.md +0 -7
  7. {vk_scripts-1.0.5 → vk_scripts-1.1}/docs/status.md +0 -7
  8. {vk_scripts-1.0.5 → vk_scripts-1.1}/pyproject.toml +1 -4
  9. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/api.py +18 -18
  10. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/last_seen.py +9 -15
  11. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/mutuals.py +23 -18
  12. vk_scripts-1.1/vk/platform.py +39 -0
  13. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/tracking/__init__.py +2 -2
  14. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/tracking/db/__init__.py +1 -1
  15. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/tracking/db/backend/__init__.py +3 -3
  16. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/tracking/db/backend/csv.py +2 -2
  17. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/tracking/db/backend/log.py +9 -22
  18. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/tracking/db/format.py +11 -11
  19. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/tracking/db/record.py +2 -2
  20. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/tracking/db/timestamp.py +2 -2
  21. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/tracking/sessions.py +62 -63
  22. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/tracking/status.py +26 -26
  23. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/user.py +38 -56
  24. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/utils/bar_chart.py +15 -15
  25. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/utils/io.py +6 -6
  26. vk_scripts-1.1/vk/version.py +16 -0
  27. {vk_scripts-1.0.5 → vk_scripts-1.1/vk_scripts.egg-info}/PKG-INFO +2 -4
  28. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk_scripts.egg-info/SOURCES.txt +2 -0
  29. vk_scripts-1.1/vk_scripts.egg-info/scm_file_list.json +50 -0
  30. vk_scripts-1.1/vk_scripts.egg-info/scm_version.json +8 -0
  31. vk_scripts-1.0.5/.pylintrc +0 -7
  32. vk_scripts-1.0.5/vk/platform.py +0 -53
  33. vk_scripts-1.0.5/vk/version.py +0 -16
  34. {vk_scripts-1.0.5 → vk_scripts-1.1}/.git-blame-ignore-revs +0 -0
  35. {vk_scripts-1.0.5 → vk_scripts-1.1}/.gitattributes +0 -0
  36. {vk_scripts-1.0.5 → vk_scripts-1.1}/.github/dependabot.yml +0 -0
  37. {vk_scripts-1.0.5 → vk_scripts-1.1}/.gitignore +0 -0
  38. {vk_scripts-1.0.5 → vk_scripts-1.1}/LICENSE.txt +0 -0
  39. {vk_scripts-1.0.5 → vk_scripts-1.1}/docs/images/date.png +0 -0
  40. {vk_scripts-1.0.5 → vk_scripts-1.1}/docs/images/hour.png +0 -0
  41. {vk_scripts-1.0.5 → vk_scripts-1.1}/docs/images/user.png +0 -0
  42. {vk_scripts-1.0.5 → vk_scripts-1.1}/docs/images/weekday.png +0 -0
  43. {vk_scripts-1.0.5 → vk_scripts-1.1}/setup.cfg +0 -0
  44. {vk_scripts-1.0.5 → vk_scripts-1.1}/test/bin/main.sh +0 -0
  45. {vk_scripts-1.0.5 → vk_scripts-1.1}/test/bin/mutuals.sh +0 -0
  46. {vk_scripts-1.0.5 → vk_scripts-1.1}/test/bin/sessions.sh +0 -0
  47. {vk_scripts-1.0.5 → vk_scripts-1.1}/test/bin/status.sh +0 -0
  48. {vk_scripts-1.0.5 → vk_scripts-1.1}/test/bin/status_once.sh +0 -0
  49. {vk_scripts-1.0.5 → vk_scripts-1.1}/test/lib/test.sh +0 -0
  50. {vk_scripts-1.0.5 → vk_scripts-1.1}/test/share/test_db.csv +0 -0
  51. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/__init__.py +0 -0
  52. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/error.py +0 -0
  53. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/tracking/db/backend/null.py +0 -0
  54. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/tracking/db/meta.py +0 -0
  55. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk/utils/__init__.py +0 -0
  56. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk_scripts.egg-info/dependency_links.txt +0 -0
  57. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk_scripts.egg-info/entry_points.txt +0 -0
  58. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk_scripts.egg-info/requires.txt +0 -0
  59. {vk_scripts-1.0.5 → vk_scripts-1.1}/vk_scripts.egg-info/top_level.txt +0 -0
@@ -18,9 +18,10 @@ jobs:
18
18
  lint:
19
19
  runs-on: ubuntu-latest
20
20
  name: Linting
21
+ continue-on-error: "${{ github.ref != 'refs/heads/master' }}"
21
22
  steps:
22
23
  - name: Checkout
23
- uses: actions/checkout@v6
24
+ uses: actions/checkout@v7
24
25
  with:
25
26
  fetch-depth: 0
26
27
  - name: Set up Python
@@ -40,7 +41,7 @@ jobs:
40
41
  name: 'Python ${{ matrix.python-version }}'
41
42
  steps:
42
43
  - name: Checkout
43
- uses: actions/checkout@v6
44
+ uses: actions/checkout@v7
44
45
  with:
45
46
  fetch-depth: 0
46
47
  - name: Set up Python
@@ -63,7 +64,7 @@ jobs:
63
64
  name: Publish
64
65
  steps:
65
66
  - name: Checkout
66
- uses: actions/checkout@v6
67
+ uses: actions/checkout@v7
67
68
  with:
68
69
  fetch-depth: 0
69
70
  - name: Set up Python
@@ -0,0 +1,8 @@
1
+ [MESSAGES CONTROL]
2
+
3
+ disable=
4
+ invalid-name,
5
+ missing-docstring,
6
+ multiple-imports,
7
+ too-few-public-methods,
8
+ too-many-public-methods,
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vk_scripts
3
- Version: 1.0.5
3
+ Version: 1.1
4
4
  Summary: Scripts to stalk people on VK
5
5
  Author-email: Egor Tensin <egor@tensin.name>
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/egor-tensin/vk-scripts
8
8
  Project-URL: Bug Tracker, https://github.com/egor-tensin/vk-scripts/issues
9
9
  Classifier: Development Status :: 5 - Production/Stable
10
- Requires-Python: >=3.4
10
+ Requires-Python: >=3.6
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE.txt
13
13
  Requires-Dist: importlib-metadata~=4.0; python_version < "3.8"
@@ -26,8 +26,6 @@ Installation
26
26
 
27
27
  pip install vk-scripts
28
28
 
29
-
30
- Python 3.4 or higher is required.
31
29
  `vk-sessions` uses [matplotlib].
32
30
 
33
31
  [matplotlib]: http://matplotlib.org/
@@ -10,8 +10,6 @@ Installation
10
10
 
11
11
  pip install vk-scripts
12
12
 
13
-
14
- Python 3.4 or higher is required.
15
13
  `vk-sessions` uses [matplotlib].
16
14
 
17
15
  [matplotlib]: http://matplotlib.org/
@@ -13,8 +13,8 @@ Usage
13
13
  For example (using made up user IDs/"screen names"),
14
14
 
15
15
  > vk-mutuals john.doe jane.doe
16
- 89497105,John,Smith
17
- 3698577,Jane,Smith
16
+ 89497105,John,Smith,john.smith
17
+ 3698577,Jane,Smith,jane.smith
18
18
 
19
19
  In the example above, both "John Doe" and "Jane Doe" are friends with "John
20
20
  Smith" and "Jane Smith", whose user IDs are 89497105 and 3698577 respectively.
@@ -27,18 +27,13 @@ You can also get a JSON document:
27
27
  {
28
28
  "uid": 89497105,
29
29
  "first_name": "John",
30
- "last_name": "Smith"
30
+ "last_name": "Smith",
31
+ "domain": "john.smith"
31
32
  },
32
33
  {
33
34
  "uid": 3698577,
34
35
  "first_name": "Jane",
35
- "last_name": "Smith"
36
+ "last_name": "Smith",
37
+ "domain": "jane.smith"
36
38
  }
37
39
  ]
38
-
39
- See also
40
- --------
41
-
42
- * [License]
43
-
44
- [License]: ../README.md#license
@@ -159,10 +159,3 @@ Known issues
159
159
  time (for example, just listening to music), they appear offline.
160
160
  Hence the 0:00:00 durations you might sometimes encounter.
161
161
  This might also happen using other clients.
162
-
163
- See also
164
- --------
165
-
166
- * [License]
167
-
168
- [License]: ../README.md#license
@@ -31,10 +31,3 @@ If you want to record when people go online/offline for further analysis using
31
31
  [vk-sessions], specify the path to a database using the `--output` parameter.
32
32
 
33
33
  [vk-sessions]: sessions.md
34
-
35
- See also
36
- --------
37
-
38
- * [License]
39
-
40
- [License]: ../README.md#license
@@ -12,7 +12,7 @@ license-files = ["LICENSE.txt"]
12
12
  dynamic = ["version"]
13
13
  authors = [{name = "Egor Tensin", email = "egor@tensin.name"}]
14
14
  readme = "README.md"
15
- requires-python = ">=3.4"
15
+ requires-python = ">=3.6"
16
16
 
17
17
  dependencies = [
18
18
  'importlib-metadata ~= 4.0 ; python_version < "3.8"',
@@ -33,6 +33,3 @@ vk-status = "vk.tracking.status:main"
33
33
  vk-mutuals = "vk.mutuals:main"
34
34
 
35
35
  [tool.setuptools_scm]
36
-
37
- [tool.black]
38
- skip-string-normalization = true
@@ -49,29 +49,29 @@ def _build_url(scheme, host, path, params=None, empty_params=False):
49
49
  else:
50
50
  raise TypeError()
51
51
  path = urllib.parse.quote(path)
52
- return urllib.parse.urlunsplit((scheme, host, path, params, ''))
52
+ return urllib.parse.urlunsplit((scheme, host, path, params, ""))
53
53
 
54
54
 
55
55
  def _join_param_values(values):
56
56
  if isinstance(values, str):
57
57
  return values
58
58
  if isinstance(values, Iterable):
59
- return ','.join(map(str, values))
59
+ return ",".join(map(str, values))
60
60
  return values
61
61
 
62
62
 
63
63
  def _join_path(base, url):
64
- if not base.endswith('/'):
65
- base += '/'
64
+ if not base.endswith("/"):
65
+ base += "/"
66
66
  return urllib.parse.urljoin(base, url)
67
67
 
68
68
 
69
- ACCESS_TOKEN = '9722cef09722cef09722cef071974b8cbe997229722cef0cbabfd816916af6c7bd37006'
69
+ ACCESS_TOKEN = "9722cef09722cef09722cef071974b8cbe997229722cef0cbabfd816916af6c7bd37006"
70
70
 
71
71
 
72
72
  class Version(Enum):
73
73
  # https://dev.vk.com/en/reference/versions
74
- V5_199 = '5.199'
74
+ V5_199 = "5.199"
75
75
  DEFAULT = V5_199
76
76
 
77
77
  def __str__(self):
@@ -79,7 +79,7 @@ class Version(Enum):
79
79
 
80
80
 
81
81
  class Language(Enum):
82
- EN = 'en'
82
+ EN = "en"
83
83
  DEFAULT = EN
84
84
 
85
85
  def __str__(self):
@@ -87,24 +87,24 @@ class Language(Enum):
87
87
 
88
88
 
89
89
  class Method(Enum):
90
- USERS_GET = 'users.get'
91
- FRIENDS_GET = 'friends.get'
90
+ USERS_GET = "users.get"
91
+ FRIENDS_GET = "friends.get"
92
92
 
93
93
  def __str__(self):
94
94
  return self.value
95
95
 
96
96
 
97
97
  class CommonParameters(Enum):
98
- ACCESS_TOKEN = 'access_token'
99
- VERSION = 'v'
100
- LANGUAGE = 'lang'
98
+ ACCESS_TOKEN = "access_token"
99
+ VERSION = "v"
100
+ LANGUAGE = "lang"
101
101
 
102
102
  def __str__(self):
103
103
  return self.value
104
104
 
105
105
 
106
106
  class API:
107
- _ROOT_URL = 'https://api.vk.com/method/'
107
+ _ROOT_URL = "https://api.vk.com/method/"
108
108
 
109
109
  _SCHEME, _HOST, _ROOT_PATH = _split_url(_ROOT_URL)
110
110
 
@@ -127,9 +127,9 @@ class API:
127
127
  with urlopen(url) as response:
128
128
  response = json.loads(response.read().decode())
129
129
  # print(response)
130
- if 'response' not in response:
130
+ if "response" not in response:
131
131
  raise vk.error.InvalidAPIResponseError(response)
132
- return response['response']
132
+ return response["response"]
133
133
  except (ConnectionError, URLError) as e:
134
134
  raise vk.error.APIConnectionError(str(e)) from e
135
135
 
@@ -138,7 +138,7 @@ class API:
138
138
  user_list = map(User.from_api_response, user_list)
139
139
  if deactivated_users:
140
140
  return user_list
141
- return [user for user in user_list if not user.is_deactivated()]
141
+ return [user for user in user_list if not user.is_deactivated]
142
142
 
143
143
  def users_get(self, user_ids, fields=(), deactivated_users=True):
144
144
  return self._filter_response_with_users(
@@ -154,6 +154,6 @@ class API:
154
154
  response = self._call_method(
155
155
  Method.FRIENDS_GET, user_id=user_id, fields=_join_param_values(fields)
156
156
  )
157
- if 'items' not in response:
157
+ if "items" not in response:
158
158
  raise vk.error.InvalidAPIResponseError(response)
159
- return self._filter_response_with_users(response['items'], deactivated_users)
159
+ return self._filter_response_with_users(response["items"], deactivated_users)
@@ -31,8 +31,8 @@ def _parse_platform(x):
31
31
 
32
32
 
33
33
  class LastSeenField(Enum):
34
- TIME = 'time'
35
- PLATFORM = 'platform'
34
+ TIME = "time"
35
+ PLATFORM = "platform"
36
36
 
37
37
  def __str__(self):
38
38
  return self.value
@@ -80,20 +80,14 @@ class LastSeen(MutableMapping):
80
80
 
81
81
  _DEFAULT_FIELD_PARSER = str
82
82
 
83
- def has_time(self):
84
- return LastSeenField.TIME in self
85
-
86
- def get_time(self):
83
+ @property
84
+ def time(self):
87
85
  return self[LastSeenField.TIME]
88
86
 
89
- def set_time(self, time):
90
- self[LastSeenField.TIME] = time
91
-
92
- def has_platform(self):
93
- return LastSeenField.PLATFORM in self
87
+ @property
88
+ def time_local(self):
89
+ return self.time.astimezone()
94
90
 
95
- def get_platform(self):
91
+ @property
92
+ def platform(self):
96
93
  return self[LastSeenField.PLATFORM]
97
-
98
- def set_platform(self, platform):
99
- self[LastSeenField.PLATFORM] = platform
@@ -14,11 +14,16 @@ from vk.user import UserField
14
14
  from vk.utils import io
15
15
  import vk.version
16
16
 
17
- _OUTPUT_USER_FIELDS = UserField.UID, UserField.FIRST_NAME, UserField.LAST_NAME
17
+ _OUTPUT_USER_FIELDS = (
18
+ UserField.UID,
19
+ UserField.FIRST_NAME,
20
+ UserField.LAST_NAME,
21
+ UserField.DOMAIN,
22
+ )
18
23
 
19
24
 
20
25
  def _query_friend_list(api, user):
21
- return api.friends_get(user.get_uid(), fields=_OUTPUT_USER_FIELDS)
26
+ return api.friends_get(user.uid, fields=_OUTPUT_USER_FIELDS)
22
27
 
23
28
 
24
29
  def _filter_user_fields(user):
@@ -52,8 +57,8 @@ class OutputSinkJSON(OutputSinkMutualFriends):
52
57
 
53
58
 
54
59
  class OutputFormat(Enum):
55
- CSV = 'csv'
56
- JSON = 'json'
60
+ CSV = "csv"
61
+ JSON = "json"
57
62
 
58
63
  def __str__(self):
59
64
  return self.value
@@ -67,14 +72,14 @@ class OutputFormat(Enum):
67
72
  return OutputSinkCSV(fd)
68
73
  if self is OutputFormat.JSON:
69
74
  return OutputSinkJSON(fd)
70
- raise NotImplementedError('unsupported output format: ' + str(self))
75
+ raise NotImplementedError(f"unsupported output format: {self}")
71
76
 
72
77
 
73
78
  def _parse_output_format(s):
74
79
  try:
75
80
  return OutputFormat(s)
76
81
  except ValueError:
77
- raise argparse.ArgumentTypeError('invalid output format: ' + s)
82
+ raise argparse.ArgumentTypeError(f"invalid output format: {s}")
78
83
 
79
84
 
80
85
  def _parse_args(args=None):
@@ -82,29 +87,29 @@ def _parse_args(args=None):
82
87
  args = sys.argv[1:]
83
88
 
84
89
  parser = argparse.ArgumentParser(
85
- description='Learn who your ex and her new boyfriend are both friends with.'
90
+ description="Learn who your ex and her new boyfriend are both friends with."
86
91
  )
87
92
 
88
93
  vk.version.add_to_arg_parser(parser)
89
94
 
90
95
  parser.add_argument(
91
- 'uids', metavar='UID', nargs='+', help='user IDs or "screen names"'
96
+ "uids", metavar="UID", nargs="+", help='user IDs or "screen names"'
92
97
  )
93
98
  parser.add_argument(
94
- '-f',
95
- '--format',
96
- dest='out_fmt',
99
+ "-f",
100
+ "--format",
101
+ dest="out_fmt",
97
102
  type=_parse_output_format,
98
103
  default=OutputFormat.CSV,
99
104
  choices=OutputFormat,
100
- help='specify output format',
105
+ help="specify output format",
101
106
  )
102
107
  parser.add_argument(
103
- '-o',
104
- '--output',
105
- metavar='PATH',
106
- dest='out_path',
107
- help='set output file path (standard output by default)',
108
+ "-o",
109
+ "--output",
110
+ metavar="PATH",
111
+ dest="out_path",
112
+ help="set output file path (standard output by default)",
108
113
  )
109
114
 
110
115
  return parser.parse_args(args)
@@ -127,5 +132,5 @@ def main(args=None):
127
132
  write_mutual_friends(**vars(_parse_args(args)))
128
133
 
129
134
 
130
- if __name__ == '__main__':
135
+ if __name__ == "__main__":
131
136
  main()
@@ -0,0 +1,39 @@
1
+ # Copyright (c) 2016 Egor Tensin <egor@tensin.name>
2
+ # This file is part of the "VK scripts" project.
3
+ # For details, see https://github.com/egor-tensin/vk-scripts.
4
+ # Distributed under the MIT License.
5
+
6
+ from enum import Enum
7
+
8
+
9
+ class Platform(Enum):
10
+ # https://dev.vk.com/en/reference/objects/user#last_seen
11
+ MOBILE = 1
12
+ IPHONE = 2
13
+ IPAD = 3
14
+ ANDROID = 4
15
+ WINDOWS_PHONE = 5
16
+ WINDOWS10 = 6
17
+ WEB = 7
18
+
19
+ @staticmethod
20
+ def from_string(s):
21
+ return Platform(int(s))
22
+
23
+ def __str__(self):
24
+ return str(self.value)
25
+
26
+ @property
27
+ def descr(self):
28
+ return f"the {_PLATFORM_DESCRIPTIONS[self]}"
29
+
30
+
31
+ _PLATFORM_DESCRIPTIONS = {
32
+ Platform.MOBILE: '"mobile" web version (or an unrecognized mobile app)',
33
+ Platform.IPHONE: "official iPhone app",
34
+ Platform.IPAD: "official iPad app",
35
+ Platform.ANDROID: "official Android app",
36
+ Platform.WINDOWS_PHONE: "official Windows Phone app",
37
+ Platform.WINDOWS10: "official Windows 10 app",
38
+ Platform.WEB: "web version (or an unrecognized app)",
39
+ }
@@ -3,6 +3,6 @@
3
3
  # See LICENSE.txt for details.
4
4
 
5
5
  __all__ = (
6
- 'sessions',
7
- 'status',
6
+ "sessions",
7
+ "status",
8
8
  )
@@ -5,4 +5,4 @@
5
5
 
6
6
  from .format import Format
7
7
 
8
- __all__ = 'format'
8
+ __all__ = ("format",)
@@ -6,7 +6,7 @@
6
6
  from . import csv, log, null
7
7
 
8
8
  __all__ = (
9
- 'csv',
10
- 'log',
11
- 'null',
9
+ "csv",
10
+ "log",
11
+ "null",
12
12
  )
@@ -43,6 +43,6 @@ class Reader(meta.Reader):
43
43
  @staticmethod
44
44
  def _record_from_row(row):
45
45
  record = Record(Timestamp.from_string(row[0]))
46
- for i in range(len(Record.FIELDS)):
47
- record[Record.FIELDS[i]] = row[i + 1]
46
+ for i, field in enumerate(Record.FIELDS):
47
+ record[field] = row[i + 1]
48
48
  return record
@@ -15,7 +15,7 @@ class Writer(meta.Writer):
15
15
  handler = logging.StreamHandler(fd)
16
16
  handler.setFormatter(
17
17
  logging.Formatter(
18
- fmt='[%(asctime)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S%z'
18
+ fmt="[%(asctime)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S%z"
19
19
  )
20
20
  )
21
21
  self._logger.addHandler(handler)
@@ -47,7 +47,7 @@ class Writer(meta.Writer):
47
47
 
48
48
  def on_initial_status(self, user):
49
49
  self._reset_last_notification()
50
- if user.is_online():
50
+ if user.is_online:
51
51
  self.info(self._format_user_is_online(user))
52
52
  else:
53
53
  self.info(self._format_user_is_offline(user))
@@ -55,7 +55,7 @@ class Writer(meta.Writer):
55
55
 
56
56
  def on_status_update(self, user):
57
57
  self._reset_last_notification()
58
- if user.is_online():
58
+ if user.is_online:
59
59
  self.info(self._format_user_went_online(user))
60
60
  else:
61
61
  self.info(self._format_user_went_offline(user))
@@ -69,39 +69,26 @@ class Writer(meta.Writer):
69
69
  self._set_last_notification(e)
70
70
  self.exception(e)
71
71
 
72
- @staticmethod
73
- def _format_user(user):
74
- if user.has_last_name():
75
- return '{} {}'.format(user.get_first_name(), user.get_last_name())
76
- return '{}'.format(user.get_first_name())
77
-
78
72
  @staticmethod
79
73
  def _format_user_is_online(user):
80
- return '{} is ONLINE.'.format(Writer._format_user(user))
74
+ return f"{user.full_name} is ONLINE."
81
75
 
82
76
  @staticmethod
83
77
  def _format_user_is_offline(user):
84
- return '{} is OFFLINE.'.format(Writer._format_user(user))
78
+ return f"{user.full_name} is OFFLINE."
85
79
 
86
80
  @staticmethod
87
81
  def _format_user_last_seen(user):
88
- return '{} was last seen at {} using {}.'.format(
89
- Writer._format_user(user),
90
- user.get_last_seen_time_local(),
91
- user.get_last_seen_platform().get_descr_text(),
92
- )
82
+ return f"{user.full_name} was last seen at {user.last_seen.time_local} using {user.last_seen.platform.descr}."
93
83
 
94
84
  @staticmethod
95
85
  def _format_user_went_online(user):
96
- return '{} went ONLINE.'.format(Writer._format_user(user))
86
+ return f"{user.full_name} went ONLINE."
97
87
 
98
88
  @staticmethod
99
89
  def _format_user_went_offline(user):
100
- return '{} went OFFLINE.'.format(Writer._format_user(user))
90
+ return f"{user.full_name} went OFFLINE."
101
91
 
102
92
  @staticmethod
103
93
  def _format_another_connection_error(e):
104
- return (
105
- 'Encountered a connection error which looks like the previous one: '
106
- + str(e)
107
- )
94
+ return f"Encountered a connection error which looks like the previous one: {e}"
@@ -12,9 +12,9 @@ from . import backend
12
12
 
13
13
 
14
14
  class Format(Enum):
15
- CSV = 'csv'
16
- LOG = 'log'
17
- NULL = 'null'
15
+ CSV = "csv"
16
+ LOG = "log"
17
+ NULL = "null"
18
18
 
19
19
  def __str__(self):
20
20
  return self.value
@@ -26,7 +26,7 @@ class Format(Enum):
26
26
  return backend.log.Writer(fd)
27
27
  if self is Format.NULL:
28
28
  return backend.null.Writer()
29
- raise NotImplementedError('unsupported database format: ' + str(self))
29
+ raise NotImplementedError(f"unsupported database format: {self}")
30
30
 
31
31
  def open_output_file(self, path=None):
32
32
  if self is Format.CSV:
@@ -35,30 +35,30 @@ class Format(Enum):
35
35
  return self._open_output_log_file(path)
36
36
  if self is Format.NULL:
37
37
  return self._open_output_database_file(None)
38
- raise NotImplementedError('unsupported database format: ' + str(self))
38
+ raise NotImplementedError(f"unsupported database format: {self}")
39
39
 
40
40
  @staticmethod
41
41
  def _open_output_log_file(path):
42
- return io.open_output_text_file(path, mode='a')
42
+ return io.open_output_text_file(path, mode="a")
43
43
 
44
44
  @staticmethod
45
45
  def _open_output_database_file(path):
46
- return io.open_output_text_file(path, mode='x')
46
+ return io.open_output_text_file(path, mode="x")
47
47
 
48
48
  def create_reader(self, fd=sys.stdin):
49
49
  if self is Format.CSV:
50
50
  return backend.csv.Reader(fd)
51
51
  if self is Format.LOG:
52
- return NotImplementedError('cannot read from a log file')
52
+ return NotImplementedError("cannot read from a log file")
53
53
  if self is Format.NULL:
54
54
  return backend.null.Reader()
55
- raise NotImplementedError('unsupported database format: ' + str(self))
55
+ raise NotImplementedError(f"unsupported database format: {self}")
56
56
 
57
57
  def open_input_file(self, path=None):
58
58
  if self is Format.CSV:
59
59
  return io.open_input_text_file(path)
60
60
  if self is Format.LOG:
61
- raise NotImplementedError('cannot read from a log file')
61
+ raise NotImplementedError("cannot read from a log file")
62
62
  if self is Format.NULL:
63
63
  return io.open_input_text_file(None)
64
- raise NotImplementedError('unsupported database format: ' + str(self))
64
+ raise NotImplementedError(f"unsupported database format: {self}")
@@ -73,7 +73,7 @@ class Record(MutableMapping):
73
73
  if isinstance(field, UserField):
74
74
  record[field] = user[field]
75
75
  elif isinstance(field, LastSeenField):
76
- record[field] = user.get_last_seen()[field]
76
+ record[field] = user.last_seen[field]
77
77
  else:
78
78
  assert False
79
79
  return record
@@ -98,5 +98,5 @@ class Record(MutableMapping):
98
98
  else:
99
99
  assert False
100
100
  if last_seen:
101
- user.set_last_seen(last_seen)
101
+ user.last_seen = last_seen
102
102
  return user
@@ -30,7 +30,7 @@ class Timestamp:
30
30
 
31
31
  @staticmethod
32
32
  def from_string(s):
33
- return Timestamp(datetime.strptime(s, '%Y-%m-%dT%H:%M:%SZ'))
33
+ return Timestamp(datetime.strptime(s, "%Y-%m-%dT%H:%M:%SZ"))
34
34
 
35
35
  def __str__(self):
36
- return self.dt.isoformat() + 'Z'
36
+ return f"{self.dt.isoformat()}Z"