├── MANIFEST.in ├── requirements-devel.txt ├── requirements.txt ├── pypvwatts ├── __version__.py ├── __init__.py ├── pvwattserror.py ├── pvwattsresult.py ├── test.py └── pypvwatts.py ├── setup.cfg ├── .travis.yml ├── tox.ini ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── setup.py └── README.md /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | -------------------------------------------------------------------------------- /requirements-devel.txt: -------------------------------------------------------------------------------- 1 | tox>=3.24.0 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests >= 2.1.0 2 | -------------------------------------------------------------------------------- /pypvwatts/__version__.py: -------------------------------------------------------------------------------- 1 | VERSION = '3.0.4' 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /pypvwatts/__init__.py: -------------------------------------------------------------------------------- 1 | from .pypvwatts import PVWatts 2 | from .pvwattserror import PVWattsValidationError 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - pip install python-coveralls 3 | - pip install pytest-cov 4 | - pip install pytest-flakes 5 | 6 | sudo: false 7 | language: python 8 | python: 9 | - "2.7" 10 | - "3.6" 11 | install: pip install tox-travis 12 | script: tox 13 | 14 | after_success: 15 | - coveralls 16 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # tox (https://tox.readthedocs.io/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = py27, py3 8 | 9 | [testenv] 10 | commands = python -m unittest pypvwatts.test 11 | deps = requests 12 | 13 | -------------------------------------------------------------------------------- /pypvwatts/pvwattserror.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | class PVWattsError(Exception): 3 | """ 4 | Base class for PVWatts errors 5 | """ 6 | def __init__(self, message): 7 | Exception.__init__(self, message) 8 | 9 | 10 | class PVWattsValidationError(PVWattsError): 11 | """ 12 | Validation error on request 13 | """ 14 | def __init__(self, message): 15 | PVWattsError.__init__(self, message) 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | .pyirc 3 | 4 | # C extensions 5 | *.so 6 | 7 | # Packages 8 | *.egg 9 | *.egg-info 10 | dist 11 | build 12 | eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | __pycache__ 22 | 23 | # Installer logs 24 | pip-log.txt 25 | 26 | # Unit test / coverage reports 27 | .coverage 28 | .tox 29 | nosetests.xml 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 3.0.4 - Moved Changelog to its own file 4 | 5 | 3.0.3 - Fix nsrdb dataset validation error 6 | 7 | 3.0.2 - Fix example output in README 8 | 9 | 3.0.1 - Fix NREL documentation URL in README 10 | 11 | 3.0.0 - Updated to PVWatts v6. Minor input validation fixes and generalized tox config to test with any python 3 version 12 | 13 | 2.1.0 - Add Python 3 support while maintaining Python 2 backwards compatibility 14 | 15 | 2.0.3 - Fix pip install under OSX 16 | 17 | 2.0.2 - Make all requests using HTTPS 18 | 19 | 2.0.1 - Minor updates to README 20 | 21 | 2.0.0 - Version is now compatible with PVWatts v5. 2.0.0 is not backwards compatible with 1.2.0. Attributes of the API have changed. 22 | 23 | 1.2.0 - Fixed proxy handling, now using proxies parameter. 24 | 25 | 1.1.1 - Updated copyright notice 26 | 27 | 1.1.0 - Minor import fix and README update 28 | 29 | 1.0.0 - First release 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 renooble.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | try: 4 | from setuptools import setup 5 | except ImportError: 6 | from distutils.core import setup 7 | 8 | from pypvwatts.__version__ import VERSION 9 | 10 | setup( 11 | name='pypvwatts', 12 | version=VERSION, 13 | author='Miguel Paolino', 14 | author_email='mpaolino@gmail.com', 15 | url='https://github.com/mpaolino/pypvwatts', 16 | download_url='https://github.com/mpaolino/pypvwatts/archive/master.zip', 17 | description='Python wrapper for NREL PVWatts\'s API.', 18 | long_description=open('README.md').read(), 19 | packages=['pypvwatts'], 20 | provides=['pypvwatts'], 21 | requires=['requests'], 22 | install_requires=['requests >= 2.1.0'], 23 | classifiers=[ 24 | 'Development Status :: 5 - Production/Stable', 25 | 'Intended Audience :: Developers', 26 | 'Natural Language :: English', 27 | 'Operating System :: OS Independent', 28 | 'Programming Language :: Python :: 2', 29 | 'License :: OSI Approved :: MIT License', 30 | 'Topic :: Internet', 31 | 'Topic :: Internet :: WWW/HTTP', 32 | ], 33 | keywords='nrel pvwatts pypvwatts', 34 | license='MIT', 35 | python_requires=">=2.7", 36 | ) 37 | -------------------------------------------------------------------------------- /pypvwatts/pvwattsresult.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | class PVWattsResult(object): 4 | """ 5 | Result class for PVWatts request 6 | """ 7 | # Result's output fields to be accesed as properties of PVWattsResult 8 | shortcut_fields = ('poa_monthly', 'dc_monthly', 'ac_annual', 9 | 'solrad_annual', 'solrad_monthly', 'ac_monthly', 10 | 'ac', 'poa', 'dn', 'dc', 'df', 'tamb', 'tcell', 'wspd') 11 | 12 | def __init__(self, result): 13 | """ 14 | Creates instance of PVWattsResult from the JSON API response 15 | """ 16 | self.result = result 17 | 18 | @property 19 | def raw(self): 20 | return self.result 21 | 22 | def __getattr__(self, name): 23 | """ 24 | Access outputs results as properties 25 | """ 26 | result = None 27 | if name in PVWattsResult.shortcut_fields and 'outputs' in self.result: 28 | return self.result['outputs'][name] 29 | if name is not None: 30 | return self.result[name] 31 | return result 32 | 33 | def __unicode__(self): 34 | return unicode(self.result) 35 | 36 | def __str__(self): 37 | return self.__unicode__().encode('utf8') 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pypvwatts 2 | ========= 3 | 4 | [![Build Status](https://travis-ci.org/mpaolino/pypvwatts.svg?branch=master)](https://travis-ci.org/mpaolino/pypvwatts) 5 | 6 | A NREL PVWAtts API v6 thin Python wrapper built around requests library. 7 | 8 | Originally developed by . 9 | 10 | Github repository: 11 | 12 | 13 | PVWatts API v6 Documentation: 14 | 15 | Python requests library: 16 | 17 | Supports 18 | -------- 19 | 20 | Python 2 & Python 3 21 | 22 | Installing 23 | ---------- 24 | 25 | pypvwatts can be installed using distutils/setuptools, either using the setup.py included or directly over PyPi package repository: 26 | 27 | 28 | Using PyPi 29 | 30 | 31 | $ pip install pypvwatts 32 | 33 | 34 | Download the tarball, unpack and then run setup.py 35 | 36 | 37 | $ python setup.py install 38 | 39 | 40 | Usage - with class methods 41 | -------------------------- 42 | 43 | 44 | >>> from pypvwatts import PVWatts 45 | >>> PVWatts.api_key = 'myapikey' 46 | >>> result = PVWatts.request( 47 | system_capacity=4, module_type=1, array_type=1, 48 | azimuth=190, tilt=30, dataset='tmy2', 49 | losses=13, lat=40, lon=-105) 50 | >>> result.ac_annual 51 | 6683.64501953125 52 | 53 | Usage - with instance methods 54 | ----------------------------- 55 | 56 | 57 | >>> from pypvwatts import PVWatts 58 | >>> p = PVWatts(api_key='myapikey') 59 | >>> result = p.request( 60 | system_capacity=4, module_type=1, array_type=1, 61 | azimuth=190, tilt=30, dataset='tmy2', 62 | losses=13, lat=40, lon=-105) 63 | >>> result.ac_annual 64 | 6683.64501953125 65 | 66 | 67 | Request parameters and responses 68 | -------------------------------- 69 | 70 | All request parameters correspond to NREL PVWatts API parameters. 71 | 72 | This library provides shortcuts for all response output fields, all can be 73 | accessed as a result property. 74 | 75 | Please refer to NREL PVWatts documentation for further details. 76 | 77 | https://developer.nrel.gov/docs/solar/pvwatts/v6/ 78 | 79 | Raw data 80 | -------- 81 | 82 | Raw result data can be queried using the result.raw attribute. 83 | 84 | 85 | >>> from pypvwatts import PVWatts 86 | >>> PVWatts.api_key = 'DEMO_KEY' 87 | >>> result = PVWatts.request( 88 | system_capacity=4, module_type=1, array_type=1, 89 | azimuth=190, tilt=30, dataset='tmy2', 90 | losses=13, lat=40, lon=-105) 91 | >>> print(result.raw) 92 | {'inputs': {'system_capacity': '4', 'module_type': '1', 'losses': '13', 'array_type': '1', 'tilt': '30', 'azimuth': '190', 'lat': '40', 'lon': '-105', 'dataset': 'tmy2', 'radius': '0','timeframe': 'monthly'}, 'errors': [], 'warnings': [], 'version': '1.1.0', 'ssc_info': {'version': 45, 'build': 'Linux 64 bit GNU/C++ Jul 7 2015 14:24:09'}, 'station_info': {'lat': 40.01666641235352, 'lon': -105.25, 'elev': 1634.0, 'tz': -7.0, 'location': '94018', 'city': 'BOULDER', 'state': 'CO', 'solar_resource_file': '94018.tm2', 'distance': 21235}, 'outputs': {'ac_monthly': [418.8210754394531, 422.0429992675781, 588.85791015625, 586.0773315429688, 612.3723754882812, 598.5872802734375, 595.2975463867188, 597.31396484375, 569.5850219726562, 524.8071899414062, 419.6332397460938, 401.0901184082031], 'poa_monthly': [124.2255630493164, 127.3401947021484, 180.2367248535156, 183.1153717041016, 193.9059143066406, 193.1558837890625, 195.6830749511719, 196.3893127441406, 184.4766387939453, 165.8097991943359, 126.6421508789062, 118.6332244873047], 'solrad_monthly': [4.007276058197021, 4.547863960266113, 5.814087867736816, 6.103845596313477, 6.255029678344727, 6.438529491424561, 6.312357425689697, 6.335139274597168, 6.149221420288086, 5.348703384399414, 4.221405029296875, 3.826878309249878], 'dc_monthly': [437.2481384277344, 441.5818176269531, 617.2501831054688, 614.1566162109375, 639.8923950195312, 625.9356689453125, 622.40185546875, 623.8253173828125, 594.6722412109375, 547.7664794921875, 438.1986389160156, 418.7980346679688], 'ac_annual': 6334.48486328125, 'solrad_annual': 5.446694850921631, 'capacity_factor': 18.07786750793457}} 93 | 94 | 95 | Errors 96 | ------ 97 | 98 | All API errors are reported via JSON response, using the errors attribute. 99 | 100 | 101 | >>> from pypvwatts import PVWatts 102 | >>> result = PVWatts.request( 103 | system_capacity=4, module_type=1, array_type=1, 104 | azimuth=190, tilt=30, dataset='tmy2', 105 | losses=13, lat=40, lon=-105) 106 | >>> result.errors 107 | [u'You have exceeded your rate limit. Try again later or contact us at http://developer.nrel.gov/contact for assistance'] 108 | 109 | 110 | All parameters feeded to make the request are validated, all validations follow the restrictions documented in NREL v6 API docs at . 111 | All validation errors will be raised with *pypvwatts.pvwattserror.PVWattsValidationError* exception. 112 | 113 | pypvwatts does not try to hide the fact is a thin wrapper around requests library so all other service errors such as connectivity or timeouts are raised as requests library exceptions . 114 | 115 | 116 | Tests 117 | ----- 118 | 119 | Simple tests are provided in test.py. Run them with: 120 | 121 | $ python -m unittest pypvwatts.test 122 | 123 | Or the preferred way, testing Python 2.7 and Python 3.9 together using tox (you need to install it): 124 | 125 | $ tox 126 | 127 | 128 | Author: Miguel Paolino , Hannes Hapke - Copyright 129 | -------------------------------------------------------------------------------- /pypvwatts/test.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Unit tests for pypvwatts. 4 | 5 | """ 6 | from .pypvwatts import PVWatts, PVWattsResult 7 | from .pvwattserror import PVWattsValidationError 8 | 9 | import unittest 10 | import json 11 | 12 | SAMPLE_RESPONSE = """ 13 | { 14 | "inputs":{ 15 | "lat":"40", 16 | "lon":"-105", 17 | "system_capacity":"4", 18 | "azimuth":"180", 19 | "tilt":"40", 20 | "array_type":"1", 21 | "module_type":"1", 22 | "losses":"10" 23 | }, 24 | "errors":[ 25 | 26 | ], 27 | "warnings":[ 28 | 29 | ], 30 | "version":"1.0.1", 31 | "ssc_info":{ 32 | "version":34, 33 | "build":"Unix 64 bit GNU/C++ Aug 18 2014 13:38:36" 34 | }, 35 | "station_info":{ 36 | "lat":40.016666412353516, 37 | "lon":-105.25, 38 | "elev":1634.0, 39 | "tz":-7.0, 40 | "location":"94018", 41 | "city":"BOULDER", 42 | "state":"CO", 43 | "solar_resource_file":"94018.tm2", 44 | "distance":21235 45 | }, 46 | "outputs":{ 47 | "ac_monthly":[ 48 | 474.3351745605469, 49 | 465.9206237792969, 50 | 628.4765625, 51 | 602.564208984375, 52 | 611.0515747070312, 53 | 591.2024536132812, 54 | 596.1395874023438, 55 | 610.1753540039062, 56 | 598.2145385742188, 57 | 574.7982177734375, 58 | 471.78070068359375, 59 | 458.9857177734375 60 | ], 61 | "poa_monthly":[ 62 | 136.04103088378906, 63 | 136.04443359375, 64 | 185.7895965576172, 65 | 181.16891479492188, 66 | 185.77963256835938, 67 | 182.52105712890625, 68 | 187.89971923828125, 69 | 193.35572814941406, 70 | 187.40081787109375, 71 | 175.5979461669922, 72 | 137.59872436523438, 73 | 131.25526428222656 74 | ], 75 | "solrad_monthly":[ 76 | 4.388420581817627, 77 | 4.858729839324951, 78 | 5.993212699890137, 79 | 6.038963794708252, 80 | 5.992891311645508, 81 | 6.084035396575928, 82 | 6.061281204223633, 83 | 6.237281322479248, 84 | 6.246694087982178, 85 | 5.664449691772461, 86 | 4.5866241455078125, 87 | 4.2340407371521 88 | ], 89 | "dc_monthly":[ 90 | 495.09564208984375, 91 | 487.5823669433594, 92 | 657.702880859375, 93 | 629.8565063476562, 94 | 638.9706420898438, 95 | 618.6126708984375, 96 | 623.68994140625, 97 | 637.5205688476562, 98 | 624.635009765625, 99 | 599.9056396484375, 100 | 492.5662841796875, 101 | 479.1076354980469 102 | ], 103 | "ac_annual":6683.64501953125, 104 | "solrad_annual":5.5322184562683105 105 | } 106 | } 107 | """ 108 | 109 | 110 | class Test(unittest.TestCase): 111 | """ 112 | Unit tests for PVWatts. 113 | 114 | """ 115 | def test_pvwatts_results(self): 116 | """Test PVWattsResult attrib handling""" 117 | result = PVWattsResult(json.loads(SAMPLE_RESPONSE)) 118 | self.assertEqual(result.solrad_annual, 5.5322184562683105) 119 | self.assertEqual(result.station_info['city'], 'BOULDER') 120 | 121 | def test_pypvwatts_validation(self): 122 | """Test pypvwatts validations""" 123 | self.assertRaises(PVWattsValidationError, PVWatts.request, 124 | system_capacity='a', module_type=1, array_type=1, 125 | azimuth=190, tilt=30, 126 | dataset='tmy2', losses=0.13, 127 | lat=40, lon=-105) 128 | self.assertRaises(PVWattsValidationError, PVWatts.request, 129 | system_capacity=4, module_type='1', array_type=1, 130 | azimuth=190, tilt=30, 131 | dataset='tmy2', losses=0.13, 132 | lat=40, lon=-105) 133 | self.assertRaises(PVWattsValidationError, PVWatts.request, 134 | system_capacity=4, module_type=1, array_type='1', 135 | azimuth=190, tilt=30, 136 | dataset='tmy2', losses=0.13, 137 | lat=40, lon=-105) 138 | self.assertRaises(PVWattsValidationError, PVWatts.request, 139 | system_capacity=4, module_type=1, array_type=1, 140 | azimuth=-190, tilt=30, 141 | dataset='1', losses=0.13, 142 | lat=40, lon=-105) 143 | self.assertRaises(PVWattsValidationError, PVWatts.request, 144 | system_capacity=4, module_type=1, array_type=1, 145 | azimuth=190, tilt=100, 146 | dataset='1', losses=0.13, 147 | lat=40, lon=-105) 148 | self.assertRaises(PVWattsValidationError, PVWatts.request, 149 | system_capacity=4, module_type=1, array_type=1, 150 | azimuth=190, tilt=30, 151 | dataset='1', losses=0.13, 152 | lat=40, lon=-105) 153 | self.assertRaises(PVWattsValidationError, PVWatts.request, 154 | system_capacity=4, module_type=1, array_type=1, 155 | azimuth=190, tilt=30, 156 | dataset='tmy2', losses=-400, 157 | lat=40, lon=-105) 158 | self.assertRaises(PVWattsValidationError, PVWatts.request, 159 | system_capacity=4, module_type=1, array_type=1, 160 | azimuth=190, tilt=30, 161 | dataset='tmy2', losses=0.13, 162 | lat=-100, lon=-105) 163 | self.assertRaises(PVWattsValidationError, PVWatts.request, 164 | system_capacity=4, module_type=1, array_type=1, 165 | azimuth=190, tilt=30, 166 | dataset='tmy2', losses=0.13, 167 | lat=40, lon=400) 168 | self.assertRaises(PVWattsValidationError, PVWatts.request, 169 | system_capacity=4, module_type=1, array_type=1, 170 | azimuth=190, tilt=30, 171 | dataset='tmy2', losses=0.13, 172 | lat=40, lon=-105, timeframe='notvalid') 173 | self.assertRaises(PVWattsValidationError, PVWatts.request, 174 | system_capacity=4, module_type=1, array_type=1, 175 | azimuth=190, tilt='a', 176 | dataset='tmy2', losses=0.13, 177 | lat=40, lon=-105) 178 | self.assertRaises(PVWattsValidationError, PVWatts.request, 179 | system_capacity=4, module_type=1, array_type=1, 180 | azimuth=190, tilt=30, 181 | dataset='tmy2', losses=0.13, 182 | lat=40, lon=-105, dc_ac_ratio=-10) 183 | self.assertRaises(PVWattsValidationError, PVWatts.request, 184 | system_capacity=4, module_type=1, array_type=1, 185 | azimuth=190, tilt=30, 186 | dataset='tmy2', losses=0.13, 187 | lat=40, lon=-105, gcr=10) 188 | self.assertRaises(PVWattsValidationError, PVWatts.request, 189 | system_capacity=4, module_type=1, array_type=1, 190 | azimuth=190, tilt=30, 191 | dataset='tmy2', losses=0.13, 192 | lat=40, lon=-105, inv_eff=0) 193 | 194 | datasets = ['nsrdb', 'nsrdb', 'tmy2', 'tmy3', 'intl'] 195 | for dataset in datasets: 196 | result = PVWatts.request(system_capacity=4, module_type=1, array_type=1, 197 | azimuth=190, tilt=30, 198 | dataset=dataset, losses=0.13, 199 | lat=40, lon=-105) 200 | self.assertIsInstance(result, (PVWattsResult)) 201 | 202 | 203 | def test_pypvwatts(self): 204 | """Test pypvwatts""" 205 | PVWatts.api_key = 'DEMO_KEY' 206 | results = PVWatts.request( 207 | system_capacity=4, module_type=1, array_type=1, 208 | azimuth=190, tilt=30, dataset='tmy2', 209 | losses=0.13, lat=40, lon=-105) 210 | self.assert_results(results) 211 | 212 | def test_pypvwatts_instance(self): 213 | """Test pypvwatts instance based searches""" 214 | p = PVWatts(api_key='DEMO_KEY') 215 | results = p.request( 216 | system_capacity=4, module_type=1, array_type=1, 217 | azimuth=190, tilt=30, dataset='tmy2', 218 | losses=0.13, lat=40, lon=-105) 219 | self.assert_results(results) 220 | 221 | def assert_results(self, results): 222 | self.assertEqual(results.ac_annual, 7201.1396484375) 223 | self.assertEqual(results.solrad_annual, 5.446694850921631) 224 | self.assertEqual(results.station_info['city'], 'BOULDER') 225 | self.assertIn(501.930908203125, results.dc_monthly) 226 | self.assertIn(180.2367248535156, results.poa_monthly) 227 | self.assertIn(6.103845596313477, results.solrad_monthly) 228 | 229 | 230 | if __name__ == "__main__": 231 | unittest.main() 232 | -------------------------------------------------------------------------------- /pypvwatts/pypvwatts.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Python wrapper for NREL PVWatt version 6. 4 | """ 5 | from .pvwattsresult import PVWattsResult 6 | from .pvwattserror import PVWattsError, PVWattsValidationError 7 | import requests 8 | from .__version__ import VERSION 9 | 10 | import functools 11 | import sys 12 | 13 | if sys.version_info > (3,): 14 | long = int 15 | unicode = str 16 | 17 | 18 | # this decorator lets me use methods as both static and instance methods 19 | class omnimethod(object): 20 | def __init__(self, func): 21 | self.func = func 22 | 23 | def __get__(self, instance, owner): 24 | return functools.partial(self.func, instance) 25 | 26 | 27 | class PVWatts(): 28 | ''' 29 | A Python wrapper for NREL PVWatts V6.0.0 API 30 | ''' 31 | 32 | PVWATTS_QUERY_URL = 'https://developer.nrel.gov/api/pvwatts/v6.json' 33 | api_key = 'DEMO_KEY' 34 | 35 | def __init__(self, api_key='DEMO_KEY', proxies=None): 36 | PVWatts.api_key = api_key 37 | self.proxies = proxies 38 | 39 | @omnimethod 40 | def validate_system_capacity(self, system_capacity): 41 | if system_capacity is None: 42 | return 43 | 44 | if not isinstance(system_capacity, (int, long, float)): 45 | raise PVWattsValidationError( 46 | 'system_capacity must be int, long or float') 47 | 48 | if not (0.05 <= system_capacity and system_capacity <= 500000): 49 | raise PVWattsValidationError( 50 | 'system_capacity must be >= 0.05 and <= 500000') 51 | 52 | return system_capacity 53 | 54 | @omnimethod 55 | def validate_module_type(self, module_type): 56 | if module_type is None: 57 | return 58 | 59 | if not isinstance(module_type, (int, float, long)): 60 | raise PVWattsValidationError( 61 | 'module_type must be int, long or float') 62 | 63 | if module_type not in (0, 1, 2): 64 | raise PVWattsValidationError( 65 | 'module_type must be 0, 1 or 2') 66 | 67 | return module_type 68 | 69 | @omnimethod 70 | def validate_losses(self, losses): 71 | if losses is None: 72 | return 73 | 74 | if not isinstance(losses, (int, long, float)): 75 | raise PVWattsValidationError('losses must be int, long or float') 76 | 77 | if not (-5 <= losses and losses <= 99): 78 | raise PVWattsValidationError('losses must be >= -5\% and <= 99%') 79 | 80 | return losses 81 | 82 | @omnimethod 83 | def validate_array_type(self, array_type): 84 | if array_type is None: 85 | return 86 | 87 | if not isinstance(array_type, (int, long, float)): 88 | raise PVWattsValidationError( 89 | 'array_type must be int, long or float') 90 | 91 | if array_type not in (0, 1, 2, 3, 4): 92 | raise PVWattsValidationError( 93 | 'array_type must be 0, 1, 2, 3 or 4') 94 | 95 | return array_type 96 | 97 | @omnimethod 98 | def validate_tilt(self, tilt): 99 | if tilt is None: 100 | return 101 | 102 | if not isinstance(tilt, (int, long, float)): 103 | raise PVWattsValidationError('tilt must be int, long or float') 104 | 105 | if not (0 <= tilt and tilt <= 90): 106 | raise PVWattsValidationError('tilt must be >= 0 and <= 90') 107 | 108 | return tilt 109 | 110 | @omnimethod 111 | def validate_azimuth(self, azimuth): 112 | if azimuth is None: 113 | return 114 | 115 | if not isinstance(azimuth, (int, long, float)): 116 | raise PVWattsValidationError('azimuth must be int, long or float') 117 | 118 | if not (0 <= azimuth and azimuth <= 360): 119 | raise PVWattsValidationError('azimuth must be >= 0 and <= 360') 120 | 121 | return azimuth 122 | 123 | @omnimethod 124 | def validate_lat(self, lat): 125 | if lat is None: 126 | return 127 | 128 | if not isinstance(lat, (int, long, float)): 129 | raise PVWattsValidationError('lat must be int, long or float') 130 | 131 | if not (-90 <= lat and lat <= 90): 132 | raise PVWattsValidationError('lat must be >= -90 and <= 90') 133 | 134 | return lat 135 | 136 | @omnimethod 137 | def validate_lon(self, lon): 138 | if lon is None: 139 | return 140 | 141 | if not isinstance(lon, (int, long, float)): 142 | raise PVWattsValidationError('lon must be int, long or float') 143 | 144 | if not (-180 <= lon and lon <= 180): 145 | raise PVWattsValidationError('lon must be >= -180 and <= 180') 146 | 147 | return lon 148 | 149 | @omnimethod 150 | def validate_dataset(self, dataset): 151 | if dataset is None: 152 | return 153 | 154 | if not isinstance(dataset, (str, unicode)): 155 | raise PVWattsValidationError('dataset must be str or unicode') 156 | 157 | if dataset not in ('tmy2', 'tmy3', 'intl', 'nsrdb'): 158 | raise PVWattsValidationError( 159 | 'dataset must be \'nsrdb\', \'tmy2\', \'tmy3\' or \'intl\'') 160 | 161 | return dataset 162 | 163 | @omnimethod 164 | def validate_radius(self, radius): 165 | if radius is None: 166 | return 167 | 168 | if not isinstance(radius, (int, long, float)): 169 | raise PVWattsValidationError('radius must be int, long or float') 170 | 171 | if not (0 <= radius): 172 | raise PVWattsValidationError('radius must be >= 0') 173 | 174 | return radius 175 | 176 | @omnimethod 177 | def validate_timeframe(self, timeframe): 178 | if timeframe is None: 179 | return 180 | 181 | if not isinstance(timeframe, (str, unicode)): 182 | raise PVWattsValidationError( 183 | 'timeframe must be str or unicode') 184 | 185 | if timeframe not in ('hourly', 'monthly'): 186 | raise PVWattsValidationError( 187 | 'dataset must be \'hourly\' or \'monthly\'') 188 | 189 | return timeframe 190 | 191 | @omnimethod 192 | def validate_dc_ac_ratio(self, dc_ac_ratio): 193 | if dc_ac_ratio is None: 194 | return 195 | 196 | if not isinstance(dc_ac_ratio, (int, long, float)): 197 | raise PVWattsValidationError( 198 | 'dc_ac_ratio must be int, long or float') 199 | 200 | if not (0 < dc_ac_ratio): 201 | raise PVWattsValidationError( 202 | 'dc_ac_ratio must be positive') 203 | 204 | return dc_ac_ratio 205 | 206 | @omnimethod 207 | def validate_gcr(self, gcr): 208 | if gcr is None: 209 | return 210 | 211 | if not isinstance(gcr, (int, long, float)): 212 | raise PVWattsValidationError('gcr must be int, long or float') 213 | 214 | if not (0 <= gcr and gcr <= 3): 215 | raise PVWattsValidationError('gcr must be >= 0 and <= 3') 216 | 217 | return gcr 218 | 219 | @omnimethod 220 | def validate_inv_eff(self, inv_eff): 221 | if inv_eff is None: 222 | return 223 | 224 | if not isinstance(inv_eff, (int, long, float)): 225 | raise PVWattsValidationError('inv_eff must be int, long or float') 226 | 227 | if not (90 <= inv_eff and inv_eff <= 99.5): 228 | raise PVWattsValidationError('inv_eff must be >= 90 and <= 99.5') 229 | 230 | return inv_eff 231 | 232 | @property 233 | def version(self): 234 | return VERSION 235 | 236 | @omnimethod 237 | def get_data(self, params={}): 238 | """ 239 | Make the request and return the deserialided JSON from the response 240 | 241 | :param params: Dictionary mapping (string) query parameters to values 242 | :type params: dict 243 | :return: JSON object with the data fetched from that URL as a 244 | JSON-format object. 245 | :rtype: (dict or array) 246 | 247 | """ 248 | if self and hasattr(self, 'proxies') and self.proxies is not None: 249 | response = requests.request('GET', 250 | url=PVWatts.PVWATTS_QUERY_URL, 251 | params=params, 252 | headers={'User-Agent': ''.join( 253 | ['pypvwatts/', VERSION, 254 | ' (Python)'])}, 255 | proxies=self.proxies) 256 | else: 257 | response = requests.request('GET', 258 | url=PVWatts.PVWATTS_QUERY_URL, 259 | params=params, 260 | headers={'User-Agent': ''.join( 261 | ['pypvwatts/', VERSION, 262 | ' (Python)'])}) 263 | 264 | if response.status_code == 403: 265 | raise PVWattsError("Forbidden, 403") 266 | return response.json() 267 | 268 | @omnimethod 269 | def request(self, format=None, system_capacity=None, module_type=0, 270 | losses=12, array_type=1, tilt=None, azimuth=None, 271 | address=None, lat=None, lon=None, file_id=None, dataset='tmy3', 272 | radius=0, timeframe='monthly', dc_ac_ratio=None, gcr=None, 273 | inv_eff=None, callback=None): 274 | 275 | params = { 276 | 'format': format, 277 | 'system_capacity': 278 | PVWatts.validate_system_capacity(system_capacity), 279 | 'module_type': PVWatts.validate_module_type(module_type), 280 | 'losses': PVWatts.validate_losses(losses), 281 | 'array_type': PVWatts.validate_array_type(array_type), 282 | 'tilt': PVWatts.validate_tilt(tilt), 283 | 'azimuth': PVWatts.validate_azimuth(azimuth), 284 | 'address': address, 285 | 'lat': PVWatts.validate_lat(lat), 286 | 'lon': PVWatts.validate_lon(lon), 287 | 'file_id': file_id, 288 | 'dataset': PVWatts.validate_dataset(dataset), 289 | 'radius': PVWatts.validate_radius(radius), 290 | 'timeframe': PVWatts.validate_timeframe(timeframe), 291 | 'dc_ac_ratio': PVWatts.validate_dc_ac_ratio(dc_ac_ratio), 292 | 'gcr': PVWatts.validate_gcr(gcr), 293 | 'inv_eff': PVWatts.validate_inv_eff(inv_eff), 294 | 'callback': callback 295 | } 296 | 297 | params['api_key'] = PVWatts.api_key 298 | 299 | if self is not None: 300 | return PVWattsResult(self.get_data(params=params)) 301 | return PVWattsResult(PVWatts.get_data(params=params)) 302 | --------------------------------------------------------------------------------