degiro-cli 0.1.2__tar.gz → 0.1.4__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,27 +1,41 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: degiro-cli
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Unofficial command line tools for Degiro
5
- Home-page: https://github.com/OhMajesticLama/degiro-cli
6
- Author-email: ohmajesticlama@gmail.com
5
+ Author-email: ohmajesticlama <ohmajesticlama@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/OhMajesticLama/degiro-cli
7
8
  Project-URL: Documentation, https://ohmajesticlama.github.io/degiro-cli
9
+ Keywords: degiro,cli,finance,investment
8
10
  Classifier: Programming Language :: Python :: 3
9
11
  Classifier: License :: OSI Approved :: MIT License
10
12
  Classifier: Operating System :: OS Independent
11
- Classifier: Development Status :: 4 - Beta
13
+ Classifier: Development Status :: 5 - Production/Stable
12
14
  Classifier: Intended Audience :: End Users/Desktop
13
15
  Classifier: Topic :: Office/Business :: Financial :: Investment
14
16
  Requires-Python: >=3.8
15
17
  Description-Content-Type: text/markdown
16
- Provides-Extra: dev
17
18
  License-File: LICENSE
19
+ Requires-Dist: degiroasync>=1.1.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7.0.1; extra == "dev"
22
+ Requires-Dist: coverage>=6.3; extra == "dev"
23
+ Requires-Dist: flake8>=4.0.1; extra == "dev"
24
+ Requires-Dist: mypy>=0.931; extra == "dev"
25
+ Requires-Dist: build>=0.7.0; extra == "dev"
26
+ Requires-Dist: twine>=3.8.0; extra == "dev"
27
+ Requires-Dist: sphinx>=4.4.0; extra == "dev"
28
+ Requires-Dist: sphinx_rtd_theme>=1.0.0; extra == "dev"
29
+ Requires-Dist: myst-parser>=0.17.0; extra == "dev"
30
+ Requires-Dist: ipython; extra == "dev"
31
+ Requires-Dist: ipdb; extra == "dev"
32
+ Dynamic: license-file
18
33
 
19
34
  # DegiroAsync Command Line Interface
20
35
 
21
36
  ## Introduction
22
37
 
23
- Command line tools for DEGIRO platform. It's primary purpose at this date
24
- is to:
38
+ Command line tools for DEGIRO platform. Primary features:
25
39
  - Search for STOCK products by various criteria.
26
40
  - Access STOCK products history.
27
41
 
@@ -31,7 +45,11 @@ orders.
31
45
  ## Installation
32
46
 
33
47
  ``` sh
34
- pip3 install degiro-cli
48
+ # With [uv](https://docs.astral.sh/uv/) (recommended)
49
+ uv tool install degiro-cli
50
+
51
+ # With pip
52
+ pip3 install -u degiro-cli
35
53
  ```
36
54
 
37
55
  ## Use
