degiro-cli 0.1.0__tar.gz → 0.1.2__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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: degiro-cli
3
- Version: 0.1.0
4
- Summary: Command line tools for Degiro
3
+ Version: 0.1.2
4
+ Summary: Unofficial command line tools for Degiro
5
5
  Home-page: https://github.com/OhMajesticLama/degiro-cli
6
6
  Author-email: ohmajesticlama@gmail.com
7
7
  Project-URL: Documentation, https://ohmajesticlama.github.io/degiro-cli
@@ -11,6 +11,7 @@ Classifier: Operating System :: OS Independent
11
11
  Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Intended Audience :: End Users/Desktop
13
13
  Classifier: Topic :: Office/Business :: Financial :: Investment
14
+ Requires-Python: >=3.8
14
15
  Description-Content-Type: text/markdown
15
16
  Provides-Extra: dev
16
17
  License-File: LICENSE
@@ -30,7 +31,7 @@ orders.
30
31
  ## Installation
31
32
 
32
33
  ``` sh
33
- pip3 install degirocli
34
+ pip3 install degiro-cli
34
35
  ```
35
36
 
36
37
  ## Use
@@ -150,7 +151,7 @@ degiro-search --index 'EURO STOXX 50'
150
151
 
151
152
  ```
152
153
 
153
- ## By Index
154
+ ## Get symbol history
154
155
  ``` sh
155
156
  degiro-history --period 1m EPA.SAF
156
157
  # exchange,symbol,date,currency,open,high,low,close
@@ -13,7 +13,7 @@ orders.
13
13
  ## Installation
14
14
 
15
15
  ``` sh
16
- pip3 install degirocli
16
+ pip3 install degiro-cli
17
17
  ```
18
18
 
19
19
  ## Use
@@ -133,7 +133,7 @@ degiro-search --index 'EURO STOXX 50'
133
133
 
134
134
  ```
135
135
 
136
- ## By Index
136
+ ## Get symbol history
137
137
  ``` sh
138
138
  degiro-history --period 1m EPA.SAF
139
139
  # exchange,symbol,date,currency,open,high,low,close
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: degiro-cli
3
- Version: 0.1.0
4
- Summary: Command line tools for Degiro
3
+ Version: 0.1.2
4
+ Summary: Unofficial command line tools for Degiro
5
5
  Home-page: https://github.com/OhMajesticLama/degiro-cli
6
6
  Author-email: ohmajesticlama@gmail.com
7
7
  Project-URL: Documentation, https://ohmajesticlama.github.io/degiro-cli
@@ -11,6 +11,7 @@ Classifier: Operating System :: OS Independent
11
11
  Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Intended Audience :: End Users/Desktop
13
13
  Classifier: Topic :: Office/Business :: Financial :: Investment
14
+ Requires-Python: >=3.8
14
15
  Description-Content-Type: text/markdown
15
16
  Provides-Extra: dev
16
17
  License-File: LICENSE
@@ -30,7 +31,7 @@ orders.
30
31
  ## Installation
31
32
 
32
33
  ``` sh
33
- pip3 install degirocli
34
+ pip3 install degiro-cli
34
35
  ```
35
36
 
36
37
  ## Use
@@ -150,7 +151,7 @@ degiro-search --index 'EURO STOXX 50'
150
151
 
151
152
  ```
152
153
 
153
- ## By Index
154
+ ## Get symbol history
154
155
  ``` sh
155
156
  degiro-history --period 1m EPA.SAF
156
157
  # exchange,symbol,date,currency,open,high,low,close
@@ -11,4 +11,9 @@ degiro_cli.egg-info/PKG-INFO
11
11
  degiro_cli.egg-info/SOURCES.txt
12
12
  degiro_cli.egg-info/dependency_links.txt
13
13
  degiro_cli.egg-info/requires.txt
