├── .github └── workflows │ ├── python-app.yml │ └── python-publish.yml ├── .gitignore ├── BunnyCDN ├── CDN.py ├── Storage.py └── __init__.py ├── LICENSE ├── README.md └── setup.py /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python application 5 | 6 | on: 7 | push: 8 | branches: [ '*' ] 9 | pull_request: 10 | branches: [ '*' ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Python 3.9 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: 3.9 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install flake8 27 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 28 | - name: Check Syntax 29 | run: | 30 | # stop the build if there are Python syntax errors or undefined names 31 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 32 | - name: Lint with Flake8 33 | run: | 34 | flake8 . --count --max-line-length=127 --statistics 35 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | python setup.py sdist bdist_wheel 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /BunnyCDN/CDN.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/mathrithms/BunnyCDN-Python-Lib/blob/master/BunnyCDN/CDN.py 2 | import json 3 | import requests 4 | from requests.exceptions import HTTPError 5 | from urllib import parse 6 | 7 | 8 | class CDN: 9 | # initializer function 10 | def __init__(self, api_key): 11 | 12 | """ 13 | Parameters 14 | ---------- 15 | api_key : String 16 | BunnyCDN account api key 17 | 18 | """ 19 | assert api_key != "", "api_key for the account must be specified" 20 | self.headers = { 21 | "AccessKey": api_key, 22 | "Content-Type": "application/json", 23 | "Accept": "application/json", 24 | } 25 | self.base_url = "https://bunnycdn.com/api/" 26 | 27 | def _Geturl(self, Task_name): 28 | """ 29 | This function is helper for the other methods in code 30 | to create appropriate url. 31 | 32 | """ 33 | if Task_name[0] == "/": 34 | if Task_name[-1] == "/": 35 | url = self.base_url + parse.quote(Task_name[1:-1]) 36 | else: 37 | url = self.base_url + parse.quote(Task_name[1:]) 38 | elif Task_name[-1] == "/": 39 | url = self.base_url + parse.quote(Task_name[1:-1]) 40 | else: 41 | url = self.base_url + parse.quote(Task_name) 42 | return url 43 | 44 | def AddCertificate(self, 45 | PullZoneId, 46 | Hostname, 47 | Certificate, 48 | CertificateKey): 49 | """ 50 | This function adds custom certificate to the given pullzone 51 | 52 | Parameters 53 | ---------- 54 | PullZoneId : int64 55 | The ID of the Pull Zone to which the certificate 56 | will be added. 57 | 58 | Hostname : string 59 | The hostname to which the certificate belongs to. 60 | 61 | Certificate : string 62 | A base64 encoded binary certificate file data 63 | Value must be of format 'base64' 64 | 65 | CertificateKey : string 66 | A base64 encoded binary certificate key file data 67 | Value must be of format 'base64' 68 | """ 69 | values = json.dumps( 70 | { 71 | "PullZoneId": PullZoneId, 72 | "Hostname": Hostname, 73 | "Certificate": Certificate, 74 | "CertificateKey": CertificateKey, 75 | } 76 | ) 77 | 78 | try: 79 | response = requests.post( 80 | self._Geturl("pullzone/addCertificate"), 81 | data=values, 82 | headers=self.headers, 83 | ) 84 | response.raise_for_status() 85 | except HTTPError as http: 86 | return {"status": "error", 87 | "HTTP": response.status_code, 88 | "msg": http} 89 | except Exception as err: 90 | return {"status": "error", 91 | "HTTP": response.status_code, 92 | "msg": err} 93 | else: 94 | return { 95 | "status": "success", 96 | "HTTP": response.status_code, 97 | "msg": f"Certificated Added successfully Hostname:{Hostname}", 98 | } 99 | 100 | def AddBlockedIp(self, PullZoneId, BlockedIp): 101 | """ 102 | This method adds an IP to the list of blocked IPs that are not 103 | allowed to access the zone. 104 | 105 | Parameters 106 | ---------- 107 | PullZoneId : int64 108 | The ID of the Pull Zone to which the IP block 109 | will be added. 110 | BlockedIP : string 111 | The IP address that will be blocked 112 | """ 113 | values = json.dumps( 114 | {"PullZoneId": PullZoneId, 115 | "BlockedIp": BlockedIp} 116 | ) 117 | 118 | try: 119 | response = requests.post( 120 | self._Geturl("pullzone/addBlockedIp"), data=values, 121 | headers=self.headers 122 | ) 123 | response.raise_for_status() 124 | except HTTPError as http: 125 | return {"status": "error", 126 | "HTTP": response.status_code, 127 | "msg": http} 128 | except Exception as err: 129 | return {"status": "error", 130 | "HTTP": response.status_code, 131 | "msg": err} 132 | else: 133 | return { 134 | "status": "success", 135 | "HTTP": response.status_code, 136 | "msg": "Ip successfully added to list of blocked IPs", 137 | } 138 | 139 | def RemoveBlockedIp(self, PullZoneId, BlockedIp): 140 | """ 141 | This method removes mentioned IP from the list of blocked IPs 142 | that are not allowed to access the zone. 143 | 144 | Parameters 145 | ---------- 146 | PullZoneId : int64 147 | The ID of the Pull Zone to which the 148 | IP block will be added. 149 | BlockedIP : string 150 | The IP address that will be blocked 151 | """ 152 | values = json.dumps({"PullZoneId": PullZoneId, "BlockedIp": BlockedIp}) 153 | 154 | try: 155 | response = requests.post( 156 | self._Geturl("pullzone/removeBlockedIp"), 157 | data=values, 158 | headers=self.headers, 159 | ) 160 | response.raise_for_status() 161 | except HTTPError as http: 162 | return {"status": "error", 163 | "HTTP": response.status_code, 164 | "msg": http} 165 | except Exception as err: 166 | return {"status": "error", 167 | "HTTP": response.status_code, 168 | "msg": err} 169 | else: 170 | return { 171 | "status": "success", 172 | "HTTP": response.status_code, 173 | "msg": "Ip removed from blocked IPs list " 174 | } 175 | 176 | def StorageZoneData(self): 177 | """ 178 | This function returns a list of details of each storage zones 179 | in user's account 180 | 181 | """ 182 | try: 183 | response = requests.get(self._Geturl("storagezone"), 184 | headers=self.headers) 185 | response.raise_for_status() 186 | except HTTPError as http: 187 | return {"status": "error", 188 | "HTTP": response.status_code, 189 | "msg": http} 190 | except Exception as err: 191 | return {"status": "error", 192 | "HTTP": response.status_code, 193 | "msg": err} 194 | else: 195 | storage_summary = [] 196 | for storagezone in response.json(): 197 | storage_zone_details = {} 198 | storage_zone_details["Id"] = storagezone["Id"] 199 | storage_zone_details["Storage_Zone_Name"] = storagezone["Name"] 200 | storage_zone_details["Usage"] = storagezone["StorageUsed"] 201 | hostnames = [] 202 | pullzone = [] 203 | for data in storagezone["PullZones"]: 204 | pullzone.append(data["Name"]) 205 | for host_name in data["Hostnames"]: 206 | hostnames.append(host_name["Value"]) 207 | storage_zone_details["host_names"] = hostnames 208 | storage_zone_details["PullZones"] = pullzone 209 | storage_summary.append(storage_zone_details) 210 | return storage_summary 211 | 212 | def StorageZoneList(self): 213 | """ 214 | Returns list of dictionaries containing storage zone 215 | name and storage zone id 216 | """ 217 | try: 218 | response = requests.get(self._Geturl("storagezone"), 219 | headers=self.headers) 220 | response.raise_for_status() 221 | except HTTPError as http: 222 | return {"status": "error", 223 | "HTTP": response.status_code, 224 | "msg": http} 225 | except Exception as err: 226 | return {"status": "error", 227 | "HTTP": response.status_code, 228 | "msg": err} 229 | else: 230 | storage_list = [] 231 | for storagezone in response.json(): 232 | storage_list.append({storagezone["Name"]: storagezone["Id"]}) 233 | return storage_list 234 | 235 | def AddStorageZone( 236 | self, storage_zone_name, storage_zone_region="DE", 237 | ReplicationRegions=["DE"] 238 | ): 239 | """ 240 | This method creates a new storage zone 241 | 242 | Parameters 243 | ---------- 244 | storage_zone_name : string 245 | The name of the storage zone 246 | 1.Matches regex pattern: ^[a-zA-Z0-9]+$ 247 | 2.Length of string must be less than, 248 | or equal to 20 249 | 3.Length of string must be 250 | greater than, or equal to 3 251 | 252 | storage_zone_region : string 253 | (optional) The main region code of storage zone 254 | 1.Matches regex pattern: ^[a-zA-Z0-9]+$ 255 | 2.Length of string must be less than, 256 | or equal to 2 257 | 3.Length of string must be 258 | greater than, or equal to 2 259 | 260 | ReplicationsRegions : array 261 | (optional) The list of active replication regions 262 | for the zone 263 | 264 | """ 265 | values = json.dumps( 266 | { 267 | "Name": storage_zone_name, 268 | "Region": storage_zone_region, 269 | "ReplicationRegions": ReplicationRegions, 270 | } 271 | ) 272 | try: 273 | response = requests.post( 274 | self._Geturl("storagezone"), data=values, headers=self.headers 275 | ) 276 | response.raise_for_status() 277 | except HTTPError as http: 278 | return {"status": "error", 279 | "HTTP": response.status_code, 280 | "msg": http} 281 | except Exception as err: 282 | return {"status": "error", 283 | "HTTP": response.status_code, 284 | "msg": err} 285 | else: 286 | return { 287 | "status": "success", 288 | "HTTP": response.status_code, 289 | "msg": response.json(), 290 | } 291 | 292 | def GetStorageZone(self, storage_zone_id): 293 | 294 | """ 295 | This function returns details about the storage zone 296 | whose id is mentioned 297 | 298 | Parameters 299 | ---------- 300 | storage_zone_id : int64 301 | The ID of the Storage Zone to return 302 | 303 | """ 304 | try: 305 | response = requests.get( 306 | self._Geturl(f"storagezone/{storage_zone_id}"), 307 | headers=self.headers 308 | ) 309 | response.raise_for_status() 310 | except HTTPError as http: 311 | return {"status": "error", 312 | "HTTP": response.status_code, 313 | "msg": http} 314 | except Exception as err: 315 | return {"status": "error", 316 | "HTTP": response.status_code, 317 | "msg": err} 318 | else: 319 | return response.json() 320 | 321 | def DeleteStorageZone(self, storage_zone_id): 322 | """ 323 | This method deletes the Storage zone with id : storage_zone_id 324 | 325 | Parameters 326 | ---------- 327 | storage_zone_id : int64 328 | The ID of the storage zone that should be deleted 329 | """ 330 | try: 331 | response = requests.delete( 332 | self._Geturl(f"storagezone/{storage_zone_id}"), 333 | headers=self.headers 334 | ) 335 | response.raise_for_status() 336 | except HTTPError as http: 337 | return {"status": "error", 338 | "HTTP": response.status_code, 339 | "msg": http} 340 | except Exception as err: 341 | return {"status": "error", 342 | "HTTP": response.status_code, 343 | "msg": err} 344 | else: 345 | return { 346 | "status": "Success", 347 | "HTTP": response.status_code, 348 | "msg": "Deleted Storagezone successfully", 349 | } 350 | 351 | def PurgeUrlCache(self, url): 352 | """ 353 | This method purges the given URL from our edge server cache. 354 | 355 | Parameters 356 | ---------- 357 | url : string 358 | The URL of the file that will be purged. 359 | Use a CDN enabled URL such as http://myzone.b-cdn.net/style.css 360 | """ 361 | try: 362 | response = requests.post( 363 | self._Geturl("purge"), params={"url": url}, 364 | headers=self.headers 365 | ) 366 | response.raise_for_status() 367 | except HTTPError as http: 368 | return {"status": "error", 369 | "HTTP": response.status_code, 370 | "msg": http} 371 | except Exception as err: 372 | return {"status": "error", 373 | "HTTP": response.status_code, 374 | "msg": err} 375 | else: 376 | return { 377 | "status": "Success", 378 | "HTTP": response.status_code, 379 | "msg": f"Purged Cache for url:{url}", 380 | } 381 | 382 | def Billing(self): 383 | """ 384 | This method returns the current billing summary of the account 385 | 386 | """ 387 | try: 388 | response = requests.get(self._Geturl("billing"), 389 | headers=self.headers) 390 | response.raise_for_status() 391 | except HTTPError as http: 392 | return {"status": "error", 393 | "HTTP": response.status_code, 394 | "msg": http} 395 | except Exception as err: 396 | return {"status": "error", 397 | "HTTP": response.status_code, 398 | "msg": err} 399 | else: 400 | return response.json() 401 | 402 | def ApplyCode(self, couponCode): 403 | """ 404 | This method applys promo code to the account 405 | 406 | Parameters 407 | ---------- 408 | couponCode : The promo code that will be applied 409 | 410 | """ 411 | try: 412 | response = requests.get( 413 | self._Geturl("billing/applycode"), 414 | params={"couponCode": couponCode}, 415 | headers=self.headers, 416 | ) 417 | response.raise_for_status() 418 | except HTTPError as http: 419 | return {"status": "error", 420 | "HTTP": response.status_code, 421 | "msg": http} 422 | except Exception as err: 423 | return {"status": "error", 424 | "HTTP": response.status_code, 425 | "msg": err} 426 | else: 427 | return { 428 | "status": "success", 429 | "HTTP": response.status_code, 430 | "msg": f"Applied promo code:{couponCode} successfully", 431 | } 432 | 433 | def Stats( 434 | self, 435 | dateFrom=None, 436 | dateTo=None, 437 | pullZone=None, 438 | serverZoneId=None, 439 | loadErrors=True, 440 | ): 441 | """ 442 | This method returns the statistics associated 443 | with your account as json object 444 | 445 | Parameters 446 | ---------- 447 | 448 | dateFrom : string 449 | (optional) The start date of the range the statistics 450 | should be returned for. Format: yyyy-mm-dd 451 | 452 | dateTo : string 453 | (optional) The end date of the range the statistics 454 | should be returned for. Format: yyyy-MM-dd 455 | 456 | pullZone : int64 457 | (optional) The ID of the Pull Zone for which the 458 | statistics should be returned 459 | 460 | serverZoneId : int64 461 | (optional) The server zone for which the data 462 | should be returned. 463 | 464 | loadErrors : boolean 465 | (optional) Set to true by default 466 | """ 467 | 468 | params = { 469 | "dateFrom": dateFrom, 470 | "dateTo": dateTo, 471 | "pullZone": pullZone, 472 | "serverZoneId": serverZoneId, 473 | "loadErrors": loadErrors, 474 | } 475 | 476 | try: 477 | response = requests.get( 478 | self._Geturl("statistics"), params=params, headers=self.headers 479 | ) 480 | response.raise_for_status() 481 | except HTTPError as http: 482 | return {"status": "error", 483 | "HTTP": response.status_code, 484 | "msg": http} 485 | except Exception as err: 486 | return {"status": "error", 487 | "HTTP": response.status_code, 488 | "msg": err} 489 | else: 490 | return response.json() 491 | 492 | def GetPullZoneList(self): 493 | """ 494 | This function fetches the list of pullzones in the User's Account 495 | 496 | Parameters 497 | ---------- 498 | None 499 | """ 500 | try: 501 | response = requests.get(self._Geturl("pullzone"), 502 | headers=self.headers) 503 | response.raise_for_status() 504 | except HTTPError as http: 505 | return {"status": "error", 506 | "HTTP": response.status_code, 507 | "msg": http} 508 | except Exception as err: 509 | return {"status": "error", 510 | "HTTP": response.status_code, 511 | "msg": err} 512 | else: 513 | pullzone_list = [] 514 | for pullzone in response.json(): 515 | pullzone_list.append({pullzone["Name"]: pullzone["Id"]}) 516 | return pullzone_list 517 | 518 | def CreatePullZone(self, Name, OriginURL, Type, StorageZoneId=None): 519 | """ 520 | This function creates a new Pulzone in User's Account 521 | Parameters 522 | ---------- 523 | Name : string 524 | The name of the new pull zone 525 | 526 | Type : string 527 | number 528 | The pricing type of the pull zone to be added. 529 | 0 = Standard, 1 = High Volume 530 | 531 | OriginURL : string 532 | The origin URL where the pull zone files 533 | are pulled from. 534 | 535 | StorageZoneId : int64 536 | The ID(number) of the storage zone to which 537 | the pull zone will be linked (Optional) 538 | 539 | """ 540 | 541 | if StorageZoneId is None: 542 | values = json.dumps( 543 | {"Name": Name, 544 | "Type": Type, 545 | "OriginURL": OriginURL} 546 | ) 547 | else: 548 | values = { 549 | "Name": Name, 550 | "Type": Type, 551 | "OriginURL": OriginURL, 552 | "StorageZoneId": StorageZoneId, 553 | } 554 | try: 555 | response = requests.post( 556 | self._Geturl("pullzone"), data=values, headers=self.headers 557 | ) 558 | response.raise_for_status() 559 | except HTTPError as http: 560 | return {"status": "error", 561 | "HTTP": response.status_code, 562 | "msg": http} 563 | except Exception as err: 564 | return {"status": "error", 565 | "HTTP": response.status_code, 566 | "msg": err} 567 | else: 568 | return response.json() 569 | 570 | def GetPullZone(self, PullZoneID): 571 | """ 572 | This function returns the pullzone details 573 | for the zone with the given ID 574 | 575 | Parameters 576 | ---------- 577 | PullZoneID : int64 578 | The ID (number) of the pullzone to return 579 | """ 580 | try: 581 | response = requests.get( 582 | self._Geturl(f"pullzone/{PullZoneID}"), headers=self.headers 583 | ) 584 | response.raise_for_status() 585 | except HTTPError as http: 586 | return {"status": "error", 587 | "HTTP": response.status_code, 588 | "msg": http} 589 | except Exception as err: 590 | return {"status": "error", 591 | "HTTP": response.status_code, 592 | "msg": err} 593 | else: 594 | return response.json() 595 | 596 | def UpdatePullZone( 597 | self, 598 | PullZoneID, 599 | OriginUrl, 600 | AllowedReferrers, 601 | BlockedIps, 602 | EnableCacheSlice, 603 | EnableGeoZoneUS, 604 | EnableGeoZoneEU, 605 | EnableGeoZoneASIA, 606 | EnableGeoZoneSA, 607 | EnableGeoZoneAF, 608 | ZoneSecurityEnabled, 609 | IncludeHashRemoteIP, 610 | IgnoreQueryStrings, 611 | MonthlyBandwidthLimit, 612 | OriginHeaderExtensions, 613 | EnableOriginHeader, 614 | BlockRootPathAccess, 615 | EnableWebpVary, 616 | EnableHostnameVary, 617 | EnableCountryCodeVary, 618 | EnableLogging, 619 | DisableCookies, 620 | BudgetRedirectedCountries, 621 | BlockedCountries, 622 | EnableOriginShield, 623 | EnableQueryStringOrdering, 624 | CacheErrorResponses, 625 | OriginShieldZoneCode, 626 | AddCanonicalHeader, 627 | CacheControlMaxAgeOverride, 628 | AddHostHeader, 629 | AWSSigningEnabled, 630 | AWSSigningKey, 631 | AWSSigningRegionName, 632 | AWSSigningSecret, 633 | EnableTLS1, 634 | LoggingSaveToStorage, 635 | LoggingStorageZoneId, 636 | LogForwardingEnabled, 637 | LogForwardingHostname, 638 | LogForwardingPort, 639 | LogForwardingToken, 640 | ): 641 | """ 642 | This function updates the pullzone with the given ID 643 | 644 | Parameters 645 | ---------- 646 | PullZoneID : int64 647 | The ID (number) of the 648 | pullzone to update 649 | 650 | OriginUrl : string 651 | The origin URL of the pull zone 652 | 653 | AllowedReferrers : array 654 | 655 | BlockedIps : array 656 | 657 | EnableCacheSlice : boolean 658 | If enabled, the cached data will be 659 | stored in small chunks and allow the 660 | server to process byte range requests 661 | even for uncached files. 662 | 663 | EnableGeoZoneUS : boolean 664 | If enabled, the zone will serve data 665 | through our United States PoPs. 666 | 667 | EnableGeoZoneEU : boolean 668 | If enabled, the zone will serve data 669 | through our European PoPs. 670 | 671 | EnableGeoZoneASIA : boolean 672 | If enabled, the zone will serve data 673 | through our Asian and Oceanian PoPs. 674 | 675 | EnableGeoZoneSA : boolean 676 | If enabled, the zone will serve data 677 | through our South American PoPs. 678 | 679 | EnableGeoZoneAF : boolean 680 | If enabled, the zone will serve data 681 | through our African PoPs. 682 | 683 | ZoneSecurityEnabled : boolean 684 | If enabled, the zone will be secured 685 | using token authentication. 686 | 687 | IncludeHashRemoteIP : boolean 688 | If enabled, the zone token 689 | authentication hash will 690 | include the remote IP. 691 | 692 | IgnoreQueryStrings : boolean 693 | If enabled, the URLs will ignore any 694 | kind of query strings when looking 695 | for and storing cached files 696 | 697 | MonthlyBandwidthLimit : number 698 | Set the monthly pull zone 699 | bandwidth limit in bytes. 700 | 701 | OriginHeaderExtensions : array 702 | 703 | EnableOriginHeader : boolean 704 | If enabled the CORS headers will be 705 | returned with the requests to CORS 706 | enabled extensions. 707 | 708 | BlockRootPathAccess : boolean 709 | Set to true if you want to block all 710 | requests going to root directories 711 | instead of files. 712 | 713 | EnableWebpVary : boolean 714 | If enabled, the zone will dynamically 715 | vary the cached based on WebP support 716 | 717 | EnableHostnameVary : boolean 718 | Set to true if the cache files should 719 | vary based on the request hostname 720 | 721 | EnableCountryCodeVary : boolean 722 | Set to true if the cache files should 723 | vary based on the country code 724 | 725 | EnableLogging : boolean 726 | Set to true if the logging for the 727 | zone should be enabled 728 | 729 | DisableCookies : boolean 730 | If true, the cookies are disabled 731 | for the pull zone 732 | 733 | BudgetRedirectedCountries : array 734 | 735 | BlockedCountries : array 736 | 737 | EnableOriginShield : boolean 738 | Set to true to enable the origin 739 | shield for this zone 740 | 741 | EnableQueryStringOrdering : boolean 742 | Set to true to enable query string 743 | sorting when caching files 744 | 745 | CacheErrorResponses : boolean 746 | Set to true to temporary cache error 747 | responses from the origins erver 748 | 749 | OriginShieldZoneCode : string 750 | The zone code of the origin 751 | shield location 752 | 753 | AddCanonicalHeader : boolean 754 | True if the zone should return an 755 | automatically generated canonical 756 | header 757 | 758 | CacheControlMaxAgeOverride : number 759 | Set the cache control override, 760 | set to 0 to respect the origin headers 761 | 762 | CacheControlBrowserMaxAgeOverride : number 763 | Set the browser cache control 764 | override,set to -1 for this 765 | to match the internal cache-control 766 | 767 | AddHostHeader : boolean 768 | If enabled, the original host header 769 | of the request will be forwarded to 770 | the origin server. 771 | 772 | AWSSigningEnabled : boolean 773 | If enabled, this will send Amazon S3 774 | authentication headers back to 775 | the origin. 776 | 777 | AWSSigningKey : string 778 | The Amazon S3 signing key used 779 | to sign origin requests 780 | 781 | AWSSigningRegionName : string 782 | The Amazon S3 region name used 783 | to sign origin requests 784 | 785 | AWSSigningSecret : string 786 | The Amazon S3 secret used to 787 | sign origin requests 788 | 789 | EnableTLS1 : boolean 790 | True if the zone should allow legacy 791 | TLS 1 connections 792 | 793 | EnableTLS1_1 : boolean 794 | True if the zone should allow legacy 795 | TLS 1.1 connections 796 | 797 | LoggingSaveToStorage : boolean 798 | True to enable permanent log storage. 799 | This must be sent together with a 800 | valid LoggingStorageZoneId property. 801 | 802 | LoggingStorageZoneId : number 803 | The ID of the permanent log 804 | storage zone. 805 | 806 | LogForwardingEnabled : boolean 807 | True if the log forwarding feature 808 | should be enabled. 809 | 810 | LogForwardingHostname : string 811 | The hostname of the log forwarding 812 | endpoint. 813 | 814 | LogForwardingPort : number 815 | The port of the log forwarding 816 | endpoint. 817 | 818 | LogForwardingToken : string 819 | The authentication token for the log 820 | forwarding endpoint. 821 | 822 | """ 823 | values = json.dumps( 824 | { 825 | "PullZoneID": PullZoneID, 826 | "OriginUrl": OriginUrl, 827 | "AllowedReferrers": AllowedReferrers, 828 | "BlockedIps": BlockedIps, 829 | "EnableCacheSlice": EnableCacheSlice, 830 | "EnableGeoZoneUS": EnableGeoZoneUS, 831 | "EnableGeoZoneEU": EnableGeoZoneEU, 832 | "EnableGeoZoneASIA": EnableGeoZoneASIA, 833 | "EnableGeoZoneSA": EnableGeoZoneSA, 834 | "EnableGeoZoneAF": EnableGeoZoneAF, 835 | "ZoneSecurityEnabled": ZoneSecurityEnabled, 836 | "ZoneSecurityIncludeHashRemoteIP": IncludeHashRemoteIP, 837 | "IgnoreQueryStrings": IgnoreQueryStrings, 838 | "MonthlyBandwidthLimit": MonthlyBandwidthLimit, 839 | "AccessControlOriginHeaderExtensions": OriginHeaderExtensions, 840 | "EnableAccessControlOriginHeader": EnableOriginHeader, 841 | "BlockRootPathAccess": BlockRootPathAccess, 842 | "EnableWebpVary": EnableWebpVary, 843 | "EnableHostnameVary": EnableHostnameVary, 844 | "EnableCountryCodeVary": EnableCountryCodeVary, 845 | "EnableLogging": EnableLogging, 846 | "DisableCookies": DisableCookies, 847 | "BudgetRedirectedCountries": BudgetRedirectedCountries, 848 | "BlockedCountries": BlockedCountries, 849 | "EnableOriginShield": EnableOriginShield, 850 | "EnableQueryStringOrdering": EnableQueryStringOrdering, 851 | "CacheErrorResponses": CacheErrorResponses, 852 | "OriginShieldZoneCode": OriginShieldZoneCode, 853 | "AddCanonicalHeader": AddCanonicalHeader, 854 | "CacheControlMaxAgeOverride": CacheControlMaxAgeOverride, 855 | "AddHostHeader": AddHostHeader, 856 | "AWSSigningEnabled": AWSSigningEnabled, 857 | "AWSSigningKey": AWSSigningKey, 858 | "AWSSigningRegionName": AWSSigningRegionName, 859 | "AWSSigningSecret": AWSSigningSecret, 860 | "EnableTLS1": EnableTLS1, 861 | "LoggingSaveToStorage": LoggingSaveToStorage, 862 | "LoggingStorageZoneId": LoggingStorageZoneId, 863 | "LogForwardingEnabled": LogForwardingEnabled, 864 | "LogForwardingHostname": LogForwardingHostname, 865 | "LogForwardingPort": LogForwardingPort, 866 | "LogForwardingToken": LogForwardingToken, 867 | } 868 | ) 869 | try: 870 | response = requests.post( 871 | self._Geturl(f"pullzone/{PullZoneID}"), 872 | data=values, 873 | headers=self.headers 874 | ) 875 | response.raise_for_status() 876 | except HTTPError as http: 877 | return {"status": "error", 878 | "HTTP": response.status_code, 879 | "msg": http} 880 | except Exception as err: 881 | return {"status": "error", 882 | "HTTP": response.status_code, 883 | "msg": err} 884 | else: 885 | return { 886 | "status": "success", 887 | "HTTP": response.status_code, 888 | "msg": "Update successful", 889 | } 890 | 891 | def DeletePullZone(self, PullZoneID): 892 | """ 893 | This function deletes the pullzone with the given ID 894 | 895 | Parameters 896 | ---------- 897 | PullZoneID : int64 898 | The ID (number) of the pullzone to delete 899 | 900 | """ 901 | try: 902 | response = requests.delete( 903 | self._Geturl(f"pullzone/{PullZoneID}"), headers=self.headers 904 | ) 905 | response.raise_for_status() 906 | except HTTPError as http: 907 | return {"status": "error", 908 | "HTTP": response.status_code, 909 | "msg": http} 910 | except Exception as err: 911 | return {"status": "error", 912 | "HTTP": response.status_code, 913 | "msg": err} 914 | else: 915 | return { 916 | "status": "success", 917 | "HTTP": response.status_code, 918 | "msg": "Successfully Deleted Pullzone", 919 | } 920 | 921 | def PurgePullZoneCache(self, PullZoneID): 922 | """ 923 | This function purges the full cache of given pullzone 924 | 925 | Parameters 926 | ---------- 927 | PullZoneID : int64 928 | The ID (number) of the pullzone 929 | who's cache is to be Purged 930 | """ 931 | try: 932 | response = requests.post( 933 | self._Geturl(f"pullzone/{PullZoneID}/purgeCache"), 934 | headers=self.headers 935 | ) 936 | response.raise_for_status() 937 | except HTTPError as http: 938 | return {"status": "error", 939 | "HTTP": response.status_code, 940 | "msg": http} 941 | except Exception as err: 942 | return {"status": "error", 943 | "HTTP": response.status_code, 944 | "msg": err} 945 | else: 946 | return { 947 | "status": "success", 948 | "HTTP": response.status_code, 949 | "msg": "successfully purged the cache of the given pullzone ", 950 | } 951 | 952 | def AddorUpdateEdgerule( 953 | self, 954 | PullZoneID, 955 | ActionParameter1, 956 | ActionParameter2, 957 | Enabled, 958 | Description, 959 | ActionType, 960 | TriggerMatchingType, 961 | Triggers, 962 | GUID=None, 963 | ExtraActions=None, 964 | ): 965 | 966 | """ 967 | This function Adds or Updates the Edgerule 968 | 969 | Parameters 970 | ---------- 971 | PullZoneID :int64 972 | The Id(number) of the pullzone whose edgerule 973 | is to be updated or where new edgerule has to 974 | be added 975 | 976 | GUID :number 977 | Guid of the edgerule 978 | (exclude when adding a new edgerule) 979 | 980 | ActionParameter1 :string 981 | The action parameter 1 of the edge rule 982 | 983 | ActionParameter2 :string 984 | The action parameter 2 of the edge rule 985 | 986 | Enabled :boolean 987 | The boolean 988 | 989 | Description :string 990 | The description of the Edge rule 991 | 992 | ActionType :number 993 | The action type of the edge rule. 994 | The possible values are: ForceSSL = 0 995 | Redirect = 1,OriginUrl = 2 996 | OverrideCacheTime = 3,BlockRequest = 4, 997 | SetResponseHeader = 5,SetRequestHeader = 6, 998 | ForceDownload =7,DisableTokenAuthentication=8, 999 | EnableTokenAuthentication = 9 1000 | 1001 | TriggerMatchingType :number 1002 | Trigger matching type 1003 | 1004 | Triggers :array 1005 | 1006 | ExtraActions :list of arrays 1007 | (optional) Extra actions to be added to the 1008 | edge rule 1009 | """ 1010 | success_msg = "successfully added edgerule" 1011 | request_payload = { 1012 | "ActionParameter1": ActionParameter1, 1013 | "ActionParameter2": ActionParameter2, 1014 | "Enabled": Enabled, 1015 | "Description": Description, 1016 | "ActionType": ActionType, 1017 | "TriggerMatchingType": TriggerMatchingType, 1018 | "Triggers": Triggers, 1019 | } 1020 | if GUID: 1021 | request_payload["GUID"] = GUID 1022 | success_msg = "successfully updated edgerule" 1023 | if ExtraActions: 1024 | request_payload["ExtraActions"] = ExtraActions 1025 | try: 1026 | response = requests.post( 1027 | self._Geturl(f"pullzone/{PullZoneID}/edgerules/addOrUpdate"), 1028 | data=json.dumps(request_payload), 1029 | headers=self.headers, 1030 | ) 1031 | response.raise_for_status() 1032 | except HTTPError as http: 1033 | return {"status": "error", 1034 | "HTTP": response.status_code, 1035 | "msg": http} 1036 | except Exception as err: 1037 | return {"status": "error", 1038 | "HTTP": response.status_code, 1039 | "msg": err} 1040 | else: 1041 | return { 1042 | "status": "success", 1043 | "HTTP": response.status_code, 1044 | "msg": success_msg, 1045 | } 1046 | 1047 | 1048 | def DeleteEdgeRule(self, PullZoneID, EdgeRuleID): 1049 | """ 1050 | This function deletes the edgerule 1051 | 1052 | Parameters 1053 | --------- 1054 | PullZoneID :number 1055 | ID of the pullzone that holds the edgerule 1056 | 1057 | EdgeRuleID :string 1058 | ID of the edgerule to be deleted 1059 | 1060 | """ 1061 | try: 1062 | response = requests.delete( 1063 | self._Geturl(f"pullzone/{PullZoneID}/edgerules/{EdgeRuleID}"), 1064 | headers=self.headers, 1065 | ) 1066 | response.raise_for_status() 1067 | except HTTPError as http: 1068 | return {"status": "error", 1069 | "HTTP": response.status_code, 1070 | "msg": http} 1071 | except Exception as err: 1072 | return {"status": "error", 1073 | "HTTP": response.status_code, 1074 | "msg": err} 1075 | else: 1076 | return { 1077 | "status": "success", 1078 | "HTTP": response.status_code, 1079 | "msg": "Successfully Deleted edgerule", 1080 | } 1081 | 1082 | def AddCustomHostname(self, PullZoneID, Hostname): 1083 | """ 1084 | This function is used to add custom hostname to a pullzone 1085 | 1086 | Parameters 1087 | ---------- 1088 | PullZoneID: : int64 1089 | ID of the pullzone to which hostname 1090 | will be added 1091 | 1092 | Hostname: : string 1093 | The hostname that will be registered 1094 | 1095 | """ 1096 | values = json.dumps({"Hostname": Hostname}) 1097 | 1098 | try: 1099 | response = requests.post( 1100 | self._Geturl(f"pullzone/{PullZoneID}/addHostname"), 1101 | data=values, 1102 | headers=self.headers 1103 | ) 1104 | response.raise_for_status() 1105 | except HTTPError as http: 1106 | return {"status": "error", 1107 | "HTTP": response.status_code, 1108 | "msg": http} 1109 | except Exception as err: 1110 | return {"status": "error", 1111 | "HTTP": response.status_code, 1112 | "msg": err} 1113 | else: 1114 | return { 1115 | "status": "success", 1116 | "HTTP": response.status_code, 1117 | "msg": "Update was Successfull", 1118 | } 1119 | 1120 | def DeleteCustomHostname(self, PullZoneID, Hostname): 1121 | 1122 | """ 1123 | This function is used to delete custom hostname of a pullzone 1124 | 1125 | Parameters 1126 | ---------- 1127 | PullZoneID: :number 1128 | ID of the pullzone of which custom hostname 1129 | will be delted 1130 | 1131 | Hostname: :string 1132 | The hostname that will be deleted 1133 | 1134 | """ 1135 | params = {"Hostname": Hostname} 1136 | try: 1137 | response = requests.delete( 1138 | self._Geturl(f"pullzone/{PullZoneID}/removeHostname"), 1139 | json=params, 1140 | headers=self.headers, 1141 | ) 1142 | response.raise_for_status() 1143 | except HTTPError as http: 1144 | return {"status": "error", 1145 | "HTTP": response.status_code, 1146 | "msg": http} 1147 | except Exception as err: 1148 | return {"status": "error", 1149 | "HTTP": response.status_code, 1150 | "msg": err} 1151 | else: 1152 | return { 1153 | "status": "success", 1154 | "HTTP": response.status_code, 1155 | "msg": "Successfully Deleted Hostname", 1156 | } 1157 | 1158 | def SetForceSSL(self, PullZoneID, Hostname, ForceSSL): 1159 | """ 1160 | This function is used to enable or disable the ForceSSL 1161 | setting for a pulzone 1162 | 1163 | Parameters 1164 | ---------- 1165 | PullZoneID :number 1166 | The id of the pull zone that the hostname 1167 | belongs to 1168 | 1169 | Hostname :string 1170 | The hostname that will be updated 1171 | 1172 | ForceSSL :boolean 1173 | If enabled, the zone will force redirect 1174 | to the SSL version of the URLs 1175 | 1176 | """ 1177 | values = json.dumps( 1178 | {"Hostname": Hostname, 1179 | "ForceSSL": ForceSSL} 1180 | ) 1181 | try: 1182 | response = requests.post( 1183 | self._Geturl(f"pullzone/{PullZoneID}/setForceSSL"), 1184 | data=values, 1185 | headers=self.headers 1186 | ) 1187 | response.raise_for_status() 1188 | except HTTPError as http: 1189 | return {"status": "error", 1190 | "HTTP": response.status_code, 1191 | "msg": http} 1192 | except Exception as err: 1193 | return {"status": "error", 1194 | "HTTP": response.status_code, 1195 | "msg": err} 1196 | else: 1197 | return { 1198 | "status": "success", 1199 | "HTTP": response.status_code, 1200 | "msg": "successfully added Hostname ", 1201 | } 1202 | 1203 | def LoadFreeCertificate(self, Hostname): 1204 | """ 1205 | This function Loads a FREE SSL Certificate to the domain 1206 | provided by Let's Encrypt 1207 | 1208 | Parameters 1209 | ---------- 1210 | Hostname : string 1211 | Hostname that the ForceSSL certificate 1212 | will be loaded for 1213 | 1214 | """ 1215 | try: 1216 | response = requests.get( 1217 | self._Geturl("pullzone/loadFreeCertificate"), 1218 | params={'hostname': Hostname}, 1219 | headers=self.headers, 1220 | ) 1221 | response.raise_for_status() 1222 | except HTTPError as http: 1223 | return {"status": "error", 1224 | "HTTP": response.status_code, 1225 | "msg": http} 1226 | except Exception as err: 1227 | return {"status": "error", 1228 | "HTTP": response.status_code, 1229 | "msg": err} 1230 | else: 1231 | return self.GetPullZoneList() 1232 | 1233 | def GetVideoLibrary(self, id): 1234 | ''' 1235 | Returns the Video Library details for the given ID 1236 | 1237 | Parameters 1238 | ---------- 1239 | id : number 1240 | The ID of the Video Library to return 1241 | 1242 | ''' 1243 | try: 1244 | response = requests.get( 1245 | self._Geturl("videolibrary"), params={'id': id}, 1246 | headers=self.headers, 1247 | ) 1248 | response.raise_for_status() 1249 | except HTTPError as http: 1250 | return {"status": "error", 1251 | "HTTP": response.status_code, 1252 | "msg": http} 1253 | except Exception as err: 1254 | return {"status": "error", 1255 | "HTTP": response.status_code, 1256 | "msg": err} 1257 | else: 1258 | return {"status": "success", 1259 | "HTTP": response.status_code, 1260 | "msg": response.json() 1261 | } 1262 | 1263 | def DeleteVideoLibrary(self, id): 1264 | ''' 1265 | Deletes the Video Library with the given ID 1266 | 1267 | Parameters 1268 | ---------- 1269 | id : number 1270 | The ID of the library that should be deleted 1271 | ''' 1272 | try: 1273 | response = requests.delete( 1274 | self._Geturl(f"videolibrary/{id}"), 1275 | headers=self.headers, 1276 | ) 1277 | response.raise_for_status() 1278 | except HTTPError as http: 1279 | return {"status": "error", 1280 | "HTTP": response.status_code, 1281 | "msg": http} 1282 | except Exception as err: 1283 | return {"status": "error", 1284 | "HTTP": response.status_code, 1285 | "msg": err} 1286 | else: 1287 | return {"status": "success", 1288 | "HTTP": response.status_code, 1289 | "msg": "Deleted Video Library" 1290 | } 1291 | -------------------------------------------------------------------------------- /BunnyCDN/Storage.py: -------------------------------------------------------------------------------- 1 | """This code is to use the BunnyCDN Storage API""" 2 | 3 | import os 4 | import requests 5 | from requests.exceptions import HTTPError 6 | from urllib import parse 7 | 8 | 9 | class Storage: 10 | 11 | # initializer for storage account 12 | 13 | def __init__(self, api_key, storage_zone, storage_zone_region="de"): 14 | """ 15 | Creates an object for using BunnyCDN Storage API 16 | Parameters 17 | ---------- 18 | api_key : String 19 | Your bunnycdn storage 20 | Apikey/FTP password of 21 | storage zone 22 | 23 | storage_zone : String 24 | Name of your storage zone 25 | 26 | storage_zone_region(optional parameter) : String 27 | The storage zone region code 28 | as per BunnyCDN 29 | """ 30 | self.headers = { 31 | # headers to be passed in HTTP requests 32 | "AccessKey": api_key, 33 | "Content-Type": "application/json", 34 | "Accept": "applcation/json", 35 | } 36 | 37 | # applying constraint that storage_zone must be specified 38 | assert storage_zone != "", "storage_zone is not specified/missing" 39 | 40 | # For generating base_url for sending requests 41 | if storage_zone_region == "de" or storage_zone_region == "": 42 | self.base_url = "https://storage.bunnycdn.com/" + storage_zone + "/" 43 | else: 44 | self.base_url = ( 45 | "https://" 46 | + storage_zone_region 47 | + ".storage.bunnycdn.com/" 48 | + storage_zone 49 | + "/" 50 | ) 51 | 52 | def DownloadFile(self, storage_path, download_path=os.getcwd()): 53 | """ 54 | This function will get the files and subfolders of storage zone mentioned in path 55 | and download it to the download_path location mentioned 56 | Parameters 57 | ---------- 58 | storage_path : String 59 | The path of the directory 60 | (including file name and excluding storage zone name) 61 | from which files are to be retrieved 62 | download_path : String 63 | The directory on local server to which downloaded file must be saved 64 | Note:For download_path instead of '\' '\\' should be used example: C:\\Users\\XYZ\\OneDrive 65 | """ 66 | 67 | assert ( 68 | storage_path != "" 69 | ), "storage_path must be specified" # to make sure storage_path is not null 70 | # to build correct url 71 | if storage_path[0] == "/": 72 | storage_path = storage_path[1:] 73 | if storage_path[-1] == "/": 74 | storage_path = storage_path[:-1] 75 | url = self.base_url + parse.quote(storage_path) 76 | file_name = url.split("/")[-1] # For storing file name 77 | 78 | # to return appropriate help messages if file is present or not and download file if present 79 | try: 80 | response = requests.get(url, headers=self.headers, stream=True) 81 | response.raise_for_status() 82 | except HTTPError as http: 83 | return { 84 | "status": "error", 85 | "HTTP": response.status_code, 86 | "msg": f"Http error occured {http}", 87 | } 88 | except Exception as err: 89 | return { 90 | "status": "error", 91 | "HTTP": response.status_code, 92 | "msg": f"error occured {err}", 93 | } 94 | else: 95 | download_path = os.path.join(download_path, file_name) 96 | # Downloading file 97 | with open(download_path, "wb") as file: 98 | 99 | for chunk in response.iter_content(chunk_size=1024): 100 | if chunk: 101 | file.write(chunk) 102 | return { 103 | "status": "success", 104 | "HTTP": response.status_code, 105 | "msg": "File downloaded Successfully", 106 | } 107 | 108 | def PutFile( 109 | self, 110 | file_name, 111 | storage_path=None, 112 | local_upload_file_path=os.getcwd(), 113 | ): 114 | 115 | """ 116 | This function uploads files to your BunnyCDN storage zone 117 | Parameters 118 | ---------- 119 | storage_path : String 120 | The path of directory in storage zone 121 | (including the name of file as desired and excluding storage zone name) 122 | to which file is to be uploaded 123 | file_name : String 124 | The name of the file as stored in local server 125 | local_upload_file_path : String 126 | The path of file as stored in local server(excluding file name) 127 | from where file is to be uploaded 128 | Examples 129 | -------- 130 | file_name : 'ABC.txt' 131 | local_upload_file_path : 'C:\\User\\Sample_Directory' 132 | storage_path : '/.txt' 133 | #Here .txt because the file being uploaded in example is txt 134 | """ 135 | local_upload_file_path = os.path.join(local_upload_file_path, file_name) 136 | 137 | # to build correct url 138 | if storage_path is not None and storage_path != "": 139 | if storage_path[0] == "/": 140 | storage_path = storage_path[1:] 141 | if storage_path[-1] == "/": 142 | storage_path = storage_path[:-1] 143 | url = self.base_url + parse.quote(storage_path) 144 | else: 145 | url = self.base_url + parse.quote(file_name) 146 | with open(local_upload_file_path, "rb") as file: 147 | file_data = file.read() 148 | response = requests.put(url, data=file_data, headers=self.headers) 149 | try: 150 | response.raise_for_status() 151 | except HTTPError as http: 152 | return { 153 | "status": "error", 154 | "HTTP": response.status_code, 155 | "msg": f"Upload Failed HTTP Error Occured: {http}", 156 | } 157 | else: 158 | return { 159 | "status": "success", 160 | "HTTP": response.status_code, 161 | "msg": "The File Upload was Successful", 162 | } 163 | 164 | def DeleteFile(self, storage_path=""): 165 | """ 166 | This function deletes a file or folder mentioned in the storage_path from the storage zone 167 | Parameters 168 | ---------- 169 | storage_path : The directory path to your file (including file name) or folder which is to be deleted. 170 | If this is the root of your storage zone, you can ignore this parameter. 171 | """ 172 | # Add code below 173 | assert ( 174 | storage_path != "" 175 | ), "storage_path must be specified" # to make sure storage_path is not null 176 | # to build correct url 177 | if storage_path[0] == "/": 178 | storage_path = storage_path[1:] 179 | url = self.base_url + parse.quote(storage_path) 180 | 181 | try: 182 | response = requests.delete(url, headers=self.headers) 183 | response.raise_for_status 184 | except HTTPError as http: 185 | return { 186 | "status": "error", 187 | "HTTP": response.raise_for_status(), 188 | "msg": f"HTTP Error occured: {http}", 189 | } 190 | except Exception as err: 191 | return { 192 | "status": "error", 193 | "HTTP": response.status_code, 194 | "msg": f"Object Delete failed ,Error occured:{err}", 195 | } 196 | else: 197 | return { 198 | "status": "success", 199 | "HTTP": response.status_code, 200 | "msg": "Object Successfully Deleted", 201 | } 202 | 203 | def GetStoragedObjectsList(self, storage_path=None): 204 | """ 205 | This functions returns a list of files and directories located in given storage_path. 206 | Parameters 207 | ---------- 208 | storage_path : The directory path that you want to list. 209 | """ 210 | # to build correct url 211 | if storage_path is not None: 212 | if storage_path[0] == "/": 213 | storage_path = storage_path[1:] 214 | if storage_path[-1] != "/": 215 | url = self.base_url + parse.quote(storage_path) + "/" 216 | else: 217 | url = self.base_url 218 | # Sending GET request 219 | try: 220 | response = requests.get(url, headers=self.headers) 221 | response.raise_for_status() 222 | except HTTPError as http: 223 | return { 224 | "status": "error", 225 | "HTTP": response.status_code, 226 | "msg": f"http error occured {http}", 227 | } 228 | else: 229 | storage_list = [] 230 | for dictionary in response.json(): 231 | temp_dict = {} 232 | for key in dictionary: 233 | if key == "ObjectName" and dictionary["IsDirectory"] is False: 234 | temp_dict["File_Name"] = dictionary[key] 235 | if key == "ObjectName" and dictionary["IsDirectory"]: 236 | temp_dict["Folder_Name"] = dictionary[key] 237 | storage_list.append(temp_dict) 238 | return storage_list 239 | -------------------------------------------------------------------------------- /BunnyCDN/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathrithms/BunnyCDN-Python-Lib/f873eda4399d36dcb478209cbce9dd22abf6136c/BunnyCDN/__init__.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mathrithms 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Downloads](https://static.pepy.tech/badge/Bunnycdnpython)](https://pepy.tech/project/Bunnycdnpython) 2 | # BunnyCDN Python Lib 3 | BunnyCDN is one of the fastest and most cost effective CDN. 4 | 5 | With this BunnyCDN Python Library you can easily implement it and turbo charge your website content to deliver it at lighting speed to your visitors. 6 | 7 | 8 | ## Getting Started 9 | 10 | These instructions will let you install the bunnycdnpython python library running on your local machine. 11 | 12 | ### Prerequisites 13 | Programming language: Python 14 | 15 | * version required : >=3.6 16 | 17 | * Python Library(s) required : requests library 18 | ``` 19 | pip install requests 20 | ``` 21 | 22 | OS : Any OS (ex: Windows , Mac , Linux) 23 | 24 | *Account* : A account on -: (https://bunny.net/) 25 | 26 | * Authentication : API Keys of of Account and Storage API 27 | 28 | 29 | ### Installing 30 | Step1: Open CMD 31 | 32 | Step2: type 33 | ``` 34 | pip install bunnycdnpython 35 | ``` 36 | Now bunnycdnpython python library is installed 37 | 38 | ### Using bunnycdnpython library 39 | * #### Importing the bunnycdnpython library Storage and CDN module 40 | ``` 41 | from BunnyCDN.Storage import Storage 42 | from BunnyCDN.CDN import CDN 43 | ``` 44 | 45 | * ##### Using Storage Module 46 | 47 | For using Storage API you have to initialize an object with your storage api key,storage zone name and storage zone region(optional) 48 | 49 | ``` 50 | obj_storage = Storage(storage_api_key,storage_zone_name,storage_zone_region) 51 | ``` 52 | * ##### Using CDN module 53 | For using Account API you have to initialize an object with your account api key 54 | 55 | ``` 56 | obj_cdn = CDN(account_api_key) 57 | 58 | ``` 59 | ## Summary of functions in Storage module 60 | Storage module has functions that utilize APIs mentioned in official Bunnycdn storage apiary SA 61 | [storage api documentation](https://bunnycdnstorage.docs.apiary.io/) 62 | 63 | 64 | * ### Download File 65 | To download a file from a storage zone to a particular path on your local server/PC 66 | ``` 67 | >>obj_storage.DownloadFile(storage_path,download_path(optional)) 68 | ``` 69 | if download_path is not mentioned then file gets downloaded to current working directory 70 | 71 | * ### Put File 72 | To upload a file to a specific directory in the storage zone 73 | ``` 74 | >>obj_storage.PutFile(file_name, storage_path=None, local_upload_file_path(optional) ) 75 | ``` 76 | The storage_path here does not include storage zone name and it should end with the desired file name to be stored in storage zone.(example: 'sample_dir/abc.txt') 77 | 78 | The local_upload_file_path is the path of the file in the local PC excluding file name 79 | * ### Delete File/Folder 80 | To delete a file or folder from a specific directory in storage zone 81 | ``` 82 | >>obj_storage.DeleteFile(storage_path) 83 | ``` 84 | If deleting a folder, make sure the storage_path ends with a trailing slash "/". 85 | Deleting a folder will delete all files within it. 86 | * ### Get Storaged Objects List 87 | Returns a list containing name of all the files and folders in the directory specified in storage path 88 | ``` 89 | >>obj_storage.GetStoragedObjectsList(storage_path) 90 | ``` 91 | 92 | 93 | ## Summary of functions in CDN module 94 | CDN module has functions that utilize APIs mentioned in official Bunnycdn apiary [CDN api documentation](https://bunnycdn.docs.apiary.io) 95 | 96 | * ### Get Pullzone list 97 | To fetch the list of pullzones in the User's Account 98 | ``` 99 | >>obj_cdn.GetPullZoneList() 100 | ``` 101 | * ### Create Pullzone 102 | To create a new Pulzone in User's Account 103 | ``` 104 | >>obj_cdn.CreatePullZone(Name,OriginURL,Type,StorageZoneId (optional)) 105 | ``` 106 | * ### Get Pullzone 107 | To return the pullzone details for the zone with the given ID 108 | ``` 109 | >>obj_cdn.GetPullZone(PullZoneID) 110 | ``` 111 | 112 | * ### Update Pullzone 113 | To update the pullzone with the given ID 114 | ``` 115 | >>obj_cdn.UpdatePullZone(PullZoneID,OriginUrl,AllowedReferrers,BlockedIps,EnableCacheSlice,EnableGeoZoneUS,EnableGeoZoneEU,EnableGeoZoneASIA,EnableGeoZoneSA,EnableGeoZoneAF,ZoneSecurityEnabled,ZoneSecurityIncludeHashRemoteIP,IgnoreQueryStrings,MonthlyBandwidthLimit,AccessControlOriginHeaderExtensions,EnableAccessControlOriginHeader,BlockRootPathAccess,EnableWebpVary,EnableHostnameVary,EnableCountryCodeVary,EnableLogging,DisableCookies,BudgetRedirectedCountries,BlockedCountries,EnableOriginShield,EnableQueryStringOrdering,CacheErrorResponses,OriginShieldZoneCode,AddCanonicalHeader,CacheControlMaxAgeOverride,AddHostHeader,AWSSigningEnabled,AWSSigningKey,AWSSigningRegionName,AWSSigningSecret,EnableTLS1,LoggingSaveToStorage,LoggingStorageZoneId,LogForwardingEnabled,LogForwardingHostname,LogForwardingPort,LogForwardingToken) 116 | ``` 117 | * Success Response 118 | ``` 119 | { 120 | "status": "success", 121 | "HTTP": 200, 122 | "msg": "Update successful", 123 | } 124 | ``` 125 | * ### Delete Pullzone 126 | To delete the pullzone with the given ID 127 | ``` 128 | >>obj_cdn.DeletePullZone(PullZoneID) 129 | ``` 130 | * Success Response 131 | ``` 132 | { 133 | "status": "success", 134 | "HTTP": response.status_code, 135 | "msg": "Successfully Deleted Pullzone", 136 | } 137 | ``` 138 | * ### Purge Pullzone Cache 139 | To purge the full cache of given pullzone 140 | ``` 141 | >>obj_cdn.PurgePullZoneCache(PullZoneID) 142 | ``` 143 | * Success Response 144 | ``` 145 | { 146 | "status": "success", 147 | "HTTP": 200, 148 | "msg": "successfully purged the cache of the given pullzone ", 149 | } 150 | 151 | ``` 152 | * ### Add or Update Edgerule 153 | To Add or Update an Edgerule of a particular Pullzone 154 | ``` 155 | >>obj_cdn.AddorUpdateEdgerule(PullZoneID,ActionParameter1,ActionParameter2,Enabled,Description,ActionType,TriggerMatchingType,Triggers,GUID(optional)) 156 | ``` 157 | * Success Response 158 | ``` 159 | { 160 | "status": "success", 161 | "HTTP": 201, 162 | "msg": "successfully updated edgerule ", 163 | } 164 | ``` 165 | * ### Delete Edgerule 166 | To Delete the pullzone with the given ID 167 | ``` 168 | >>obj_cdn.DeleteEdgeRule(PullZoneID,EdgeRuleID) 169 | ``` 170 | * Success Response 171 | ``` 172 | { 173 | "status": "success", 174 | "HTTP": 204, 175 | "msg": "Successfully Deleted edgerule", 176 | } 177 | ``` 178 | * ### Add Custom Hostname 179 | To add custom hostname to a pullzone 180 | ``` 181 | >>obj_cdn.AddCustomHostname(PullZoneID,Hostname) 182 | ``` 183 | * Success Response 184 | ``` 185 | { 186 | "status": "success", 187 | "HTTP": 200, 188 | "msg": "Update was Successfull", 189 | } 190 | ``` 191 | * ### Delete Custom Hostname 192 | To delete custom hostname of a pullzone 193 | ``` 194 | >>obj_cdn.DeleteCustomHostname(PullZoneID,Hostname) 195 | ``` 196 | * Success Response 197 | ``` 198 | { 199 | "status": "success", 200 | "HTTP": 200, 201 | "msg": "Successfully Deleted Hostname", 202 | } 203 | 204 | ``` 205 | * ### Set Force SSL 206 | To enable or disable the ForceSSL setting for a pulzone 207 | ``` 208 | >>obj_cdn.SetForceSSL(PullZoneID,Hostname,ForceSSL) 209 | ``` 210 | * Success Response 211 | ``` 212 | { 213 | "status": "success", 214 | "HTTP": 200, 215 | "msg": "successfully added Hostname ", 216 | } 217 | ``` 218 | * ### Load Free Certificate 219 | To Load a FREE SSL Certificate to the domain provided by Let's Encrypt 220 | ``` 221 | >>obj_cdn.LoadFreeCertificate(self,Hostname) 222 | ``` 223 | * Success Response 224 | ``` 225 | [{"Name":"pullzone1","Id":"pullzoneid1"},{"Name":"pullzone2","Id":"pullzoneid2"}] 226 | ``` 227 | * ### Add Certificate 228 | To Add a custom certificate to the given Pull Zone. 229 | ``` 230 | >>obj_cdn.AddCertificate(PullZoneId,Hostname,Certificate,CertificateKey) 231 | ``` 232 | * Success Response 233 | ``` 234 | { 235 | "status": "success", 236 | "HTTP": 200, 237 | "msg": f"Certificated Added successfully Hostname:{Hostname}", 238 | } 239 | ``` 240 | * ### Add Blocked IP 241 | To add an IP to the list of blocked IPs that are not allowed to access the zone 242 | ``` 243 | >>obj_cdn.AddBlockedIp(PullZoneId, BlockedIp) 244 | ``` 245 | * Success Response 246 | ``` 247 | { 248 | "status": "success", 249 | "HTTP": 200, 250 | "msg": "Ip successfully added to list of blocked IPs", 251 | } 252 | ``` 253 | * ### Remove Blocked IP 254 | To removes mentioned IP from the list of blocked IPs that are not allowed to access the zone 255 | ``` 256 | >>obj_cdn.RemoveBlockedIp(PullZoneId, BlockedIp) 257 | ``` 258 | * Success Response 259 | ``` 260 | { 261 | "status": "success", 262 | "HTTP": 200, 263 | "msg": "Ip removed from blocked IPs list " 264 | } 265 | ``` 266 | * ### Get Storagezone List 267 | Returns list of dictionaries containing storage zone name and its id 268 | ``` 269 | >>obj_cdn.StorageZoneList() 270 | ``` 271 | * Success Response 272 | ``` 273 | [{"Name":"storagezone1","Id":"storagezoneid1"},{"Name":"storagezone2","Id":"storagezoneid2"}] 274 | ``` 275 | * ### Get Storage Zone Data 276 | Returns list of dictionaries containing details of storage zones in account 277 | ``` 278 | >>obj_cdn.StorageZoneData() 279 | ``` 280 | * Success Response 281 | ``` 282 | [ 283 | { 284 | 'Id': 53889, 285 | 'Storage_Zone_Name': 'mystoragezone1', 286 | 'Usage': 39080459, 287 | 'host_names': ['test.b-cdn.net', 'cdn.samplehostname.com'], 288 | 'PullZones': ['mypullzone1'] 289 | }, 290 | 291 | { 292 | 'Id': 54559, 293 | 'Storage_Zone_Name': 'mystoragezone2', 294 | 'Usage': 0, 295 | 'host_names': [], 296 | 'PullZones': ['mypullzone2'] 297 | } 298 | ] 299 | ``` 300 | * ### Add Storagezone 301 | This function creates an new storage zone 302 | ``` 303 | >>obj_cdn.AddStorageZone(storage_zone_name, storage_zone_region(optional),ReplicationRegions(optional)) 304 | ``` 305 | * Success Response 306 | ``` 307 | { 308 | "status": "success", 309 | "HTTP": 201, 310 | "msg": { 311 | "Name": "mystoragezone", 312 | "Region": "DE", 313 | "ReplicationRegions": [ 314 | "NY", 315 | "SG" 316 | ] 317 | } 318 | } 319 | ``` 320 | * ### Get Storagezone 321 | This function returns details about the storage zone with id:storage_zone_id 322 | ``` 323 | >>obj_cdn.GetStorageZone(storage_zone_id) 324 | ``` 325 | * Success Response 326 | ``` 327 | { 328 | "Id": 4122, 329 | "Name": "mystoragezone", 330 | "Password": "storage-zone-password-key", 331 | "ReadOnlyPassword": "storage-zone-password-key", 332 | "UserId": "user-id", 333 | "FilesStored": 20, 334 | "StorageUsed": 1024, 335 | "Deleted": false, 336 | "DateModified": "2017-04-22T00:00:00Z" 337 | } 338 | ``` 339 | * ### Delete Storagezone 340 | Deletes the Storage Zone with the given ID 341 | ``` 342 | >>obj_cdn.DeleteStorageZone(storage_zone_id) 343 | ``` 344 | * Success Response 345 | ``` { 346 | "status": "Success", 347 | "HTTP": 201, 348 | "msg": "Deleted Storagezone successfully", 349 | } 350 | ``` 351 | * ### Get Video Library 352 | Gets the details of Video Library of the specified id 353 | ``` 354 | >>obj_cdn.GetVideoLibrary(id) 355 | ``` 356 | * Success Response 357 | ``` 358 | { 359 | "status": "success", 360 | "HTTP": 200, 361 | "msg": { 362 | "Id": 1234, 363 | "Name": "My Video Library", 364 | "VideoCount": 24, 365 | "DateCreated": "2021-04-22T00:00:00Z", 366 | "ApiKey": "video-library-api-key", 367 | "ReadOnlyApiKey": "video-library-readonly-api-key", 368 | "HasWatermark": false, 369 | "WatermarkPositionLeft": 70, 370 | "WatermarkPositionTop": 70, 371 | "WatermarkWidth": 12, 372 | "WatermarkHeight": 12, 373 | "EnabledResolutions": "240p,360p,480p", 374 | "VastTagUrl": "https://mydomain.com/vasttag.xml", 375 | "ViAiPublisherId": "vai-publisher-id", 376 | "CaptionsFontSize": "14,", 377 | "CaptionsFontColor": "#fff", 378 | "CaptionsBackground": "#222", 379 | "UILanguage": "en", 380 | "AllowEarlyPlay": false, 381 | "PlayerTokenAuthenticationEnabled": false, 382 | "EnableMP4Fallback": false, 383 | "AllowedReferrers": [ 384 | "mydomain.com", 385 | "myotherdomain.net" 386 | ], 387 | "BlockedReferrers": [], 388 | "BlockNoneReferrer": false, 389 | "WebhookUrl": "https://api.mybackend.net/webook", 390 | "KeepOriginalFiles": true, 391 | "AllowDirectPlay": true, 392 | "EnableDRM": false, 393 | "Bitrate240p": 600, 394 | "Bitrate360p": 800, 395 | "Bitrate480p": 1400, 396 | "Bitrate720p": 2800, 397 | "Bitrate1080p": 5000, 398 | "Bitrate1440p": 8000, 399 | "Bitrate2160p": 13000 400 | } 401 | } 402 | 403 | ``` 404 | * ### Delete Video Library 405 | This method deletes the Storage zone with id :storage_zone_id 406 | ``` 407 | >>obj_cdn.DeleteVideoLibrary(id) 408 | ``` 409 | * Success Response 410 | ``` 411 | { 412 | "status": "success", 413 | "HTTP": 201, 414 | "msg": "Deleted Video Library" 415 | } 416 | ``` 417 | * ### Purge Url Cache 418 | This method purges the given URL from our edge server cache. 419 | ``` 420 | >>obj_cdn.PurgeUrlCache(url) 421 | ``` 422 | * Success Response 423 | ``` 424 | { 425 | "status": "Success", 426 | "HTTP": 200, 427 | "msg": f"Purged Cache for url:{url}", 428 | } 429 | ``` 430 | * ### Get Statistics 431 | Returns the statistics associated with the account 432 | ``` 433 | >>obj_cdn.Stats(dateFrom=None,dateTo=None,pullZone=None,serverZoneId=None,loadErrors=True) 434 | ``` 435 | Here all the parameters are optional the method can also be called without any parameters 436 | * ### Get Billing Summary 437 | Returns the current billing summary of the account 438 | ``` 439 | >>obj_cdn.Billing() 440 | ``` 441 | * ### Apply a Promo Code 442 | Applies a promo code to the account 443 | ``` 444 | >>obj_cdn.ApplyCode( couponCode ) 445 | ``` 446 | 447 | 448 | ## Versioning 449 | 450 | For the versions available, see the [tags on this repository](https://github.com/mathrithms/BunnyCDN-Python-Lib/tags). 451 | 452 | ## Authors 453 | 454 | * **MATHIRITHMS** - (https://mathrithms.com/) 455 | 456 | * **[Contributors:](https://github.com/mathrithms/BunnyCDN-Python-Lib/graphs/contributors)** 457 | 458 | ##### 1. Aaditya Baranwal 459 | ##### 2. Joel Thomas 460 | 461 | 462 | ## License 463 | 464 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 465 | 466 | ## Acknowledgments 467 | 468 | * Used similar format as per the official libraries published by [BunnyCDN](https://github.com/BunnyWay/BunnyCDN.PHP.Storage) 469 | 470 | **For any bugs or issue mail** 471 | > 1. thomas.2@iitj.ac.in 472 | > 2. baranwal.1@iitj.ac.in 473 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | 4 | def readall(path): 5 | with open(path) as fp: 6 | return fp.read() 7 | 8 | 9 | setup( 10 | name="bunnycdnpython", 11 | version="0.0.8", 12 | author="mathrithms", 13 | author_email="hello@mathrithms.com", 14 | description="A python SDK for BunnyCDN", 15 | long_description=readall("README.md"), 16 | long_description_content_type="text/markdown", 17 | url="https://github.com/mathrithms/BunnyCDN-Python-Lib", 18 | packages=find_packages(), 19 | install_requires=["requests"], 20 | classifiers=[ 21 | "Programming Language :: Python :: 3", 22 | "License :: OSI Approved :: MIT License", 23 | "Operating System :: OS Independent", 24 | ], 25 | python_requires=">=3.6", 26 | ) 27 | --------------------------------------------------------------------------------