@@ -40,7 +58,7 @@ manipulation tools such as [CSV Kit](https://csvkit.rtfd.org/). Especially
40
58
  the `csvlook` command if you intend to just visualize outputes from command
41
59
  line.
42
60
 
43
- ## Login
61
+ ### Login
44
62
 
45
63
  ``` sh
46
64
  # Only the session id is stored, that means a new login will be required
@@ -48,6 +66,25 @@ line.
48
66
  degiro-login
49
67
  ```
50
68
 
69
+ ### Get portfolio
70
+ ```sh
71
+ degiro-portfolio
72
+ # Output:
73
+ # position_type,exchange,isin,name,currency,size,price,value
74
+ # PRODUCT,EPA,FR0000121972,Schneider Electric SE,EUR,10,276.05,2760
75
+ # PRODUCT,SCG,DE000CL5C2Q2,SG Airbus Turbo illimités Call BAR 37.08 STR 35.6901 P 10 LV 1.23,EUR,100,15.635,1563.50
76
+ # CASH,N/A,EUR,EUR,EUR,3000.00,1,3000.00
77
+
78
+ # With csvlook formatting
79
+ degiro-portfolio | csvlook --max-column-width 30
80
+ # Output:
81
+ # | position_type | exchange | isin | name | currency | size | price | value |
82
+ # | ------------- | -------- | ------------ | ------------------------------ | -------- | -------- | ------- | --------- |
83
+ # | PRODUCT | EPA | FR0000121972 | Schneider Electric SE | EUR | 10,00 | 276,050 | 2 760,00 |
84
+ # | PRODUCT | SCG | DE000CL5C2Q2 | SG Airbus Turbo illimités C... | EUR | 100,00 | 15,635 | 1 563,50 |
85
+ # | CASH | | EUR | EUR | EUR | 3 000,00 | 1,000 | 3 000,00 |
86
+ ```
87
+
51
88
  ### Find products
52
89
 
53
90
  ``` sh
@@ -76,7 +113,7 @@ degiro-search -t airbus | csvlook
76
113
  # | FRA | AIRA | Airbus SE | EUR | US0092791005 |
77
114
  ```
78
115
 
79
- ## List countries available on the platform
116
+ ### List countries available on the platform
80
117
  ``` sh
81
118
  # List country codes available on the platform
82
119
  degiro-search --list-countries
@@ -110,7 +147,7 @@ degiro-search --list-countries
110
147
  # US
111
148
  ```
112
149
 
113
- ## By Index
150
+ ### By Index
114
151
  ``` sh
115
152
  # List country codes available on the platform
116
153
  degiro-search --list-indices
@@ -151,7 +188,7 @@ degiro-search --index 'EURO STOXX 50'
151
188
 
152
189
  ```
153
190
 
154
- ## Get symbol history
191
+ ### Get symbol history
155
192
  ``` sh
156
193
  degiro-history --period 1m EPA.SAF
157
194
  # exchange,symbol,date,currency,open,high,low,close
@@ -206,11 +243,14 @@ degiro-history --period 1m EPA.SAF | csvlook
206
243
 
207
244
  ```
208
245
 
209
- ## Global Examples
246
+ ### Chain with other tools
210
247
 
248
+ degiro-cli can be chained with other command line tools to achieve more complex processing.
211
249
  ``` sh
250
+ degiro-login
212
251
  # Example command line to pull history for all stocks in a country with
213
252
  # the help of the great CLI tool csvkit
214
- country=NL; degiro-search --country $country --no-header-row | csvcut -c 1-2 | sed 's/,/./' | xargs -n 100 degiro-history -p 5y | tee -a prices.$country.csv
253
+ countr="NL"; degiro-search --country "$country" --no-header-row | csvcut -c 1-2 | sed 's/,/./' | xargs -n 100 degiro-history -p 5y | tee -a prices.$country.csv
215
254
  ```
216
255
 
256
+
@@ -1,27 +1,8 @@
1
- Metadata-Version: 2.1
2
- Name: degiro-cli
3
- Version: 0.1.2
4
- Summary: Unofficial command line tools for Degiro
5
- Home-page: https://github.com/OhMajesticLama/degiro-cli
6
- Author-email: ohmajesticlama@gmail.com
7
- Project-URL: Documentation, https://ohmajesticlama.github.io/degiro-cli
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Classifier: Development Status :: 4 - Beta
12
- Classifier: Intended Audience :: End Users/Desktop
13
- Classifier: Topic :: Office/Business :: Financial :: Investment
14
- Requires-Python: >=3.8
15
- Description-Content-Type: text/markdown
16
- Provides-Extra: dev
17
- License-File: LICENSE
18
-
19
1
  # DegiroAsync Command Line Interface
20
2
 
21
3
  ## Introduction
22
4
 
23
- Command line tools for DEGIRO platform. It's primary purpose at this date
24
- is to:
5
+ Command line tools for DEGIRO platform. Primary features:
25
6
  - Search for STOCK products by various criteria.
26
7
  - Access STOCK products history.
27
8
 
@@ -31,7 +12,11 @@ orders.
31
12
  ## Installation
32
13
 
33
14
  ``` sh
34
- pip3 install degiro-cli
15
+ # With [uv](https://docs.astral.sh/uv/) (recommended)
16
+ uv tool install degiro-cli
17
+
18
+ # With pip
19
+ pip3 install -u degiro-cli
35
20
  ```
36
21
 
37
22
  ## Use
@@ -40,7 +25,7 @@ manipulation tools such as [CSV Kit](https://csvkit.rtfd.org/). Especially
40
25
  the `csvlook` command if you intend to just visualize outputes from command
41
26
  line.
42
27
 
43
- ## Login
28
+ ### Login
44
29
 
45
30
  ``` sh
46
31
  # Only the session id is stored, that means a new login will be required
@@ -48,6 +33,25 @@ line.
48
33
  degiro-login
49
34
  ```
50
35
 
36
+ ### Get portfolio
37
+ ```sh
38
+ degiro-portfolio
39
+ # Output:
40
+ # position_type,exchange,isin,name,currency,size,price,value
41
+ # PRODUCT,EPA,FR0000121972,Schneider Electric SE,EUR,10,276.05,2760
42
+ # PRODUCT,SCG,DE000CL5C2Q2,SG Airbus Turbo illimités Call BAR 37.08 STR 35.6901 P 10 LV 1.23,EUR,100,15.635,1563.50
43
+ # CASH,N/A,EUR,EUR,EUR,3000.00,1,3000.00
44
+
45
+ # With csvlook formatting
46
+ degiro-portfolio | csvlook --max-column-width 30
47
+ # Output:
48
+ # | position_type | exchange | isin | name | currency | size | price | value |
49
+ # | ------------- | -------- | ------------ | ------------------------------ | -------- | -------- | ------- | --------- |
50
+ # | PRODUCT | EPA | FR0000121972 | Schneider Electric SE | EUR | 10,00 | 276,050 | 2 760,00 |
51
+ # | PRODUCT | SCG | DE000CL5C2Q2 | SG Airbus Turbo illimités C... | EUR | 100,00 | 15,635 | 1 563,50 |
52
+ # | CASH | | EUR | EUR | EUR | 3 000,00 | 1,000 | 3 000,00 |
53
+ ```
54
+
51
55
  ### Find products
52
56
 
53
57
  ``` sh
@@ -76,7 +80,7 @@ degiro-search -t airbus | csvlook
76
80
  # | FRA | AIRA | Airbus SE | EUR | US0092791005 |
77
81
  ```
78
82
 
79
- ## List countries available on the platform
83
+ ### List countries available on the platform
80
84
  ``` sh
81
85
  # List country codes available on the platform
82
86
  degiro-search --list-countries
@@ -110,7 +114,7 @@ degiro-search --list-countries
110
114
  # US
111
115
  ```
112
116
 
113
- ## By Index
117
+ ### By Index
114
118
  ``` sh
115
119
  # List country codes available on the platform
116
120
  degiro-search --list-indices
@@ -151,7 +155,7 @@ degiro-search --index 'EURO STOXX 50'
151
155
 
152
156
  ```
153
157
 
154
- ## Get symbol history
158
+ ### Get symbol history
155
159
  ``` sh
156
160
  degiro-history --period 1m EPA.SAF
157
161
  # exchange,symbol,date,currency,open,high,low,close
@@ -206,11 +210,14 @@ degiro-history --period 1m EPA.SAF | csvlook
206
210
 
207
211
  ```
208
212
 
209
- ## Global Examples
213
+ ### Chain with other tools
210
214
 
215
+ degiro-cli can be chained with other command line tools to achieve more complex processing.
211
216
  ``` sh
217
+ degiro-login
212
218
  # Example command line to pull history for all stocks in a country with
213
219
  # the help of the great CLI tool csvkit
214
- country=NL; degiro-search --country $country --no-header-row | csvcut -c 1-2 | sed 's/,/./' | xargs -n 100 degiro-history -p 5y | tee -a prices.$country.csv
220
+ countr="NL"; degiro-search --country "$country" --no-header-row | csvcut -c 1-2 | sed 's/,/./' | xargs -n 100 degiro-history -p 5y | tee -a prices.$country.csv
215
221
  ```
216
222
 
223
+
@@ -1,9 +1,41 @@
1
+ Metadata-Version: 2.4
2
+ Name: degiro-cli
3
+ Version: 0.1.4
4
+ Summary: Unofficial command line tools for Degiro
5
+ Author-email: ohmajesticlama <ohmajesticlama@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/OhMajesticLama/degiro-cli
8
+ Project-URL: Documentation, https://ohmajesticlama.github.io/degiro-cli
9
+ Keywords: degiro,cli,finance,investment
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Topic :: Office/Business :: Financial :: Investment
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: degiroasync>=1.1.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7.0.1; extra == "dev"
22
+ Requires-Dist: coverage>=6.3; extra == "dev"
23
+ Requires-Dist: flake8>=4.0.1; extra == "dev"
24
+ Requires-Dist: mypy>=0.931; extra == "dev"
25
+ Requires-Dist: build>=0.7.0; extra == "dev"
26
+ Requires-Dist: twine>=3.8.0; extra == "dev"
27
+ Requires-Dist: sphinx>=4.4.0; extra == "dev"
28
+ Requires-Dist: sphinx_rtd_theme>=1.0.0; extra == "dev"
29
+ Requires-Dist: myst-parser>=0.17.0; extra == "dev"
30
+ Requires-Dist: ipython; extra == "dev"
31
+ Requires-Dist: ipdb; extra == "dev"
32
+ Dynamic: license-file
33
+
1
34
  # DegiroAsync Command Line Interface
2
35
 
3
36
  ## Introduction
4
37
 
5
- Command line tools for DEGIRO platform. It's primary purpose at this date
6
- is to:
38
+ Command line tools for DEGIRO platform. Primary features:
7
39
  - Search for STOCK products by various criteria.
8
40
  - Access STOCK products history.
9
41
 
@@ -13,7 +45,11 @@ orders.
13
45
  ## Installation
14
46
 
15
47
  ``` sh
16
- pip3 install degiro-cli
48
+ # With [uv](https://docs.astral.sh/uv/) (recommended)
49
+ uv tool install degiro-cli
50
+
51
+ # With pip
52
+ pip3 install -u degiro-cli
17
53
  ```
18
54
 
19
55
  ## Use
@@ -22,7 +58,7 @@ manipulation tools such as [CSV Kit](https://csvkit.rtfd.org/). Especially
22
58
  the `csvlook` command if you intend to just visualize outputes from command
23
59
  line.
24
60
 
25
- ## Login
61
+ ### Login
26
62
 
27
63
  ``` sh
28
64
  # Only the session id is stored, that means a new login will be required
@@ -30,6 +66,25 @@ line.
30
66
  degiro-login
31
67
  ```
32
68
 
69
+ ### Get portfolio
70
+ ```sh
71
+ degiro-portfolio
72
+ # Output:
73
+ # position_type,exchange,isin,name,currency,size,price,value
74
+ # PRODUCT,EPA,FR0000121972,Schneider Electric SE,EUR,10,276.05,2760
75
+ # PRODUCT,SCG,DE000CL5C2Q2,SG Airbus Turbo illimités Call BAR 37.08 STR 35.6901 P 10 LV 1.23,EUR,100,15.635,1563.50
76
+ # CASH,N/A,EUR,EUR,EUR,3000.00,1,3000.00
77
+
78
+ # With csvlook formatting
79
+ degiro-portfolio | csvlook --max-column-width 30
80
+ # Output:
81
+ # | position_type | exchange | isin | name | currency | size | price | value |
82
+ # | ------------- | -------- | ------------ | ------------------------------ | -------- | -------- | ------- | --------- |
83
+ # | PRODUCT | EPA | FR0000121972 | Schneider Electric SE | EUR | 10,00 | 276,050 | 2 760,00 |
84
+ # | PRODUCT | SCG | DE000CL5C2Q2 | SG Airbus Turbo illimités C... | EUR | 100,00 | 15,635 | 1 563,50 |
85
+ # | CASH | | EUR | EUR | EUR | 3 000,00 | 1,000 | 3 000,00 |
86
+ ```
87
+
33
88
  ### Find products
34
89
 
35
90
  ``` sh
@@ -58,7 +113,7 @@ degiro-search -t airbus | csvlook
58
113
  # | FRA | AIRA | Airbus SE | EUR | US0092791005 |
59
114
  ```
60
115
 
61
- ## List countries available on the platform
116
+ ### List countries available on the platform
62
117
  ``` sh
63
118
  # List country codes available on the platform
64
119
  degiro-search --list-countries
@@ -92,7 +147,7 @@ degiro-search --list-countries
92
147
  # US
93
148
  ```
94
149
 
95
- ## By Index
150
+ ### By Index
96
151
  ``` sh
97
152
  # List country codes available on the platform
98
153
  degiro-search --list-indices
@@ -133,7 +188,7 @@ degiro-search --index 'EURO STOXX 50'
133
188
 
134
189
  ```
135
190
 
136
- ## Get symbol history
191
+ ### Get symbol history
137
192
  ``` sh
138
193
  degiro-history --period 1m EPA.SAF
139
194
  # exchange,symbol,date,currency,open,high,low,close
@@ -188,11 +243,14 @@ degiro-history --period 1m EPA.SAF | csvlook
188
243
 
189
244
  ```
190
245
 
191
- ## Global Examples
246
+ ### Chain with other tools
192
247
 
248
+ degiro-cli can be chained with other command line tools to achieve more complex processing.
193
249
  ``` sh
250
+ degiro-login
194
251
  # Example command line to pull history for all stocks in a country with
195
252
  # the help of the great CLI tool csvkit
196
- country=NL; degiro-search --country $country --no-header-row | csvcut -c 1-2 | sed 's/,/./' | xargs -n 100 degiro-history -p 5y | tee -a prices.$country.csv
253
+ countr="NL"; degiro-search --country "$country" --no-header-row | csvcut -c 1-2 | sed 's/,/./' | xargs -n 100 degiro-history -p 5y | tee -a prices.$country.csv
197
254
  ```
198
255
 
256
+
@@ -1,19 +1,15 @@
1
1
  LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
- setup.py
5
- bin/degiro-history
6
- bin/degiro-login
7
- bin/degiro-logout
8
- bin/degiro-portfolio
9
- bin/degiro-search
10
4
  degiro_cli.egg-info/PKG-INFO
11
5
  degiro_cli.egg-info/SOURCES.txt
12
6
  degiro_cli.egg-info/dependency_links.txt
7
+ degiro_cli.egg-info/entry_points.txt
13
8
  degiro_cli.egg-info/requires.txt
14
9
  degiro_cli.egg-info/top_level.txt
15
10
  degirocli/__init__.py
16
11
  degirocli/helpers.py
17
12
  degirocli/history.py
18
13
  degirocli/login.py
14
+ degirocli/portfolio.py
19
15
  degirocli/search.py
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ degiro-history = degirocli.history:main
3
+ degiro-login = degirocli.login:main
4
+ degiro-portfolio = degirocli.portfolio:main
5
+ degiro-search = degirocli.search:main
@@ -0,0 +1,14 @@
1
+ degiroasync>=1.1.0
2
+
3
+ [dev]
4
+ pytest>=7.0.1
5
+ coverage>=6.3
6
+ flake8>=4.0.1
7
+ mypy>=0.931
8
+ build>=0.7.0
9
+ twine>=3.8.0
10
+ sphinx>=4.4.0
11
+ sphinx_rtd_theme>=1.0.0
12
+ myst-parser>=0.17.0
13
+ ipython
14
+ ipdb
@@ -87,10 +87,10 @@ def prepare_inputs(input_stream: Iterable[str]) -> Iterable[Tuple[str, str]]:
87
87
  inputs = filter(lambda l: l[0] != '#', inputs)
88
88
  inputs = more_itertools.unique_everseen(inputs)
89
89
  inputs = (line.split('.')[:2] for line in inputs)
90
- return inputs
90
+ return inputs # type: ignore , correct type.
91
91
 
92
92
 
93
- async def run(
93
+ async def run_price_pipeline(
94
94
  session: dapi.Session,
95
95
  *,
96
96
  symbols: Iterable[str],
@@ -111,7 +111,7 @@ async def run(
111
111
  return await asyncio.gather(*queries)
112
112
 
113
113
 
114
- def run_cli():
114
+ def main():
115
115
  handler = logging.StreamHandler()
116
116
 
117
117
  parser = argparse.ArgumentParser(
@@ -190,8 +190,8 @@ def run_cli():
190
190
  "low",
191
191
  "close",
192
192
  ))
193
- asyncio.get_event_loop().run_until_complete(
194
- run(
193
+ asyncio.run(
194
+ run_price_pipeline(
195
195
  session,
196
196
  symbols=symbols,
197
197
  period=period,
@@ -1,12 +1,9 @@
1
1
  import tempfile
2
2
  import getpass
3
- import functools
4
- import multiprocessing
5
3
  import stat
6
4
  import asyncio
7
5
  import json
8
6
  import time
9
- import pickle
10
7
  import sys
11
8
  import os
12
9
  from pathlib import Path
@@ -17,10 +14,10 @@ import degiroasync.api as dapi
17
14
  import degiroasync.webapi as wapi
18
15
  import degiroasync.core as dcore
19
16
 
20
- from .helpers import ERROR_CODES
21
- from .helpers import LOGGER
17
+ from .helpers import ERROR_CODES, LOGGER
22
18
 
23
19
 
20
+ _USER_MASK = stat.S_IRUSR | stat.S_IWUSR
24
21
 
25
22
  def get_credentials() -> dapi.Credentials:
26
23
  """
@@ -55,10 +52,17 @@ def get_credentials() -> dapi.Credentials:
55
52
 
56
53
 
57
54
  def get_tmp_path():
55
+ "Return temporary path specific to current user."
58
56
  tmpdir = Path(tempfile.gettempdir())
59
- return tmpdir / 'degirocli'
60
57
 
61
- def _get_hash(path: Union[Path, str]) -> str:
58
+ # parent folder must be readable and writable by other users on multi-user systems.
59
+ module_folder = tmpdir / 'degirocli'
60
+ module_folder.mkdir(mode=0o777, exist_ok=True)
61
+
62
+ user_path = module_folder / str(os.getuid())
63
+ return user_path
64
+
65
+ def _get_hash(path: Union[Path, str]) -> int:
62
66
  with open(path, 'rb') as fh:
63
67
  fhash = hash(fh)
64
68
  return fhash
@@ -116,17 +120,7 @@ def expire_path(
116
120
  return 0
117
121
 
118
122
 
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():
123
+ async def login(*, expire_session_file: int = 3*60*60):
130
124
  tmp_path = get_tmp_path()
131
125
 
132
126
  if tmp_path.exists():
@@ -140,8 +134,21 @@ async def login():
140
134
  # Get sessionID and write it
141
135
  credentials = get_credentials()
142
136
  session = await wapi.login(credentials)
143
- tmp_path.touch()
144
- tmp_path.chmod(stat.S_IRUSR | stat.S_IWUSR)
137
+ LOGGER.debug("temporary file: %s", tmp_path)
138
+ tmp_path.touch(exist_ok=True)
139
+ # Check current user owns the file.
140
+ tmp_stat = os.stat(tmp_path)
141
+ if tmp_stat.st_uid != os.getuid():
142
+ msg = (
143
+ f"Session cache file user identifier (UID) {tmp_stat.st_uid} is"
144
+ f" different from current user UID {os.getuid()}."
145
+ "This should not happen and could be a local attack. "
146
+ "Abort."
147
+ )
148
+ LOGGER.error(msg)
149
+ return
150
+ raise AssertionError()
151
+ tmp_path.chmod(_USER_MASK)
145
152
  with open(tmp_path, 'w') as fh:
146
153
  json.dump({
147
154
  'version': 1,
@@ -151,7 +158,7 @@ async def login():
151
158
  ),
152
159
  }, fh)
153
160
  # Delete file in 3 hour
154
- expire_path(tmp_path, 3*60*60)
161
+ expire_path(tmp_path, expire_session_file)
155
162
 
156
163
  async def _get_session_from_cache() -> dapi.Session:
157
164
  cache_path = get_tmp_path()
@@ -171,7 +178,7 @@ def get_session_from_cache() -> dapi.Session:
171
178
  """
172
179
  Helper to get Session when already logged in.
173
180
  """
174
- return asyncio.get_event_loop().run_until_complete(_get_session_from_cache())
181
+ return asyncio.run(_get_session_from_cache())
175
182
 
176
- def run_cli():
177
- asyncio.get_event_loop().run_until_complete(login())
183
+ def main():
184
+ asyncio.run(login())
@@ -0,0 +1,173 @@
1
+ import sys
2
+ import asyncio
3
+ import logging
4
+ import io
5
+ import argparse
6
+ import csv
7
+ from typing import Optional, Sequence, Literal
8
+
9
+ import degiroasync.api as dapi
10
+
11
+ from .helpers import LOGGER
12
+ from .login import get_session_from_cache
13
+
14
+
15
+ async def run_portfolio_pipeline(
16
+ session: dapi.Session,
17
+ output_file: io.TextIOBase,
18
+ *,
19
+ headers: bool = True,
20
+ position_type: Optional[str] = None,
21
+ filter: Literal['all', 'open', 'closed'] = 'open',
22
+ ):
23
+ """
24
+ Fetch and output portfolio data.
25
+ """
26
+ positions: Sequence[dapi.Position] = await dapi.get_portfolio(session, filter=filter)
27
+
28
+ exc_dict: dapi.ExchangeDictionary = session.dictionary
29
+ writer = csv.writer(output_file)
30
+ if headers:
31
+ writer.writerow((
32
+ "position_type",
33
+ "exchange",
34
+ "isin",
35
+ "name",
36
+ "currency",
37
+ "size",
38
+ "price",
39
+ "value",
40
+ ))
41
+
42
+ for position in positions:
43
+ if position_type is not None and position.position_type != position_type:
44
+ continue
45
+
46
+ exchange = "N/A"
47
+
48
+ info = position.product.info
49
+ if hasattr(info, 'exchange_id'):
50
+ # Should be the case for stock products at least.
51
+ try:
52
+ exchange_obj = exc_dict.exchange_by(id=info.exchange_id)
53
+ exchange = exchange_obj.hiq_abbr if exchange_obj else ""
54
+ except KeyError:
55
+ LOGGER.warning("Exchange id %s unknown.", info.exchange_id)
56
+ exchange = "Unknown"
57
+
58
+ LOGGER.debug("position: %s", position)
59
+ LOGGER.debug("info: %s", info)
60
+ writer.writerow((
61
+ position.position_type,
62
+ exchange,
63
+ info.isin,
64
+ info.name,
65
+ info.currency,
66
+ position.size,
67
+ position.price,
68
+ position.value,
69
+ ))
70
+
71
+
72
+ async def run_portfolio_total_pipeline(
73
+ session: dapi.Session,
74
+ output_file: io.TextIOBase
75
+ ):
76
+ """
77
+ Fetch and output total portfolio data.
78
+ """
79
+ total_portfolio = await dapi.get_portfolio_total(session)
80
+
81
+ writer = csv.writer(output_file)
82
+
83
+ # TotalPortfolio is an object with attributes, iterate over public ones
84
+ for attr_name in dir(total_portfolio):
85
+ if not attr_name.startswith('_'):
86
+ writer.writerow((attr_name, getattr(total_portfolio, attr_name)))
87
+
88
+
89
+ def main():
90
+ handler = logging.StreamHandler()
91
+
92
+ parser = argparse.ArgumentParser(
93
+ formatter_class=argparse.RawDescriptionHelpFormatter,
94
+ description="Get user portfolio from DEGIRO."
95
+ )
96
+ parser.add_argument(
97
+ '-f',
98
+ '--filter',
99
+ dest='filter',
100
+ default='open',
101
+ required=False,
102
+ help="Get either 'all', 'open' (default) or 'closed' positions only."
103
+ )
104
+ parser.add_argument(
105
+ '--total',
106
+ dest='total',
107
+ default=False,
108
+ action='store_true',
109
+ required=False,
110
+ help="Get total portfolio summary instead of individual positions"
111
+ )
112
+ parser.add_argument(
113
+ '-t',
114
+ '--type',
115
+ dest='position_type',
116
+ default=None,
117
+ required=False,
118
+ help="Filter by position type: PRODUCT or CASH"
119
+ )
120
+ parser.add_argument(
121
+ '-H',
122
+ '--no-header-row',
123
+ dest='no_headers',
124
+ default=False,
125
+ action='store_true',
126
+ required=False,
127
+ help="Do not print header line"
128
+ )
129
+ parser.add_argument(
130
+ '--debug',
131
+ default=False,
132
+ action='store_true',
133
+ dest='debug',
134
+ help="Enable debug logging."
135
+ )
136
+ args = parser.parse_args()
137
+
138
+ print_headers = not args.no_headers
139
+ position_type = args.position_type
140
+ if position_type is not None:
141
+ position_type = position_type.upper()
142
+ get_total = args.total
143
+ filter = args.filter
144
+ if get_total and filter != 'open':
145
+ print("--filter does not work with --total. Abort.")
146
+ return
147
+
148
+ logging_level = logging.WARNING
149
+ if args.debug:
150
+ logging_level = logging.DEBUG
151
+ handler.setLevel(logging_level)
152
+ LOGGER.setLevel(logging_level)
153
+ LOGGER.addHandler(handler)
154
+
155
+ output_io: io.TextIOBase = sys.stdout # type: ignore
156
+
157
+ session = get_session_from_cache()
158
+ session.update_throttling(max_requests=7, period_seconds=1)
159
+
160
+ if get_total:
161
+ if print_headers:
162
+ writer = csv.writer(sys.stdout)
163
+ writer.writerow(("key", "value"))
164
+ asyncio.run(run_portfolio_total_pipeline(session, output_io))
165
+ else:
166
+ asyncio.run(
167
+ run_portfolio_pipeline(
168
+ session,
169
+ output_file=output_io,
170
+ position_type=position_type,
171
+ headers=print_headers,
172
+ )
173
+ )
@@ -11,7 +11,7 @@ from .helpers import LOGGER
11
11
  from .login import get_session_from_cache
12
12
 
13
13
 
14
- async def run(
14
+ async def run_search_pipeline(
15
15
  session: dapi.Session,
16
16
  *,
17
17
  search_txt: Optional[str],
@@ -45,7 +45,7 @@ async def run(
45
45
 
46
46
 
47
47
 
48
- def run_cli():
48
+ def main():
49
49
  handler = logging.StreamHandler()
50
50
 
51
51
  parser = argparse.ArgumentParser(
@@ -163,8 +163,8 @@ def run_cli():
163
163
  "currency",
164
164
  "isin",
165
165
  ))
166
- asyncio.get_event_loop().run_until_complete(
167
- run(
166
+ asyncio.run(
167
+ run_search_pipeline(
168
168
  session,
169
169
  search_txt=search_txt,
170
170
  exchange_txt=exchange_txt,
@@ -0,0 +1,60 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "degiro-cli"
7
+ version = "0.1.4"
8
+ authors = [{ name = "ohmajesticlama", email = "ohmajesticlama@gmail.com" }]
9
+ description = "Unofficial command line tools for Degiro"
10
+ readme = "README.md"
11
+ requires-python = ">=3.8"
12
+ license = { text = "MIT" }
13
+
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Development Status :: 5 - Production/Stable",
19
+ "Intended Audience :: End Users/Desktop",
20
+ "Topic :: Office/Business :: Financial :: Investment"
21
+ ]
22
+
23
+ keywords = ["degiro", "cli", "finance", "investment"]
24
+
25
+ dependencies = [
26
+ "degiroasync >= 1.1.0",
27
+ ]
28
+
29
+ [project.urls]
30
+ "Homepage" = "https://github.com/OhMajesticLama/degiro-cli"
31
+ "Documentation" = "https://ohmajesticlama.github.io/degiro-cli"
32
+
33
+ [project.scripts]
34
+ degiro-login = "degirocli.login:main"
35
+ degiro-history = "degirocli.history:main"
36
+ degiro-search = "degirocli.search:main"
37
+ degiro-portfolio = "degirocli.portfolio:main"
38
+
39
+ [project.optional-dependencies]
40
+ dev = [
41
+ # Tests
42
+ "pytest >= 7.0.1",
43
+ "coverage >= 6.3",
44
+ # Code quality
45
+ "flake8 >= 4.0.1",
46
+ "mypy >= 0.931",
47
+ # For shipping
48
+ "build >= 0.7.0",
49
+ "twine >= 3.8.0",
50
+ # Documentation
51
+ "sphinx >= 4.4.0",
52
+ "sphinx_rtd_theme >= 1.0.0",
53
+ "myst-parser >= 0.17.0",
54
+ # Other dev tools
55
+ "ipython",
56
+ "ipdb",
57
+ ]
58
+
59
+ [tool.setuptools]
60
+ packages = ["degirocli"]
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- if __name__ == '__main__':
5
- import sys
6
- import degirocli.history
7
- res = degirocli.history.run_cli()
8
- sys.exit(res)
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- if __name__ == '__main__':
5
- import sys
6
- import degirocli.login
7
- res = degirocli.login.run_cli()
8
- sys.exit(res)
File without changes
File without changes
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- if __name__ == '__main__':
5
- import sys
6
- import degirocli.search
7
- res = degirocli.search.run_cli()
8
- sys.exit(res)
@@ -1,9 +0,0 @@
1
- degiroasync>=0.20.0
2
-
3
- [dev]
4
- pytest>=7.0.1
5
- coverage>=6.3
6
- flake8>=4.0.1
7
- mypy>=0.931
8
- ipython
9
- ipdb
@@ -1,4 +0,0 @@
1
- [build-system]
2
- requires=["setuptools >= 59.6.0", "wheel >= 0.34.2"]
3
- build-backend= "setuptools.build_meta"
4
-
degiro-cli-0.1.2/setup.py DELETED
@@ -1,68 +0,0 @@
1
- """
2
- setup file for degiroasync
3
- """
4
- import sys
5
- import os
6
-
7
- import setuptools
8
-
9
-
10
- if __name__ == '__main__':
11
- description = "Unofficial command line tools for Degiro"
12
- readme_path = os.path.join(os.path.dirname(__file__), 'README.md')
13
- with open(readme_path, "r") as fh:
14
- long_description = fh.read()
15
-
16
- setuptools.setup(
17
- name="degiro-cli",
18
- version="0.1.2",
19
- author_email="ohmajesticlama@gmail.com",
20
- description=description,
21
- long_description=long_description,
22
- long_description_content_type='text/markdown',
23
- url="https://github.com/OhMajesticLama/degiro-cli",
24
- project_urls={
25
- 'Documentation':
26
- 'https://ohmajesticlama.github.io/degiro-cli'
27
- },
28
- packages=setuptools.find_packages(),
29
- scripts=[
30
- os.path.join('bin', 'degiro-login'),
31
- os.path.join('bin', 'degiro-history'),
32
- os.path.join('bin', 'degiro-search'),
33
- ],
34
- python_requires=">=3.8",
35
- install_requires=[
36
- 'degiroasync >= 0.20.0',
37
- ],
38
- extras_require={
39
- 'dev': [
40
- # Tests
41
- 'pytest >= 7.0.1',
42
- 'coverage >= 6.3',
43
- # Code quality
44
- 'flake8 >= 4.0.1',
45
- 'mypy >= 0.931',
46
- # For shipping
47
- #'build >= 0.7.0',
48
- #'twine >= 3.8.0',
49
- # Documentation
50
- #'sphinx >= 4.4.0',
51
- #'sphinx_rtd_theme >= 1.0.0',
52
- #'myst-parser >= 0.17.0', # markdown imports
53
- # Other dev tools
54
- 'ipython',
55
- 'ipdb',
56
- ]
57
- },
58
- classifiers=[
59
- "Programming Language :: Python :: 3",
60
- "License :: OSI Approved :: MIT License",
61
- "Operating System :: OS Independent",
62
- "Development Status :: 4 - Beta",
63
- "Intended Audience :: End Users/Desktop",
64
- "Topic :: Office/Business :: Financial :: Investment"
65
- ],
66
- test_suite='pytest',
67
- tests_require=['pytest']
68
- )
File without changes
File without changes