Skip to content

Commit eccdf68

Browse files
committed
added files subpackage to download from the new ENTSOE File Library
1 parent 225a6f6 commit eccdf68

File tree

4 files changed

+167
-1
lines changed

4 files changed

+167
-1
lines changed

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Documentation of the API found on https://transparency.entsoe.eu/content/static_
77
`python3 -m pip install entsoe-py`
88

99
## Usage
10-
The package comes with 2 clients:
10+
The package comes with 2 clients for the REST API:
1111
- [`EntsoeRawClient`](#EntsoeRawClient): Returns data in its raw format, usually XML or a ZIP-file containing XML's
1212
- [`EntsoePandasClient`](#EntsoePandasClient): Returns data parsed as a Pandas Series or DataFrame
1313
### <a name="EntsoeRawClient"></a>EntsoeRawClient
@@ -148,6 +148,23 @@ ts = client.query_day_ahead_prices(country_code, start=start, end=end)
148148
ts.to_csv('outfile.csv')
149149
```
150150

151+
### Download from ENTSOE File Library
152+
To download from the file libary, which replaced the old SFTP use the ```files``` subpackage with the ```EntsoeFileClient```
153+
154+
```python
155+
from entsoe.files import EntsoeFileClient
156+
157+
client = EntsoeFileClient(username=<YOUR ENTSOE USERNAME>, pwd=<YOUR ENTSOE PASSWORD>)
158+
# this returns a dict of {filename: unique_id}:
159+
file_list = client.list_folder('AcceptedAggregatedOffers_17.1.D')
160+
161+
# either download one file by name:
162+
df = client.download_single_file(folder='AcceptedAggregatedOffers_17.1.D', filename=list(file_list.keys())[0])
163+
164+
# or download multiple by unique_id:
165+
df = client.download_multiple_files(['a1a82b3f-c453-4181-8d20-ad39c948d4b0', '64e47e15-bac6-4212-b2dd-9667bdf33b5d'])
166+
```
167+
151168
### Mappings
152169
These lists are always evolving, so let us know if something's inaccurate!
153170

entsoe/files/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .entsoe_files import EntsoeFileClient

entsoe/files/decorators.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from functools import wraps
2+
import pandas as pd
3+
4+
5+
def check_expired(func):
6+
@wraps(func)
7+
def check_expired_wrapper(*args, **kwargs):
8+
self = args[0]
9+
10+
if pd.Timestamp.now(tz='europe/amsterdam') >= self.expire:
11+
self._update_token()
12+
13+
return func(*args, **kwargs)
14+
15+
return check_expired_wrapper

entsoe/files/entsoe_files.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
from typing import Optional, Dict
2+
import requests
3+
from entsoe import __version__
4+
import pandas as pd
5+
import json
6+
from io import BytesIO
7+
import zipfile
8+
from .decorators import check_expired
9+
10+
# DOCS for entsoe file library: https://transparencyplatform.zendesk.com/hc/en-us/articles/35960137882129-File-Library-Guide
11+
# postman description: https://documenter.getpostman.com/view/28274243/2sB2qgfz3W
12+
13+
14+
class EntsoeFileClient:
15+
BASEURL = "https://fms.tp.entsoe.eu/"
16+
17+
def __init__(self, username: str, pwd: str, session: Optional[requests.Session] = None,
18+
proxies: Optional[Dict] = None, timeout: Optional[int] = None
19+
):
20+
self.proxies = proxies
21+
self.timeout = timeout
22+
self.username = username
23+
self.pwd = pwd
24+
if session is None:
25+
session = requests.Session()
26+
self.session = session
27+
self.session.headers.update({
28+
'user-agent': f'entsoe-py {__version__} (github.com/EnergieID/entsoe-py)'
29+
})
30+
31+
self.access_token = None
32+
self.expire = None
33+
34+
self._update_token()
35+
36+
def _update_token(self):
37+
# different url that other calls so hardcoded new one here
38+
r = self.session.post(
39+
'https://keycloak.tp.entsoe.eu/realms/tp/protocol/openid-connect/token', data={
40+
'client_id': 'tp-fms-public',
41+
'grant_type': 'password',
42+
'username': self.username,
43+
'password': self.pwd
44+
},
45+
proxies=self.proxies, timeout=self.timeout
46+
)
47+
r.raise_for_status()
48+
data = r.json()
49+
self.expire = pd.Timestamp.now(tz='europe/amsterdam') + pd.Timedelta(seconds=data['expires_in'])
50+
self.access_token = data['access_token']
51+
52+
@check_expired
53+
def list_folder(self, folder: str) -> dict:
54+
"""
55+
returns a dictionary of filename: unique file id
56+
"""
57+
if not folder.endswith('/'):
58+
folder += '/'
59+
r = self.session.post(self.BASEURL + "listFolder",
60+
data=json.dumps({
61+
"path": "/TP_export/" + folder,
62+
"sorterList": [
63+
{
64+
"key": "periodCovered.from",
65+
"ascending": True
66+
}
67+
],
68+
"pageInfo": {
69+
"pageIndex": 0,
70+
"pageSize": 5000 # this should be enough for basically anything right now
71+
}
72+
}),
73+
headers={
74+
'Authorization': f'Bearer {self.access_token}',
75+
'Content-Type': 'application/json'
76+
},
77+
proxies=self.proxies, timeout=self.timeout)
78+
r.raise_for_status()
79+
data = r.json()
80+
return {x['name']: x['fileId'] for x in data['contentItemList']}
81+
82+
@check_expired
83+
def download_single_file(self, folder, filename) -> pd.DataFrame:
84+
"""
85+
download a file by filename, it is important to split folder and filename here
86+
"""
87+
if not folder.endswith('/'):
88+
folder += '/'
89+
r = self.session.post(self.BASEURL + "downloadFileContent",
90+
data=json.dumps({
91+
"folder": "/TP_export/" + folder,
92+
"filename": filename,
93+
"downloadAsZip": True,
94+
"topLevelFolder": "TP_export",
95+
}),
96+
headers={
97+
'Authorization': f'Bearer {self.access_token}',
98+
'Content-Type': 'application/json'
99+
})
100+
r.raise_for_status()
101+
stream = BytesIO(r.content)
102+
stream.seek(0)
103+
zf = zipfile.ZipFile(stream)
104+
with zf.open(zf.filelist[0].filename) as file:
105+
return pd.read_csv(file, sep='\t')
106+
107+
@check_expired
108+
def download_multiple_files(self, file_ids: list) -> pd.DataFrame:
109+
"""
110+
for now when downloading multiple files only list of file ids is supported by this package
111+
"""
112+
r = self.session.post(self.BASEURL + "downloadFileContent",
113+
data=json.dumps({
114+
"fileIdList": file_ids,
115+
"downloadAsZip": True,
116+
"topLevelFolder": "TP_export",
117+
}),
118+
headers={
119+
'Authorization': f'Bearer {self.access_token}',
120+
'Content-Type': 'application/json'
121+
},
122+
proxies=self.proxies, timeout=self.timeout)
123+
r.raise_for_status()
124+
stream = BytesIO(r.content)
125+
stream.seek(0)
126+
zf = zipfile.ZipFile(stream)
127+
df = []
128+
for fz in zf.filelist:
129+
with zf.open(fz.filename) as file:
130+
df.append(pd.read_csv(file, sep='\t'))
131+
132+
return pd.concat(df)
133+

0 commit comments

Comments
 (0)