Skip to content

Commit 03d79d1

Browse files
authored
Small updates (#85)
* fix timestamp issues * Updating some links to pytr-org * Fixed --last_days option * Small clean up
1 parent 74ab06f commit 03d79d1

File tree

5 files changed

+52
-81
lines changed

5 files changed

+52
-81
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
[![GitHub tag (with filter)](https://img.shields.io/github/v/tag/marzzzello/pytr?style=for-the-badge&link=https%3A%2F%2Fgithub.com%2Fmarzzzello%2Fpytr%2Ftags)](https://github.com/marzzzello/pytr/tags)
2-
[![PyPI build and publish](https://img.shields.io/github/actions/workflow/status/marzzzello/pytr/publish-pypi.yml?link=https%3A%2F%2Fgithub.com%2Fmarzzzello%2Fpytr%2Factions%2Fworkflows%2Fpublish-pypi.yml&style=for-the-badge)](https://github.com/marzzzello/pytr/actions/workflows/publish-pypi.yml)
1+
[![GitHub tag (with filter)](https://img.shields.io/github/v/tag/pytr-org/pytr?style=for-the-badge&link=https%3A%2F%2Fgithub.com%2Fmarzzzello%2Fpytr%2Ftags)](https://github.com/pytr-org/pytr/tags)
2+
[![PyPI build and publish](https://img.shields.io/github/actions/workflow/status/pytr-org/pytr/publish-pypi.yml?link=https%3A%2F%2Fgithub.com%2Fmarzzzello%2Fpytr%2Factions%2Fworkflows%2Fpublish-pypi.yml&style=for-the-badge)](https://github.com/pytr-org/pytr/actions/workflows/publish-pypi.yml)
33
[![PyPI - Version](https://img.shields.io/pypi/v/pytr?link=https%3A%2F%2Fpypi.org%2Fproject%2Fpytr%2F&style=for-the-badge)](https://pypi.org/project/pytr/)
44

55
# pytr: Use TradeRepublic in terminal
@@ -15,7 +15,7 @@ Install release from PyPI with `pipx install pytr`
1515
Or install from git repo like so:
1616

1717
```sh
18-
pipx install git+https://github.com/marzzzello/pytr
18+
pipx install git+https://github.com/pytr-org/pytr.git
1919
```
2020

2121
### Update

pytr/dl.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def __init__(
4848
self.filepaths = []
4949
self.doc_urls = []
5050
self.doc_urls_history = []
51-
self.tl = Timeline(self.tr)
51+
self.tl = Timeline(self.tr, self.since_timestamp)
5252
self.log = get_logger(__name__)
5353
self.load_history()
5454

@@ -66,7 +66,7 @@ def load_history(self):
6666
self.log.info('Created history file')
6767

6868
async def dl_loop(self):
69-
await self.tl.get_next_timeline_transactions(max_age_timestamp=self.since_timestamp)
69+
await self.tl.get_next_timeline_transactions()
7070

7171
while True:
7272
try:
@@ -75,11 +75,11 @@ async def dl_loop(self):
7575
self.log.fatal(str(e))
7676

7777
if subscription['type'] == 'timelineTransactions':
78-
await self.tl.get_next_timeline_transactions(response, max_age_timestamp=self.since_timestamp)
78+
await self.tl.get_next_timeline_transactions(response)
7979
elif subscription['type'] == 'timelineActivityLog':
80-
await self.tl.get_next_timeline_activity_log(response, max_age_timestamp=self.since_timestamp)
80+
await self.tl.get_next_timeline_activity_log(response)
8181
elif subscription['type'] == 'timelineDetailV2':
82-
await self.tl.timelineDetail(response, self, max_age_timestamp=self.since_timestamp)
82+
await self.tl.timelineDetail(response, self)
8383
else:
8484
self.log.warning(f"unmatched subscription of type '{subscription['type']}':\n{preview(response)}")
8585

pytr/main.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from importlib.metadata import version
1111
from pathlib import Path
12+
from datetime import datetime
1213

1314
from pytr.utils import get_logger, check_version, export_transactions
1415
from pytr.dl import DL
@@ -198,8 +199,7 @@ def main():
198199
if args.last_days == 0:
199200
since_timestamp = 0
200201
else:
201-
since_timestamp = (time.time() - (24 * 3600 * args.last_days)) * 1000
202-
202+
since_timestamp = datetime.now().timestamp() - (24 * 3600 * args.last_days)
203203
dl = DL(
204204
login(phone_no=args.phone_no, pin=args.pin, web=not args.applogin),
205205
args.output,

pytr/utils.py

+40-69
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def preview(response, num_lines=5):
7979
def check_version(installed_version):
8080
log = get_logger(__name__)
8181
try:
82-
r = requests.get('https://api.github.com/repos/marzzzello/pytr/tags', timeout=1)
82+
r = requests.get('https://api.github.com/repos/pytr-org/pytr/tags', timeout=1)
8383
except Exception as e:
8484
log.error('Could not check for a newer version')
8585
log.debug(str(e))
@@ -290,7 +290,7 @@ def export_transactions(input_path, output_path, lang='auto'):
290290

291291

292292
class Timeline:
293-
def __init__(self, tr):
293+
def __init__(self, tr, max_age_timestamp):
294294
self.tr = tr
295295
self.log = get_logger(__name__)
296296
self.received_detail = 0
@@ -301,21 +301,20 @@ def __init__(self, tr):
301301
self.num_timelines = 0
302302
self.timeline_events = {}
303303
self.timeline_events_iter = None
304+
self.max_age_timestamp = max_age_timestamp
304305

305-
async def get_next_timeline_transactions(self, response=None, max_age_timestamp=0):
306+
async def get_next_timeline_transactions(self, response=None):
306307
'''
307308
Get timelines transactions and save time in list timelines.
308309
Extract timeline transactions events and save them in list timeline_events
309310
310311
'''
311-
312312
if response is None:
313313
# empty response / first timeline
314-
self.log.info('Awaiting #1 timeline transactions')
314+
self.log.info('Subscribing to #1 timeline transactions')
315315
self.num_timelines = 0
316316
await self.tr.timeline_transactions()
317317
else:
318-
timestamp = response['items'][-1]['timestamp']
319318
self.num_timelines += 1
320319
# print(json.dumps(response))
321320
self.num_timeline_details += len(response['items'])
@@ -324,30 +323,37 @@ async def get_next_timeline_transactions(self, response=None, max_age_timestamp=
324323
self.timeline_events[event['id']] = event
325324

326325
after = response['cursors'].get('after')
327-
if after is None:
328-
# last timeline is reached
329-
await self.get_next_timeline_activity_log()
330-
else:
326+
327+
last_transaction_time = response['items'][-1]['timestamp'][:19]
328+
timestamp = datetime.fromisoformat(last_transaction_time).timestamp()
329+
self.log.info(
330+
f'Received #{self.num_timelines:<2} timeline transactions until {last_transaction_time}'
331+
)
332+
if (after is not None) and ((self.max_age_timestamp == 0) or (timestamp >= self.max_age_timestamp)):
331333
self.log.info(
332-
f'Received #{self.num_timelines:<2} timeline transactions, awaiting #{self.num_timelines+1:<2} timeline transactions'
334+
f'Subscribing #{self.num_timelines+1:<2} timeline transactions'
333335
)
334-
await self.tr.timeline_transactions(after)
336+
await self.tr.timeline_transactions(after)
337+
else:
338+
# last timeline is reached
339+
self.log.info('Received last relevant timeline transaction')
340+
await self.get_next_timeline_activity_log()
335341

336342

337-
async def get_next_timeline_activity_log(self, response=None, max_age_timestamp=0):
343+
async def get_next_timeline_activity_log(self, response=None):
338344
'''
339345
Get timelines acvtivity log and save time in list timelines.
340346
Extract timeline acvtivity log events and save them in list timeline_events
341347
342348
'''
343-
344349
if response is None:
345350
# empty response / first timeline
346351
self.log.info('Awaiting #1 timeline activity log')
347352
self.num_timelines = 0
348353
await self.tr.timeline_activity_log()
349354
else:
350-
timestamp = datetime.fromisoformat(response['items'][-1]['timestamp'][:19]).timestamp()
355+
last_transaction_time = response['items'][-1]['timestamp'][:19]
356+
timestamp = datetime.fromisoformat(last_transaction_time).timestamp()
351357
self.num_timelines += 1
352358
# print(json.dumps(response))
353359
self.num_timeline_details += len(response['items'])
@@ -357,49 +363,26 @@ async def get_next_timeline_activity_log(self, response=None, max_age_timestamp=
357363
self.timeline_events[event['id']] = event
358364

359365
after = response['cursors'].get('after')
360-
if after is None:
361-
# last timeline is reached
362-
self.log.info(f'Received #{self.num_timelines:<2} (last) timeline activity log')
363-
self.timeline_events_iter = iter(self.timeline_events.values())
364-
await self._get_timeline_details(5)
365-
elif max_age_timestamp != 0 and timestamp < max_age_timestamp:
366-
self.log.info(f'Received #{self.num_timelines+1:<2} timeline activity log')
367-
self.log.info('Reached last relevant timeline activity log')
368-
self.timeline_events_iter = iter(self.timeline_events.values())
369-
await self._get_timeline_details(5, max_age_timestamp=max_age_timestamp)
370-
else:
366+
self.log.info(f'Received #{self.num_timelines:<2} timeline activity log unitl {last_transaction_time}')
367+
if (after is not None) and ((self.max_age_timestamp == 0) or (timestamp >= self.max_age_timestamp)):
371368
self.log.info(
372-
f'Received #{self.num_timelines:<2} timeline activity log, awaiting #{self.num_timelines+1:<2} timeline activity log'
369+
f'Subscribing #{self.num_timelines+1:<2} timeline activity log'
373370
)
374371
await self.tr.timeline_activity_log(after)
372+
else:
373+
self.log.info('Received last relevant timeline activity log')
374+
await self._get_timeline_details()
375375

376-
async def _get_timeline_details(self, num_torequest, max_age_timestamp=0):
376+
async def _get_timeline_details(self):
377377
'''
378378
request timeline details
379379
'''
380-
while num_torequest > 0:
381-
382-
try:
383-
event = next(self.timeline_events_iter)
384-
except StopIteration:
385-
self.log.info('All timeline details requested')
386-
return False
387-
380+
for event in self.timeline_events.values():
388381
action = event.get('action')
389-
# icon = event.get('icon')
390382
msg = ''
391383
timestamp_field = datetime.fromisoformat(event['timestamp'][:19]).timestamp()
392-
if max_age_timestamp != 0 and timestamp_field > max_age_timestamp:
384+
if self.max_age_timestamp != 0 and (timestamp_field < self.max_age_timestamp):
393385
msg += 'Skip: too old'
394-
# elif icon is None:
395-
# pass
396-
# elif icon.endswith('/human.png'):
397-
# msg += 'Skip: human'
398-
# elif icon.endswith('/CashIn.png'):
399-
# msg += 'Skip: CashIn'
400-
# elif icon.endswith('/ExemptionOrderChanged.png'):
401-
# msg += 'Skip: ExemptionOrderChanged'
402-
403386
elif action is None:
404387
if event.get('actionLabel') is None:
405388
msg += 'Skip: no action'
@@ -416,11 +399,12 @@ async def _get_timeline_details(self, num_torequest, max_age_timestamp=0):
416399
self.num_timeline_details -= 1
417400
continue
418401

419-
num_torequest -= 1
420402
self.requested_detail += 1
421403
await self.tr.timeline_detail_v2(event['id'])
404+
self.log.info('All timeline details requested')
405+
return False
422406

423-
async def timelineDetail(self, response, dl, max_age_timestamp=0):
407+
async def timelineDetail(self, response, dl):
424408
'''
425409
process timeline response and request timelines
426410
'''
@@ -429,26 +413,15 @@ async def timelineDetail(self, response, dl, max_age_timestamp=0):
429413
event = self.timeline_events[response['id']]
430414
event['details'] = response
431415

432-
# when all requested timeline events are received request 5 new
433-
if self.received_detail == self.requested_detail:
434-
remaining = len(self.timeline_events)
435-
if remaining < 5:
436-
await self._get_timeline_details(remaining)
437-
else:
438-
await self._get_timeline_details(5)
439-
440-
isSavingsPlan = (event["eventType"] == "SAVINGS_PLAN_EXECUTED")
441-
442-
if not isSavingsPlan and event['subtitle'] is not None:
443-
isSavingsPlan = 'Sparplan' in event['subtitle']
444-
416+
is_savings_plan = (event["eventType"] == "SAVINGS_PLAN_EXECUTED")
417+
445418
max_details_digits = len(str(self.num_timeline_details))
446419
self.log.info(
447420
f"{self.received_detail:>{max_details_digits}}/{self.num_timeline_details}: "
448-
+ f"{event['title']} -- {event['subtitle']} [{event['eventType']}]"
421+
+ f"{event['title']} -- {event['subtitle']}"
449422
)
450423

451-
if isSavingsPlan:
424+
if is_savings_plan:
452425
subfolder = 'Sparplan'
453426
else:
454427
subfolder = {
@@ -462,14 +435,12 @@ async def timelineDetail(self, response, dl, max_age_timestamp=0):
462435
for section in response['sections']:
463436
if section['type'] != 'documents':
464437
continue
465-
466438
for doc in section['data']:
467439
try:
468-
timestamp = datetime.strptime(doc['detail'], '%d.%m.%Y').timestamp() * 1000
440+
timestamp = datetime.strptime(doc['detail'], '%d.%m.%Y').timestamp()
469441
except (ValueError, KeyError):
470-
timestamp = datetime.now().timestamp() * 1000
471-
if max_age_timestamp == 0 or max_age_timestamp < timestamp:
472-
# save all savingsplan documents in a subdirectory
442+
timestamp = datetime.now().timestamp()
443+
if self.max_age_timestamp == 0 or self.max_age_timestamp < timestamp:
473444
title = f"{doc['title']} - {event['title']}"
474445
if event['eventType'] in ["ACCOUNT_TRANSFER_INCOMING", "ACCOUNT_TRANSFER_OUTGOING", "CREDIT"]:
475446
title += f" - {event['subtitle']}"

setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ def readme():
1010

1111
setup(
1212
name='pytr',
13-
version='0.1.9',
13+
version='0.2.0',
1414
description='Use TradeRepublic in terminal',
1515
long_description=readme(),
1616
long_description_content_type='text/markdown',
17-
url='https://gitlab.com/marzzzello/pytr/',
17+
url='https://gitlab.com/pytr-org/pytr/',
1818
author='marzzzello',
1919
author_email='[email protected]',
2020
license='MIT',

0 commit comments

Comments
 (0)