14
- degiro_cli.egg-info/top_level.txt
14
+ degiro_cli.egg-info/top_level.txt
15
+ degirocli/__init__.py
16
+ degirocli/helpers.py
17
+ degirocli/history.py
18
+ degirocli/login.py
19
+ degirocli/search.py
@@ -0,0 +1 @@
1
+ degirocli
File without changes
@@ -0,0 +1,61 @@
1
+ import enum
2
+ import logging
3
+ import asyncio
4
+ import collections
5
+ from typing import Callable
6
+ from typing import Iterable
7
+ from typing import Any
8
+ from typing import Sequence
9
+ from typing import Union
10
+
11
+
12
+ LOGGER = logging.getLogger('degirocli')
13
+
14
+ class ERROR_CODES(enum.IntEnum):
15
+ SESSION_EXISTS = 1
16
+
17
+ async def run_concurrent(
18
+ worker: Callable,
19
+ jobs: Iterable[Any],
20
+ *,
21
+ max_concurrents: int = 10,
22
+ ) -> Sequence[collections.abc.Awaitable]:
23
+ """
24
+ Run worker on jobs with a max of max_concurrents at the same time.
25
+
26
+ This is a helper to avoid sending too many queries at once to the producer
27
+ queried by worker.
28
+
29
+ >>> import asyncio
30
+ >>> async def worker(a, b):
31
+ ... await asyncio.sleep(1)
32
+ ... return a + b
33
+ ...
34
+ >>> awaitables = await run_concurrent(worker,
35
+ ... ((1, 2), (2, 3)))
36
+ ...
37
+ >>> res1 = await awaitables[0]
38
+ >>> res1
39
+ 3
40
+
41
+ Arguments
42
+ ---------
43
+
44
+ worker
45
+ Callable to apply to jobs.
46
+
47
+ jobs
48
+ Iterable of arguments that will be passed to worker.
49
+
50
+ max_concurrents
51
+ Maximum number of jobs that will be run concurrently.
52
+ """
53
+ LOGGER.debug('run_concurrent| worker %s', worker)
54
+ work_sem = asyncio.Semaphore(max_concurrents)
55
+
56
+ async def _wrapper(attrs):
57
+ async with work_sem:
58
+ res = await worker(*attrs)
59
+ return res
60
+
61
+ return [asyncio.create_task(_wrapper(attrs)) for attrs in jobs]
@@ -0,0 +1,200 @@
1
+ import logging
2
+ import argparse
3
+ import io
4
+ import sys
5
+ import asyncio
6
+ import csv
7
+ from typing import Tuple, Iterable
8
+ import getpass
9
+
10
+ import more_itertools
11
+ import degiroasync.api as dapi
12
+
13
+ from .helpers import run_concurrent
14
+ from .login import get_session_from_cache
15
+
16
+ LOGGER = logging.getLogger()
17
+
18
+
19
+ async def run_one(
20
+ session: dapi.Session,
21
+ exchange: str,
22
+ symbol: str,
23
+ period: dapi.PRICE.PERIOD
24
+ ):
25
+ """
26
+ """
27
+ try:
28
+ products = await dapi.search_product(
29
+ session,
30
+ by_symbol=symbol,
31
+ by_exchange=exchange,
32
+ product_type_id=dapi.PRODUCT.TYPEID.STOCK,
33
+ )
34
+ if len(products) > 1:
35
+ raise AssertionError("More than one product for {}.{}".format(
36
+ symbol,
37
+ exchange))
38
+ elif len(products) == 0:
39
+ LOGGER.error(
40
+ "No product found for exchange {}, symbol {}. Ignore.".format(
41
+ exchange, symbol
42
+ )
43
+ )
44
+ # Abort this one
45
+ return
46
+ product = products[0]
47
+ price_data: dapi.PriceSeriesTime = await dapi.get_price_data(
48
+ session,
49
+ product=product,
50
+ resolution=dapi.PRICE.RESOLUTION.PT1D,
51
+ period=period,
52
+ data_type=dapi.PRICE.TYPE.OHLC
53
+ )
54
+ except Exception as exc:
55
+ print(f"Error on symbol {exchange}.{symbol}: {exc}", file=sys.stderr)
56
+ return exc
57
+
58
+ writer = csv.writer(sys.stdout, delimiter=',')
59
+ writer.writerows(
60
+ (
61
+ exchange,
62
+ symbol,
63
+ date,
64
+ product.info.currency,
65
+ *price,
66
+ )
67
+
68
+ for date, price in zip(price_data.date, price_data.price)
69
+ )
70
+
71
+
72
+ def prepare_inputs(input_stream: Iterable[str]) -> Iterable[Tuple[str, str]]:
73
+ """
74
+ Filter & split inputs from input_stream. Ignore lines starting with #
75
+
76
+ Example
77
+ -------
78
+
79
+ >>> inputs = ['EPA.AIR', 'EPA.BNP', ' #EPA.VIE']
80
+ >>> list(prepare_inputs(inputs))
81
+ [('EPA', 'AIR'), ('EPA', 'BNP')]
82
+
83
+ """
84
+ # filter-out lines starting with #
85
+ inputs = (line.strip() for line in input_stream)
86
+ inputs = filter(lambda l: len(l) > 0, inputs)
87
+ inputs = filter(lambda l: l[0] != '#', inputs)
88
+ inputs = more_itertools.unique_everseen(inputs)
89
+ inputs = (line.split('.')[:2] for line in inputs)
90
+ return inputs
91
+
92
+
93
+ async def run(
94
+ session: dapi.Session,
95
+ *,
96
+ symbols: Iterable[str],
97
+ period: dapi.PRICE.PERIOD = dapi.PRICE.PERIOD.P1YEAR,
98
+ max_workers: int = 5
99
+ ):
100
+ """
101
+ Execute script: read symbols from standard input and output fetched price
102
+ data on standard output.
103
+ """
104
+ inputs = prepare_inputs(symbols)
105
+ inputs = ((session, *t, period) for t in inputs)
106
+ #inputs = ((session, t) for t in inputs)
107
+
108
+ queries = await run_concurrent(run_one, inputs, max_concurrents=max_workers)
109
+
110
+ # ensure all queries completed before quitting
111
+ return await asyncio.gather(*queries)
112
+
113
+
114
+ def run_cli():
115
+ handler = logging.StreamHandler()
116
+
117
+ parser = argparse.ArgumentParser(
118
+ formatter_class=argparse.RawDescriptionHelpFormatter,
119
+ description="Get history for provided symbols stocks. Symbols are read "
120
+ "from standard input, and expected in format "
121
+ "EXCHANGE.SYMBOL "
122
+ "with EXCHANGE being product exchange on DEGIRO platform. "
123
+ )
124
+ parser.add_argument(
125
+ '-p', '--period',
126
+ default='1y',
127
+ dest='period',
128
+ help="Period on which to request data. Must be one of: "
129
+ "1d, 1m, 3m, 6m, 1y, 3y, 5y, 50y"
130
+ )
131
+ parser.add_argument(
132
+ '-H',
133
+ '--no-header-row',
134
+ dest='no_headers',
135
+ default=False,
136
+ action='store_true',
137
+ required=False,
138
+ help="Do not print header line"
139
+ )
140
+ parser.add_argument(
141
+ '--debug',
142
+ default=False,
143
+ action='store_true',
144
+ dest='debug',
145
+ help="Enable debug logging."
146
+ )
147
+ parser.add_argument(
148
+ 'symbols',
149
+ nargs='*',
150
+ default=[],
151
+ help="Symbols should be in the form of EXCHANGE.PRODUCT_SYMBOL"
152
+ )
153
+ args = parser.parse_args()
154
+ logging_level = logging.ERROR
155
+ if args.debug:
156
+ logging_level = logging.DEBUG
157
+ handler.setLevel(logging_level)
158
+ LOGGER.setLevel(logging_level)
159
+ LOGGER.addHandler(handler)
160
+
161
+ print_headers = not args.no_headers
162
+
163
+ period = {
164
+ '1d': dapi.PRICE.PERIOD.P1DAY,
165
+ '1m': dapi.PRICE.PERIOD.P1MONTH,
166
+ '3m': dapi.PRICE.PERIOD.P3MONTH,
167
+ '6m': dapi.PRICE.PERIOD.P6MONTH,
168
+ '1y': dapi.PRICE.PERIOD.P1YEAR,
169
+ '3y': dapi.PRICE.PERIOD.P3YEAR,
170
+ '5y': dapi.PRICE.PERIOD.P5YEAR,
171
+ '50y': dapi.PRICE.PERIOD.P50YEAR
172
+ }[args.period.lower()]
173
+
174
+ session = get_session_from_cache()
175
+ session.update_throttling(max_requests=7, period_seconds=1)
176
+
177
+ symbols = sys.stdin
178
+ if len(args.symbols) > 0:
179
+ symbols = args.symbols
180
+
181
+ if print_headers:
182
+ writer = csv.writer(sys.stdout)
183
+ writer.writerow((
184
+ "exchange",
185
+ "symbol",
186
+ "date",
187
+ "currency",
188
+ "open",
189
+ "high",
190
+ "low",
191
+ "close",
192
+ ))
193
+ asyncio.get_event_loop().run_until_complete(
194
+ run(
195
+ session,
196
+ symbols=symbols,
197
+ period=period,
198
+ max_workers=5
199
+ )
200
+ )
@@ -0,0 +1,177 @@
1
+ import tempfile
2
+ import getpass
3
+ import functools
4
+ import multiprocessing
5
+ import stat
6
+ import asyncio
7
+ import json
8
+ import time
9
+ import pickle
10
+ import sys
11
+ import os
12
+ from pathlib import Path
13
+ from typing import Union
14
+ from typing import Optional
15
+
16
+ import degiroasync.api as dapi
17
+ import degiroasync.webapi as wapi
18
+ import degiroasync.core as dcore
19
+
20
+ from .helpers import ERROR_CODES
21
+ from .helpers import LOGGER
22
+
23
+
24
+
25
+ def get_credentials() -> dapi.Credentials:
26
+ """
27
+ Helper to get credentials for degiroasync provider.
28
+ """
29
+ DEGIRO_USERNAME = 'DEGIRO_USERNAME'
30
+ DEGIRO_PASSWORD = 'DEGIRO_PASSWORD'
31
+ DEGIRO_TOTP_SECRET = 'DEGIRO_TOTP_SECRET'
32
+ if (username := os.environ.get(DEGIRO_USERNAME)) is None:
33
+ username = input('Username: ')
34
+ if len(username.strip()) == 0:
35
+ raise AssertionError(
36
+ "{} not set and not provided".format(DEGIRO_USERNAME))
37
+ if (password := os.environ.get(DEGIRO_PASSWORD)) is None:
38
+ password = getpass.getpass('Password: ')
39
+ if len(password.strip()) == 0:
40
+ raise AssertionError("{} not set and not provided".format(DEGIRO_PASSWORD))
41
+ totp_secret: Optional[str] = os.environ.get(DEGIRO_TOTP_SECRET) or None # could be ''
42
+ totp = None
43
+ if totp_secret is None:
44
+ # Ask user for TOTP
45
+ totp: Optional[str] = input("One Time Password (Enter to ignore): ")
46
+ if len(totp.strip()) == 0:
47
+ totp = None
48
+
49
+ return dapi.Credentials(
50
+ username=username,
51
+ password=password,
52
+ totp_secret=totp_secret,
53
+ one_time_password=totp
54
+ )
55
+
56
+
57
+ def get_tmp_path():
58
+ tmpdir = Path(tempfile.gettempdir())
59
+ return tmpdir / 'degirocli'
60
+
61
+ def _get_hash(path: Union[Path, str]) -> str:
62
+ with open(path, 'rb') as fh:
63
+ fhash = hash(fh)
64
+ return fhash
65
+
66
+
67
+ def expire_path(
68
+ path: Union[Path, str],
69
+ lifetime_seconds: Union[int, float]=60*60*2,
70
+ start_time_seconds: Optional[Union[int, float]] = None,
71
+ force: bool = False,
72
+ ):
73
+ """
74
+ Will remove file at path after 'lifetime_seconds'.
75
+
76
+ Parameters
77
+ ----------
78
+
79
+ path
80
+ Path to remove when expired. By default, `path` will only be removed
81
+ if it hasn't changed since watcher started.
82
+
83
+ lifetime_seconds
84
+ File at path will be expired at start_time_seconds + lifetime_seconds.
85
+
86
+ start_time_seconds
87
+ File at path will be expired at start_time_seconds + lifetime_seconds.
88
+ Defaults to time.time().
89
+
90
+ force
91
+ If true, file at `path` will be removed even if it has changed since
92
+ this function has been called.
93
+ """
94
+ # Let's detach this process.
95
+ if os.fork() != 0: # If os.fork == 0 this is the detached process.
96
+ return
97
+ if not isinstance(path, Path):
98
+ assert isinstance(path, str)
99
+ path = Path(path)
100
+ fhash = _get_hash(path)
101
+ start_time_seconds = start_time_seconds or time.time()
102
+
103
+ while time.time() - start_time_seconds < lifetime_seconds:
104
+ time.sleep(time.time() - start_time_seconds)
105
+
106
+ tmp_path = get_tmp_path()
107
+ if tmp_path.exists():
108
+ if tmp_path.is_file():
109
+ fhash_new = _get_hash(path)
110
+ if fhash_new == fhash or force:
111
+ Path.unlink(tmp_path)
112
+ else:
113
+ print(f'{path} has changed since it has been set to expire. '
114
+ f'Abort deletion.', file=sys.stderr)
115
+ return 1
116
+ return 0
117
+
118
+
119
+ #@functools.wraps(_expire_path)
120
+ #def expire_path(*args):
121
+ # # Start event loop to
122
+ # proc = multiprocessing.Process(
123
+ # target=_expire_path,
124
+ # args=args,
125
+ # daemon=False)
126
+ # proc.start()
127
+
128
+
129
+ async def login():
130
+ tmp_path = get_tmp_path()
131
+
132
+ if tmp_path.exists():
133
+ ans = input("Existing session found, delete? (y/N)").strip().lower()
134
+ if ans in ('y', 'yes'):
135
+ tmp_path.unlink()
136
+ else:
137
+ print('Abort.')
138
+ return ERROR_CODES.SESSION_EXISTS
139
+
140
+ # Get sessionID and write it
141
+ credentials = get_credentials()
142
+ session = await wapi.login(credentials)
143
+ tmp_path.touch()
144
+ tmp_path.chmod(stat.S_IRUSR | stat.S_IWUSR)
145
+ with open(tmp_path, 'w') as fh:
146
+ json.dump({
147
+ 'version': 1,
148
+ 'format': 'degirocli',
149
+ 'session': dict(
150
+ cookies=session.cookies,
151
+ ),
152
+ }, fh)
153
+ # Delete file in 3 hour
154
+ expire_path(tmp_path, 3*60*60)
155
+
156
+ async def _get_session_from_cache() -> dapi.Session:
157
+ cache_path = get_tmp_path()
158
+ if not cache_path.exists():
159
+ raise AssertionError(
160
+ f"{cache_path} not found. Abort. Have you tried degiro-login?")
161
+ with open(cache_path, 'r') as fh:
162
+ session_d = json.load(fh)
163
+ session = dcore.SessionCore()
164
+ session._cookies = session_d['session']['cookies']
165
+ await wapi.get_config(session)
166
+ await wapi.get_client_info(session)
167
+ exc_dict = await dapi.get_dictionary(session)
168
+ return dapi.Session(session, exc_dict)
169
+
170
+ def get_session_from_cache() -> dapi.Session:
171
+ """
172
+ Helper to get Session when already logged in.
173
+ """
174
+ return asyncio.get_event_loop().run_until_complete(_get_session_from_cache())
175
+
176
+ def run_cli():
177
+ asyncio.get_event_loop().run_until_complete(login())
@@ -0,0 +1,174 @@
1
+ import sys
2
+ import asyncio
3
+ import logging
4
+ import argparse
5
+ import csv
6
+ from typing import Optional
7
+
8
+ import degiroasync.api as dapi
9
+
10
+ from .helpers import LOGGER
11
+ from .login import get_session_from_cache
12
+
13
+
14
+ async def run(
15
+ session: dapi.Session,
16
+ *,
17
+ search_txt: Optional[str],
18
+ exchange_txt: Optional[str],
19
+ country_txt: Optional[str],
20
+ index_txt: Optional[str]
21
+ ):
22
+ """
23
+ Execute script: read symbols from standard input and output fetched price
24
+ data on standard output.
25
+ """
26
+ exc_dict = session.dictionary
27
+ products = await dapi.search_product(
28
+ session=session,
29
+ by_text=search_txt,
30
+ by_exchange=exchange_txt,
31
+ by_country=country_txt,
32
+ by_index=index_txt,
33
+ )
34
+ writer = csv.writer(sys.stdout)
35
+ exc_dict: dapi.ExchangeDictionary = session.dictionary
36
+ for product in products:
37
+ exchange = exc_dict.exchange_by(id=product.info.exchange_id)
38
+ writer.writerow((
39
+ exchange.hiq_abbr,
40
+ product.info.symbol,
41
+ product.info.name,
42
+ product.info.currency,
43
+ product.info.isin,
44
+ ))
45
+
46
+
47
+
48
+ def run_cli():
49
+ handler = logging.StreamHandler()
50
+
51
+ parser = argparse.ArgumentParser(
52
+ formatter_class=argparse.RawDescriptionHelpFormatter,
53
+ description="Get history for provided symbols stocks. Symbols are read "
54
+ "from standard input, and expected in format "
55
+ "EXCHANGE.SYMBOL "
56
+ "with EXCHANGE being product exchange on DEGIRO platform. "
57
+ )
58
+ parser.add_argument(
59
+ '-t',
60
+ dest='search',
61
+ default=None,
62
+ required=False,
63
+ help="Search for SEARCH_TXT and returns products."
64
+ )
65
+ parser.add_argument(
66
+ '--exchange',
67
+ dest='exchange',
68
+ default=None,
69
+ required=False,
70
+ help="Search for products by exchange."
71
+ )
72
+ parser.add_argument(
73
+ '--country',
74
+ dest='country',
75
+ default=None,
76
+ required=False,
77
+ help="Search products on exchange places in COUNTRY"
78
+ )
79
+ parser.add_argument(
80
+ '--list-countries',
81
+ dest='list_countries',
82
+ default=False,
83
+ action='store_true',
84
+ required=False,
85
+ help="Print the available country codes on the platform."
86
+ )
87
+ parser.add_argument(
88
+ '--index',
89
+ dest='index',
90
+ default=None,
91
+ required=False,
92
+ help="Search products on exchange places in INDEX"
93
+ )
94
+ parser.add_argument(
95
+ '--list-indices',
96
+ dest='list_indices',
97
+ default=False,
98
+ action='store_true',
99
+ required=False,
100
+ help="Print the available incides codes on the platform."
101
+ )
102
+ parser.add_argument(
103
+ '-H',
104
+ '--no-header-row',
105
+ dest='no_headers',
106
+ default=False,
107
+ action='store_true',
108
+ required=False,
109
+ help="Do not print header line"
110
+ )
111
+ parser.add_argument(
112
+ '--debug',
113
+ default=False,
114
+ action='store_true',
115
+ dest='debug',
116
+ help="Enable debug logging."
117
+ )
118
+ args = parser.parse_args()
119
+
120
+ print_headers = not args.no_headers
121
+ search_txt = args.search
122
+ exchange_txt = args.exchange
123
+ country_txt = args.country
124
+ index_txt = args.index
125
+ list_indices = args.list_indices
126
+
127
+ logging_level = logging.ERROR
128
+ if args.debug:
129
+ logging_level = logging.DEBUG
130
+ handler.setLevel(logging_level)
131
+ LOGGER.setLevel(logging_level)
132
+ LOGGER.addHandler(handler)
133
+
134
+ session = get_session_from_cache()
135
+ # Lower throttling limit here as there is a good chance we'll
136
+ # bigger period limits: it seems degiro sends browser checks
137
+ # after many requests have been issued.
138
+ session.update_throttling(max_requests=10, period_seconds=1)
139
+
140
+ if args.list_countries:
141
+ for country in sorted(
142
+ session.dictionary.countries,
143
+ key=lambda x: x.name):
144
+ print(country.name)
145
+ return 0
146
+ if args.list_indices:
147
+ for index in sorted(
148
+ session.dictionary.indices,
149
+ key=lambda x: x.name):
150
+ if index.product_id is not None:
151
+ # 202307
152
+ # There is currently no support to pull products from an index
153
+ # where product_id not set by the platform, so don't show them.
154
+ print(index.name)
155
+ return 0
156
+
157
+ if print_headers:
158
+ writer = csv.writer(sys.stdout)
159
+ writer.writerow((
160
+ "exchange",
161
+ "symbol",
162
+ "name",
163
+ "currency",
164
+ "isin",
165
+ ))
166
+ asyncio.get_event_loop().run_until_complete(
167
+ run(
168
+ session,
169
+ search_txt=search_txt,
170
+ exchange_txt=exchange_txt,
171
+ country_txt=country_txt,
172
+ index_txt=index_txt
173
+ )
174
+ )
@@ -8,14 +8,14 @@ import setuptools
8
8
 
9
9
 
10
10
  if __name__ == '__main__':
11
- description = "Command line tools for Degiro"
11
+ description = "Unofficial command line tools for Degiro"
12
12
  readme_path = os.path.join(os.path.dirname(__file__), 'README.md')
13
13
  with open(readme_path, "r") as fh:
14
14
  long_description = fh.read()
15
15
 
16
16
  setuptools.setup(
17
17
  name="degiro-cli",
18
- version="0.1.0",
18
+ version="0.1.2",
19
19
  author_email="ohmajesticlama@gmail.com",
20
20
  description=description,
21
21
  long_description=long_description,
@@ -31,6 +31,7 @@ if __name__ == '__main__':
31
31
  os.path.join('bin', 'degiro-history'),
32
32
  os.path.join('bin', 'degiro-search'),
33
33
  ],
34
+ python_requires=">=3.8",
34
35
  install_requires=[
35
36
  'degiroasync >= 0.20.0',
36
37
  ],
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes