solcast 0.4.0__py3-none-any.whl

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.
solcast/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ __version__ = "0.4.0"
2
+
3
+ from . import api, forecast, historic, live, tmy, unmetered_locations, urls
4
+
5
+ __all__ = ["forecast", "historic", "live", "tmy", "unmetered_locations"]
solcast/api.py ADDED
@@ -0,0 +1,148 @@
1
+ import json
2
+ import os
3
+ from dataclasses import dataclass
4
+ from urllib.request import urlopen, Request
5
+ import urllib.parse
6
+ import urllib.error
7
+
8
+ try:
9
+ import pandas as pd
10
+ except ImportError:
11
+ _PANDAS = False
12
+ else:
13
+ _PANDAS = True
14
+
15
+
16
+ @dataclass
17
+ class Response:
18
+ """Class to handle API response from the Solcast API."""
19
+
20
+ code: int
21
+ url: str
22
+ data: bytes
23
+ success: bool
24
+ exception: str = None
25
+
26
+ def __repr__(self):
27
+ return f"status code={self.code}, url={self.url}"
28
+
29
+ def to_dict(self):
30
+ if self.code != 200:
31
+ raise Exception(self.exception)
32
+ return json.loads(self.data)
33
+
34
+ def to_pandas(self):
35
+ """returns the data as a Pandas DataFrame.
36
+ Some common processing is applied,
37
+ like casting the datetime columns and setting them as index.
38
+ """
39
+ # not ideal to run this for every Response
40
+ assert _PANDAS, ImportError(
41
+ "Pandas needs to be installed for this functionality."
42
+ )
43
+
44
+ if self.code != 200:
45
+ raise Exception(self.exception)
46
+
47
+ dfs = [
48
+ pd.DataFrame.from_records(self.to_dict()[k]) for k in self.to_dict().keys()
49
+ ]
50
+ dfs = pd.concat(dfs)
51
+ dfs.index = pd.DatetimeIndex(dfs["period_end"])
52
+
53
+ # to make it work with different Pandas versions
54
+ if dfs.index.tz is None:
55
+ dfs.index.tz = "UTC"
56
+
57
+ dfs.index.name = "period_end"
58
+ dfs = dfs.drop(columns=["period_end", "period"])
59
+
60
+ return dfs
61
+
62
+
63
+ class Client:
64
+ """Handles all API get requests for the different endpoints."""
65
+
66
+ def __init__(self, base_url: str, endpoint: str):
67
+ """
68
+ Args:
69
+ base_url: the base URL to Solcast API
70
+ endpoint: one of Solcast API's endpoints
71
+ """
72
+ self.base_url = base_url
73
+ self.endpoint = endpoint
74
+ self.url = self.make_url()
75
+
76
+ @staticmethod
77
+ def check_params(params: dict) -> (dict, str):
78
+ """runs some basic checks on the parameters that will be passed in the
79
+ GET request."""
80
+ assert isinstance(params, dict), "parameters needs to be a dict"
81
+
82
+ if "api_key" not in params:
83
+ params.update({"api_key": os.getenv("SOLCAST_API_KEY")})
84
+
85
+ if params["api_key"] is None:
86
+ raise ValueError("np API key provided.")
87
+
88
+ if len(params["api_key"]) <= 1:
89
+ raise ValueError("API key is too short.")
90
+
91
+ if "output_parameters" in params.keys() and isinstance(
92
+ params["output_parameters"], list
93
+ ):
94
+ params["output_parameters"] = ",".join(params["output_parameters"])
95
+
96
+ # truncate coordinates to 6 decimal places
97
+ if "latitude" in params.keys():
98
+ params["latitude"] = round(params["latitude"], 6)
99
+ if "longitude" in params.keys():
100
+ params["longitude"] = round(params["longitude"], 6)
101
+
102
+ # only json supported
103
+ if "format" in params.keys():
104
+ assert (
105
+ params["format"] == "json"
106
+ ), "only json response format is currently supported."
107
+
108
+ # api key in the header for secrecy
109
+ key = params["api_key"]
110
+ del params["api_key"]
111
+
112
+ return params, key
113
+
114
+ def make_url(self) -> str:
115
+ """composes the full URL."""
116
+ return "/".join([self.base_url, self.endpoint])
117
+
118
+ def get(self, params: dict) -> Response:
119
+ """makes the GET request.
120
+
121
+ Args:
122
+ params: a dictionary of parameters that are passed in the get request
123
+
124
+ Returns:
125
+ a Response object.
126
+ """
127
+
128
+ params, key = self.check_params(params)
129
+ url = self.url + "?" + urllib.parse.urlencode(params)
130
+ req = Request(url, headers={"Authorization": f"Bearer {key}"})
131
+ try:
132
+ with urlopen(req) as response:
133
+ body = response.read()
134
+ return Response(
135
+ code=response.code, url=url, data=body, success=True, exception=None
136
+ )
137
+ except urllib.error.HTTPError as e:
138
+ try:
139
+ exception_message = json.loads(e.read())["response_status"]["message"]
140
+ except:
141
+ exception_message = "Undefined Error"
142
+ return Response(
143
+ code=e.code,
144
+ url=e.url,
145
+ data=None,
146
+ exception=exception_message,
147
+ success=False,
148
+ )
solcast/forecast.py ADDED
@@ -0,0 +1,67 @@
1
+ from .api import Client, Response
2
+ from .urls import (
3
+ base_url,
4
+ forecast_rooftop_pv_power,
5
+ forecast_radiation_and_weather,
6
+ forecast_advanced_pv_power,
7
+ )
8
+
9
+
10
+ def radiation_and_weather(
11
+ latitude: float, longitude: float, output_parameters: str, **kwargs
12
+ ) -> Response:
13
+ """
14
+ Get irradiance and weather forecasts from the present time up to 14 days ahead
15
+ for the requested location, derived from satellite (clouds and irradiance
16
+ over non-polar continental areas, nowcasted for approx. four hours ahead)
17
+ and numerical weather models (other data and longer horizons).
18
+ """
19
+ client = Client(base_url=base_url, endpoint=forecast_radiation_and_weather)
20
+
21
+ return client.get(
22
+ {
23
+ "latitude": latitude,
24
+ "longitude": longitude,
25
+ "output_parameters": output_parameters,
26
+ "format": "json",
27
+ **kwargs,
28
+ }
29
+ )
30
+
31
+
32
+ def rooftop_pv_power(
33
+ latitude: float, longitude: float, output_parameters: str, **kwargs
34
+ ) -> Response:
35
+ """
36
+ Get basic rooftop PV power forecasts from the present time up to 14 days ahead
37
+ for the requested location, derived from satellite (clouds and irradiance
38
+ over non-polar continental areas, nowcasted for approx. four hours ahead)
39
+ and numerical weather models (other data and longer horizons).
40
+
41
+ See https://docs.solcast.com.au/ for full list of parameters.
42
+ """
43
+ client = Client(base_url=base_url, endpoint=forecast_rooftop_pv_power)
44
+
45
+ return client.get(
46
+ {
47
+ "latitude": latitude,
48
+ "longitude": longitude,
49
+ "output_parameters": output_parameters,
50
+ "format": "json",
51
+ **kwargs,
52
+ }
53
+ )
54
+
55
+
56
+ def advanced_pv_power(resource_id: int, **kwargs) -> Response:
57
+ """
58
+ Get high spec PV power forecasts from the present time up to 14 days ahead
59
+ for the requested site, derived from satellite (clouds and irradiance over
60
+ non-polar continental areas, nowcasted for approx. four hours ahead) and
61
+ numerical weather models (other data and longer horizons).
62
+
63
+ See https://docs.solcast.com.au/ for full list of parameters.
64
+ """
65
+ client = Client(base_url=base_url, endpoint=forecast_advanced_pv_power)
66
+
67
+ return client.get({"resource_id": resource_id, "format": "json", **kwargs})
solcast/historic.py ADDED
@@ -0,0 +1,51 @@
1
+ from .api import Client, Response
2
+ from .urls import base_url, historic_radiation_and_weather, historic_rooftop_pv_power
3
+
4
+
5
+ def radiation_and_weather(
6
+ latitude: float, longitude: float, start: str, **kwargs
7
+ ) -> Response:
8
+ """
9
+ Get historical irradiance and weather estimated actuals for up to 31 days of data
10
+ at a time for a requested location, derived from satellite (clouds and irradiance
11
+ over non-polar continental areas) and numerical weather models (other data).
12
+ Data is available from 2007-01-01T00:00Z up to real time estimated actuals.
13
+
14
+ See https://docs.solcast.com.au/ for full list of parameters.
15
+ """
16
+
17
+ client = Client(base_url=base_url, endpoint=historic_radiation_and_weather)
18
+
19
+ return client.get(
20
+ {
21
+ "latitude": latitude,
22
+ "longitude": longitude,
23
+ "start": start,
24
+ "format": "json",
25
+ **kwargs,
26
+ }
27
+ )
28
+
29
+
30
+ def rooftop_pv_power(
31
+ latitude: float, longitude: float, start: str, **kwargs
32
+ ) -> Response:
33
+ """
34
+ Get historical basic rooftop PV power estimated actuals for the requested location,
35
+ derived from satellite (clouds and irradiance over non-polar continental areas)
36
+ and numerical weather models (other data).
37
+
38
+ See https://docs.solcast.com.au/ for full list of parameters.
39
+ """
40
+
41
+ client = Client(base_url=base_url, endpoint=historic_rooftop_pv_power)
42
+
43
+ return client.get(
44
+ {
45
+ "latitude": latitude,
46
+ "longitude": longitude,
47
+ "start": start,
48
+ "format": "json",
49
+ **kwargs,
50
+ }
51
+ )
solcast/live.py ADDED
@@ -0,0 +1,62 @@
1
+ from typing import List
2
+
3
+ from .api import Client, Response
4
+ from .urls import (
5
+ base_url,
6
+ live_radiation_and_weather,
7
+ live_rooftop_pv_power,
8
+ live_advanced_pv_power,
9
+ )
10
+
11
+
12
+ def radiation_and_weather(
13
+ latitude: float, longitude: float, output_parameters: List[str], **kwargs
14
+ ) -> Response:
15
+ """Get irradiance and weather estimated actuals for near real-time and past 7 days
16
+ for the requested location, derived from satellite (clouds and irradiance
17
+ over non-polar continental areas) and numerical weather models (other data).
18
+
19
+ See https://docs.solcast.com.au/ for full list of parameters.
20
+ """
21
+ client = Client(base_url=base_url, endpoint=live_radiation_and_weather)
22
+
23
+ params = {
24
+ "latitude": latitude,
25
+ "longitude": longitude,
26
+ "output_parameters": output_parameters,
27
+ "format": "json",
28
+ **kwargs,
29
+ }
30
+
31
+ res = client.get(params)
32
+
33
+ return res
34
+
35
+
36
+ def rooftop_pv_power(latitude: float, longitude: float, **kwargs) -> Response:
37
+ """Get basic rooftop PV power forecasts from the present time up to 14 days ahead
38
+ for the requested location, derived from satellite (clouds and irradiance over
39
+ non-polar continental areas, nowcasted for approx. four hours ahead) and numerical
40
+ weather models (other data and longer horizons).
41
+
42
+ See https://docs.solcast.com.au/ for full list of parameters.
43
+ """
44
+ client = Client(base_url=base_url, endpoint=live_rooftop_pv_power)
45
+
46
+ return client.get(
47
+ {"latitude": latitude, "longitude": longitude, "format": "json", **kwargs}
48
+ )
49
+
50
+
51
+ def advanced_pv_power(resource_id: int, **kwargs) -> Response:
52
+ """
53
+ Get high spec PV power forecasts from the present time up to 14 days ahead for
54
+ the requested site, derived from satellite (clouds and irradiance
55
+ over non-polar continental areas, nowcasted for approx. four hours ahead)
56
+ and numerical weather models (other data and longer horizons).
57
+
58
+ See https://docs.solcast.com.au/ for full list of parameters.
59
+ """
60
+ client = Client(base_url=base_url, endpoint=live_advanced_pv_power)
61
+
62
+ return client.get({"resource_id": resource_id, "format": "json", **kwargs})
solcast/tmy.py ADDED
@@ -0,0 +1,34 @@
1
+ from .api import Client, Response
2
+ from .urls import base_url, tmy_radiation_and_weather, tmy_rooftop_pv_power
3
+
4
+
5
+ def radiation_and_weather(latitude: float, longitude: float, **kwargs) -> Response:
6
+ """
7
+ Get the irradiance and weather for a Typical Meteorological Year (TMY) at a requested location,
8
+ derived from satellite (clouds and irradiance over non-polar continental areas) and
9
+ numerical weather models (other data). The TMY is calculated with data from 2007 to 2023.
10
+
11
+ See https://docs.solcast.com.au/ for full list of parameters.
12
+ """
13
+
14
+ client = Client(base_url=base_url, endpoint=tmy_radiation_and_weather)
15
+
16
+ return client.get(
17
+ {"latitude": latitude, "longitude": longitude, "format": "json", **kwargs}
18
+ )
19
+
20
+
21
+ def rooftop_pv_power(latitude: float, longitude: float, **kwargs) -> Response:
22
+ """
23
+ Get the basic rooftop PV power estimated actuals for a Typical Meteorological Year (TMY) at a requested location,
24
+ derived from satellite (clouds and irradiance over non-polar continental areas) and
25
+ numerical weather models (other data). The TMY is calculated with data from 2007 to 2023.
26
+
27
+ See https://docs.solcast.com.au/ for full list of parameters.
28
+ """
29
+
30
+ client = Client(base_url=base_url, endpoint=tmy_rooftop_pv_power)
31
+
32
+ return client.get(
33
+ {"latitude": latitude, "longitude": longitude, "format": "json", **kwargs}
34
+ )
@@ -0,0 +1,44 @@
1
+ UNMETERED_LOCATIONS = {
2
+ "Sydney Opera House": {
3
+ "latitude": -33.856784,
4
+ "longitude": 151.215297,
5
+ "resource_id": "5f86-4c8f-2cb3-0215",
6
+ },
7
+ "Grand Canyon": {
8
+ "latitude": 36.099763,
9
+ "longitude": -112.112485,
10
+ "resource_id": " 375f-eb3e-71c0-ef5e",
11
+ },
12
+ "Stonehenge": {
13
+ "latitude": 51.178882,
14
+ "longitude": -1.826215,
15
+ "resource_id": "1a57-6b1f-ec18-c5c8",
16
+ },
17
+ "The Colosseum": {
18
+ "latitude": 41.89021,
19
+ "longitude": 12.492231,
20
+ "resource_id": "5f86-4c8f-2cb3-0215",
21
+ },
22
+ "Giza Pyramid Complex": {
23
+ "latitude": 29.977296,
24
+ "longitude": 31.132496,
25
+ "resource_id": "8d10-f530-af85-5cbb",
26
+ },
27
+ "Taj Mahal": {
28
+ "latitude": 27.175145,
29
+ "longitude": 78.042142,
30
+ "resource_id": "b926-8fd2-ad3f-e4f5",
31
+ },
32
+ }
33
+
34
+
35
+ def load_test_locations_coordinates():
36
+ """returns longitude, latitude and resource_id for the unmetered locations"""
37
+ coords = [list(coords.values()) for coords in list(UNMETERED_LOCATIONS.values())]
38
+ latitudes, longitudes = [c[0] for c in coords], [c[1] for c in coords]
39
+ return latitudes, longitudes
40
+
41
+
42
+ def load_test_locations_names():
43
+ """returns longitude, latitude and resource_id for the unmetered locations"""
44
+ return list(UNMETERED_LOCATIONS.keys())
solcast/urls.py ADDED
@@ -0,0 +1,11 @@
1
+ base_url = "https://api.solcast.com.au/data"
2
+ live_radiation_and_weather = "live/radiation_and_weather"
3
+ live_rooftop_pv_power = "live/rooftop_pv_power"
4
+ live_advanced_pv_power = "live/advanced_pv_power"
5
+ historic_radiation_and_weather = "historic/radiation_and_weather"
6
+ historic_rooftop_pv_power = "historic/rooftop_pv_power"
7
+ forecast_radiation_and_weather = "forecast/radiation_and_weather"
8
+ forecast_rooftop_pv_power = "forecast/rooftop_pv_power"
9
+ forecast_advanced_pv_power = "forecast/advanced_pv_power"
10
+ tmy_radiation_and_weather = "tmy/radiation_and_weather"
11
+ tmy_rooftop_pv_power = "tmy/rooftop_pv_power"
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.1
2
+ Name: solcast
3
+ Version: 0.4.0
4
+ Summary: a simple Python SDK for the Solcast API
5
+ Project-URL: Homepage, https://pypi.org/project/solcast/
6
+ Project-URL: Documentation, https://solcast.github.io/solcast-api-python-sdk
7
+ Project-URL: Repository, https://github.com/Solcast/solcast-api-python-sdk
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Information Technology
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.7
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Topic :: Internet
24
+ Classifier: Topic :: Scientific/Engineering
25
+ Classifier: Topic :: Software Development
26
+ Classifier: Topic :: Software Development :: Libraries
27
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
+ Classifier: Typing :: Typed
29
+ Requires-Python: >=3.7
30
+ Provides-Extra: all
31
+ Requires-Dist: black; extra == 'all'
32
+ Requires-Dist: matplotlib; extra == 'all'
33
+ Requires-Dist: notebook; extra == 'all'
34
+ Requires-Dist: pandas; extra == 'all'
35
+ Requires-Dist: solcast[docs]; extra == 'all'
36
+ Provides-Extra: docs
37
+ Requires-Dist: mkdocs; extra == 'docs'
38
+ Requires-Dist: mkdocs-jupyter; extra == 'docs'
39
+ Requires-Dist: mkdocs-material; extra == 'docs'
40
+ Requires-Dist: mkdocstrings[python]; extra == 'docs'
41
+ Requires-Dist: pytest; extra == 'docs'
42
+ Description-Content-Type: text/markdown
43
+
44
+ <img src="https://github.com/Solcast/solcast-api-python-sdk/blob/main/docs/img/logo.png?raw=true" width="100" align="right">
45
+
46
+ # Solcast API Python SDK
47
+
48
+ <em>simple Python SDK to access the Solcast API</em>
49
+
50
+ [![Docs](https://github.com/Solcast/solcast-api-python-sdk/actions/workflows/docs.yml/badge.svg)](https://github.com/Solcast/solcast-api-python-sdk/actions/workflows/docs.yml) [![Tests](https://github.com/Solcast/solcast-api-python-sdk/actions/workflows/test.yml/badge.svg)](https://github.com/Solcast/solcast-api-python-sdk/actions/workflows/test.yml) [![Publish 📦 to PyPI](https://github.com/Solcast/solcast-api-python-sdk/actions/workflows/publish-to-test-pypi.yml/badge.svg)](https://github.com/Solcast/solcast-api-python-sdk/actions/workflows/publish-to-test-pypi.yml)
51
+
52
+ ---
53
+
54
+ **Documentation**: <a href="https://solcast.github.io/solcast-api-python-sdk/" target="_blank">https://solcast.github.io/solcast-api-python-sdk/ </a>
55
+
56
+ ## Install
57
+ ```commandline
58
+ git clone https://github.com/Solcast/solcast-api-python-sdk.git
59
+ cd solcast-api-python-sdk
60
+ pip install .
61
+ # also pip install .[all] for the dev libs
62
+ ```
63
+ ## Basic Usage
64
+
65
+ ```python
66
+ from solcast import live
67
+
68
+ df = live.radiation_and_weather(
69
+ latitude=-33.856784,
70
+ longitude=151.215297,
71
+ output_parameters=['air_temp', 'dni', 'ghi']
72
+ ).to_pandas()
73
+ ```
74
+ ---
75
+
76
+ ## Contributing
77
+ ```commandline
78
+ pytest tests
79
+ ```
@@ -0,0 +1,12 @@
1
+ solcast/__init__.py,sha256=TkIXtkdYE9vpCC9GgRzmSXm_ocEs0f141KujcphvPYY,178
2
+ solcast/api.py,sha256=lHgQM9kNlmJZnrgZjjuR6somZx_MRblt_FY2wplVFhg,4578
3
+ solcast/forecast.py,sha256=epJBARPcp5VL4iPXqhSwcJEmQWioMjmGUdcu1o5_B9I,2380
4
+ solcast/historic.py,sha256=ncti6T7jIODJFNcRg46WhKOBb-AHEiawgQgUzCKpqww,1649
5
+ solcast/live.py,sha256=fY4iDVlYrK-4-B-CyofZzNP1PMnd9JwHimRhlnShwY0,2233
6
+ solcast/tmy.py,sha256=vEc65Cqj51_idEGj94_T9rvCxlOcDm2rIpR-30nYmSE,1432
7
+ solcast/unmetered_locations.py,sha256=GIJpokQMiT-P6CT7ba_7LzF3ORP4VQr0IJrN25HXumc,1390
8
+ solcast/urls.py,sha256=-CpwsIuyWUHxMOYSguDoomubV4XifyZBQDnKnRkf71Y,616
9
+ solcast-0.4.0.dist-info/METADATA,sha256=PG5AKcHP6u7oRjxU0WVL-gEEKL4Ak5hemHiZbrSX6lQ,3193
10
+ solcast-0.4.0.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87
11
+ solcast-0.4.0.dist-info/licenses/LICENSE,sha256=HOVcajFymlCFvzClhJHptoL1e7E2W5ibiFug0uaEtyo,563
12
+ solcast-0.4.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.18.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,13 @@
1
+ Copyright [2023] [Solcast]
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.