├── allegroapi ├── entities │ ├── __init__.py │ ├── users.py │ ├── offers.py │ ├── root.py │ ├── userratingssummary.py │ ├── aftersalesserviceconditions.py │ ├── sale.py │ ├── salecategoryparameters.py │ ├── aftersalesserviceconditionswarranties.py │ ├── aftersalesserviceconditionreturnpolicies.py │ ├── aftersalesserviceconditionsimpliedwarranties.py │ ├── saleimages.py │ ├── saleofferpublicationcommandtasks.py │ ├── saleshippingrates.py │ ├── offerchangepricecommands.py │ ├── saleofferpublicationcommands.py │ ├── salecategories.py │ ├── offerrenewcommands.py │ ├── saleoffervariants.py │ └── saleoffers.py ├── error.py ├── base.py ├── __init__.py ├── oauth.py └── allegroclient.py ├── requirements.txt ├── Makefile ├── setup.py ├── LICENSE ├── .gitignore └── README.md /allegroapi/entities/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | init: 2 | pip install -r requirements.txt 3 | -------------------------------------------------------------------------------- /allegroapi/error.py: -------------------------------------------------------------------------------- 1 | class AllegroError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, find_packages 4 | 5 | 6 | setup( 7 | name='allegroapi', 8 | version='0.0.1', 9 | description='Simple wrapper (client) for Allegro.pl REST API written in Python (using requests)', 10 | url='https://github.com/chawel/python-allegro.git', 11 | license=license, 12 | packages=find_packages(exclude=('tests', 'docs')), 13 | ) 14 | -------------------------------------------------------------------------------- /allegroapi/entities/users.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | The Users endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | from .userratingssummary import UserRatingsSummary 12 | 13 | 14 | class Users(BaseApi): 15 | """ 16 | Base Endpoint 17 | """ 18 | def __init__(self, *args, **kwargs): 19 | """ 20 | Initialize the endpoint 21 | """ 22 | super(Users, self).__init__(*args, **kwargs) 23 | self.endpoint = 'users' 24 | self.ratings_summary = UserRatingsSummary(self) 25 | -------------------------------------------------------------------------------- /allegroapi/entities/offers.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | The Offers endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | from .offerrenewcommands import OfferRenewCommands 12 | from .offerchangepricecommands import OfferChangePriceCommands 13 | 14 | 15 | class Offers(BaseApi): 16 | """ 17 | Base Endpoint 18 | """ 19 | def __init__(self, *args, **kwargs): 20 | """ 21 | Initialize the endpoint 22 | """ 23 | super(Offers, self).__init__(*args, **kwargs) 24 | self.endpoint = 'offers' 25 | self.change_price_commands = OfferChangePriceCommands(self) 26 | self.renew_commands = OfferRenewCommands(self) 27 | -------------------------------------------------------------------------------- /allegroapi/entities/root.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | The API Root endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | 12 | 13 | class Root(BaseApi): 14 | """ 15 | The API root resource links to all other resources available in the API. 16 | """ 17 | def __init__(self, *args, **kwargs): 18 | """ 19 | Initialize the endpoint 20 | """ 21 | super(Root, self).__init__(*args, **kwargs) 22 | self.endpoint = '' 23 | 24 | def get(self, **queryparams): 25 | """ 26 | Get links to all other resources available in the API. 27 | 28 | :param queryparams: The query string parameters 29 | """ 30 | return self._a_client._get(url=self._build_path(), **queryparams) 31 | -------------------------------------------------------------------------------- /allegroapi/entities/userratingssummary.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | The User Ratings-Summary endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | 12 | 13 | class UserRatingsSummary(BaseApi): 14 | """ 15 | Get user ratings-summary 16 | """ 17 | def __init__(self, *args, **kwargs): 18 | """ 19 | Initialize the endpoint 20 | """ 21 | super(UserRatingsSummary, self).__init__(*args, **kwargs) 22 | self.endpoint = 'users' 23 | self.user_id = None 24 | 25 | def get(self, user_id): 26 | """ 27 | Get information about specific user's ratings summary 28 | 29 | :param user_id: The user's id 30 | :type user_id: :py:class:`str` 31 | """ 32 | self.user_id = user_id 33 | return self._a_client._get(url=self._build_path(user_id, 'ratings-summary')) 34 | -------------------------------------------------------------------------------- /allegroapi/base.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | The base API object that allows constructions of various endpoint paths 4 | """ 5 | from __future__ import unicode_literals 6 | from itertools import chain 7 | 8 | 9 | class BaseApi(object): 10 | """ 11 | Simple class to buid path for entities 12 | """ 13 | 14 | def __init__(self, a_client): 15 | """ 16 | Initialize the class with you user_id and secret_key 17 | 18 | :param a_client: Allegro.pl client 19 | :type a_client: :mod:`allegroapi.allegroclient.AllegroClient` 20 | """ 21 | super(BaseApi, self).__init__() 22 | self._a_client = a_client 23 | self.endpoint = '' 24 | 25 | def _build_path(self, *args): 26 | """ 27 | Build path with endpoint and args 28 | 29 | :param args: Path elements in the endpoint URL 30 | :type args: :py:class:`unicode` 31 | """ 32 | return '/'.join(chain((self.endpoint,), map(str, args))) 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Paweł 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 | -------------------------------------------------------------------------------- /allegroapi/entities/aftersalesserviceconditions.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | The After Sale Service Conditions endpoint 4 | 5 | 6 | Documentation: https://developer.allegro.pl/documentation/#/ 7 | Schema: 8 | """ 9 | from __future__ import unicode_literals 10 | 11 | from ..base import BaseApi 12 | from .aftersalesserviceconditionreturnpolicies import AfterSaleServiceConditionReturnPolicies 13 | from .aftersalesserviceconditionsimpliedwarranties import AfterSaleServiceConditionImpliedWarranties 14 | from .aftersalesserviceconditionswarranties import AfterSaleServiceConditionWarranties 15 | 16 | 17 | class AfterSaleServiceConditions(BaseApi): 18 | """ 19 | Base Endpoint 20 | """ 21 | def __init__(self, *args, **kwargs): 22 | """ 23 | Initialize the endpoint 24 | """ 25 | super(AfterSaleServiceConditions, self).__init__(*args, **kwargs) 26 | self.endpoint = 'after-sales-service-conditions' 27 | self.return_policies = AfterSaleServiceConditionReturnPolicies(self) 28 | self.implied_warranties = AfterSaleServiceConditionImpliedWarranties(self) 29 | self.warranties = AfterSaleServiceConditionWarranties(self) 30 | -------------------------------------------------------------------------------- /allegroapi/entities/sale.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | The Sale endpoint 4 | 5 | 6 | Documentation: https://developer.allegro.pl/documentation/#/ 7 | Schema: 8 | """ 9 | from __future__ import unicode_literals 10 | 11 | from ..base import BaseApi 12 | from .saleoffers import SaleOffers 13 | from .salecategories import SaleCategories 14 | from .saleshippingrates import SaleShippingRates 15 | from .saleimages import SaleImages 16 | from .saleofferpublicationcommands import SaleOfferPublicationCommands 17 | from .saleoffervariants import SaleOfferVariants 18 | 19 | 20 | class Sale(BaseApi): 21 | """ 22 | Base Endpoint 23 | """ 24 | def __init__(self, *args, **kwargs): 25 | """ 26 | Initialize the endpoint 27 | """ 28 | super(Sale, self).__init__(*args, **kwargs) 29 | self.endpoint = 'sale' 30 | self.offers = SaleOffers(self) 31 | self.categories = SaleCategories(self) 32 | self.shipping_rates = SaleShippingRates(self) 33 | self.images = SaleImages(self) 34 | self.offer_publication_commands = SaleOfferPublicationCommands(self) 35 | self.offer_variants = SaleOfferVariants(self) 36 | 37 | -------------------------------------------------------------------------------- /allegroapi/entities/salecategoryparameters.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | [BETA] The Sale Category Parameters endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | 12 | 13 | class SaleCategoryParameters(BaseApi): 14 | """ 15 | Manage category parameters 16 | """ 17 | def __init__(self, *args, **kwargs): 18 | """ 19 | Initialize the endpoint 20 | """ 21 | super(SaleCategoryParameters, self).__init__(*args, **kwargs) 22 | self.endpoint = 'sale/categories' 23 | self.category_id = None 24 | 25 | # Custom header for this endpoint 26 | self._headers = {'Accept': 'application/vnd.allegro.public.v1+json', 27 | 'Content-type': 'application/vnd.allegro.public.v1+json'} 28 | 29 | def get(self, category_id): 30 | """ 31 | Get parameters of specific category 32 | 33 | :param category_id: The unique id (UUID) for the category. 34 | :type category_id: :py:class:`str` 35 | :return: The JSON response from API or error or None (if 204) 36 | :rtype: :py:class:`dict` or :py:data:`none` 37 | """ 38 | 39 | self.category_id = category_id 40 | return self._a_client._get(url=self._build_path(category_id, 'parameters'), headers=self._headers) 41 | -------------------------------------------------------------------------------- /allegroapi/entities/aftersalesserviceconditionswarranties.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | [BETA] The After Sale Service Condition Implied Warranties endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | 12 | 13 | class AfterSaleServiceConditionWarranties(BaseApi): 14 | """ 15 | Manage category tree 16 | """ 17 | def __init__(self, *args, **kwargs): 18 | """ 19 | Initialize the endpoint 20 | """ 21 | super(AfterSaleServiceConditionWarranties, self).__init__(*args, **kwargs) 22 | self.endpoint = '/after-sales-service-conditions/warranties' 23 | self.seller_id = None 24 | 25 | # Custom header for this endpoint 26 | self._headers = {'Accept': 'application/vnd.allegro.public.v1+json', 27 | 'Content-type': 'application/vnd.allegro.public.v1+json'} 28 | 29 | def all(self, seller_id=None): 30 | """ 31 | Get a list of categories 32 | 33 | :param seller_id: The seller's unique id. 34 | :type seller_id: :py:class:`str` 35 | :return: The JSON response from API or error or None (if 204) 36 | :rtype: :py:class:`dict` or :py:data:`none` 37 | """ 38 | 39 | self.seller_id = seller_id 40 | _params = {'sellerId': seller_id} 41 | # TODO: Maybe iterate or pagination? 42 | return self._a_client._get(url=self._build_path(), params=_params, headers=self._headers) 43 | 44 | -------------------------------------------------------------------------------- /allegroapi/entities/aftersalesserviceconditionreturnpolicies.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | [BETA] The After Sale Service Condition Return Policies endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | 12 | 13 | class AfterSaleServiceConditionReturnPolicies(BaseApi): 14 | """ 15 | Manage category tree 16 | """ 17 | def __init__(self, *args, **kwargs): 18 | """ 19 | Initialize the endpoint 20 | """ 21 | super(AfterSaleServiceConditionReturnPolicies, self).__init__(*args, **kwargs) 22 | self.endpoint = '/after-sales-service-conditions/return-policies' 23 | self.seller_id = None 24 | 25 | # Custom header for this endpoint 26 | self._headers = {'Accept': 'application/vnd.allegro.public.v1+json', 27 | 'Content-type': 'application/vnd.allegro.public.v1+json'} 28 | 29 | def all(self, seller_id=None): 30 | """ 31 | Get a list of categories 32 | 33 | :param seller_id: The seller's unique id. 34 | :type seller_id: :py:class:`str` 35 | :return: The JSON response from API or error or None (if 204) 36 | :rtype: :py:class:`dict` or :py:data:`none` 37 | """ 38 | 39 | self.seller_id = seller_id 40 | _params = {'sellerId': seller_id} 41 | # TODO: Maybe iterate or pagination? 42 | return self._a_client._get(url=self._build_path(), params=_params, headers=self._headers) 43 | 44 | -------------------------------------------------------------------------------- /allegroapi/entities/aftersalesserviceconditionsimpliedwarranties.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | [BETA] The After Sale Service Condition Implied Warranties endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | 12 | 13 | class AfterSaleServiceConditionImpliedWarranties(BaseApi): 14 | """ 15 | Manage category tree 16 | """ 17 | def __init__(self, *args, **kwargs): 18 | """ 19 | Initialize the endpoint 20 | """ 21 | super(AfterSaleServiceConditionImpliedWarranties, self).__init__(*args, **kwargs) 22 | self.endpoint = '/after-sales-service-conditions/implied-warranties' 23 | self.seller_id = None 24 | 25 | # Custom header for this endpoint 26 | self._headers = {'Accept': 'application/vnd.allegro.public.v1+json', 27 | 'Content-type': 'application/vnd.allegro.public.v1+json'} 28 | 29 | def all(self, seller_id=None): 30 | """ 31 | Get a list of categories 32 | 33 | :param seller_id: The seller's unique id. 34 | :type seller_id: :py:class:`str` 35 | :return: The JSON response from API or error or None (if 204) 36 | :rtype: :py:class:`dict` or :py:data:`none` 37 | """ 38 | 39 | self.seller_id = seller_id 40 | _params = {'sellerId': seller_id} 41 | # TODO: Maybe iterate or pagination? 42 | return self._a_client._get(url=self._build_path(), params=_params, headers=self._headers) 43 | 44 | -------------------------------------------------------------------------------- /allegroapi/entities/saleimages.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | [BETA] The Sale Images endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | 12 | 13 | class SaleImages(BaseApi): 14 | """ 15 | Manage offer images 16 | """ 17 | def __init__(self, *args, **kwargs): 18 | """ 19 | Initialize the endpoint 20 | """ 21 | super(SaleImages, self).__init__(*args, **kwargs) 22 | self.endpoint = 'sale/images' 23 | 24 | # Custom header for this endpoint 25 | self._headers = {'Accept': 'application/vnd.allegro.public.v1+json', 26 | 'Content-type': 'image/jpeg'} 27 | 28 | def upload(self, image_location, image_type='JPG'): 29 | """ 30 | Upload local image 31 | ex. upload("/imgs/1.jpg", "JPG") 32 | 33 | :param image_location: The location of image file (must not be URL but local!) 34 | :type image_location: :py:class:`str` 35 | :param image_type: (JPG, PNG, GIF) Image format for proper headers 36 | :type image_type: :py:class:`str` 37 | :return: The JSON response from API or error or None (if 204) 38 | :rtype: :py:class:`dict` or :py:data:`none` 39 | """ 40 | image_format_map = {'JPG': 'image/jpeg', 41 | 'PNG': 'image/png', 42 | 'GIF': 'image/gif'} 43 | 44 | self._headers['Content-type'] = image_format_map.get(image_type) 45 | 46 | with open(image_location, 'rb') as f: 47 | _file_read = f.read() 48 | 49 | return self._a_client._post(url=self._build_path(), data=_file_read, headers=self._headers) 50 | 51 | -------------------------------------------------------------------------------- /allegroapi/entities/saleofferpublicationcommandtasks.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8#346 Podpinam się pod ten temat, bo podobny. 2 | """ 3 | [BETA] The Sale Offer Publication Command Tasks endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | 12 | 13 | class SaleOfferPublicationCommandTasks(BaseApi): 14 | """ 15 | Manage publication offer tasks 16 | """ 17 | def __init__(self, *args, **kwargs): 18 | """ 19 | Initialize the endpoint 20 | """ 21 | super(SaleOfferPublicationCommandTasks, self).__init__(*args, **kwargs) 22 | self.endpoint = 'sale/offer-publication-commands' 23 | self.command_uuid = None 24 | 25 | # Custom header for this endpoint 26 | self._headers = {'Accept': 'application/vnd.allegro.public.v1+json', 27 | 'Content-type': 'application/vnd.allegro.public.v1+json'} 28 | 29 | def get(self, command_uuid, limit=100, offset=0): 30 | """ 31 | [BETA] Provides detailed report for single command task 32 | 33 | :param command_uuid: The global unique command ID (UUID) 34 | :type command_uuid: :py:class:`str` 35 | :param limit: Limit for page 36 | :type limit: :py:class:`int` 37 | :param offset: Offset position 38 | :type offset: :py:class:`int` 39 | :return: The JSON response from API or error or None (if 204) 40 | :rtype: :py:class:`dict` or :py:data:`none` 41 | """ 42 | 43 | self.command_uuid = command_uuid 44 | _params = {'limit': limit, 'offset': offset} 45 | return self._a_client._get(url=self._build_path(command_uuid, 'tasks'), params=_params, headers=self._headers) 46 | 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | allegroapi-env/ 4 | settings.json 5 | 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # Environments 91 | .env 92 | .venv 93 | env/ 94 | venv/ 95 | ENV/ 96 | env.bak/ 97 | venv.bak/ 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # mkdocs documentation 107 | /site 108 | 109 | # mypy 110 | .mypy_cache/ 111 | -------------------------------------------------------------------------------- /allegroapi/entities/saleshippingrates.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | [BETA] The Sale Shipping Rates endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | 12 | 13 | class SaleShippingRates(BaseApi): 14 | """ 15 | Manage shipping rates (cenniki dostaw) 16 | """ 17 | def __init__(self, *args, **kwargs): 18 | """ 19 | Initialize the endpoint 20 | """ 21 | super(SaleShippingRates, self).__init__(*args, **kwargs) 22 | self.endpoint = 'sale/shipping-rates' 23 | self.seller_id = None 24 | self.shipping_rate_id = None 25 | 26 | # Custom header for this endpoint 27 | self._headers = {'Accept': 'application/vnd.allegro.public.v1+json', 28 | 'Content-type': 'application/vnd.allegro.public.v1+json'} 29 | 30 | def get(self, shipping_rate_id): 31 | """ 32 | Get information about specific shipping rate 33 | 34 | :param shipping_rate_id: The unique id (UUID) for the shipping rate. 35 | :type shipping_rate_id: :py:class:`str` 36 | :return: The JSON response from API or error or None (if 204) 37 | :rtype: :py:class:`dict` or :py:data:`none` 38 | """ 39 | 40 | self.shipping_rate_id = shipping_rate_id 41 | self.seller_id = None 42 | return self._a_client._get(url=self._build_path(shipping_rate_id), headers=self._headers) 43 | 44 | def all(self, seller_id): 45 | """ 46 | Get a list of categories 47 | 48 | :param seller_id: The seller's unique id (UUID). 49 | :type seller_id: :py:class:`str` 50 | :return: The JSON response from API or error or None (if 204) 51 | :rtype: :py:class:`dict` or :py:data:`none` 52 | """ 53 | 54 | self.seller_id = seller_id 55 | self.shipping_rate_id = None 56 | _params = {'seller.id': seller_id} 57 | # TODO: Maybe iterate or pagination? 58 | return self._a_client._get(url=self._build_path(), params=_params, headers=self._headers) 59 | 60 | -------------------------------------------------------------------------------- /allegroapi/entities/offerchangepricecommands.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | The Offer Change Price Commands endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | 12 | 13 | class OfferChangePriceCommands(BaseApi): 14 | """ 15 | Manage offer price change commands 16 | """ 17 | def __init__(self, *args, **kwargs): 18 | """ 19 | Initialize the endpoint 20 | """ 21 | super(OfferChangePriceCommands, self).__init__(*args, **kwargs) 22 | self.endpoint = 'offers' 23 | self.command_uuid = None 24 | self.offer_id = None 25 | 26 | # Custom header for this endpoint 27 | self._headers = {'Accept': 'application/vnd.allegro.public.v1+json', 28 | 'Content-type': 'application/vnd.allegro.public.v1+json'} 29 | 30 | def create(self, offer_id, command_uuid, body): 31 | """ 32 | Use it to change a Buy Now price in a single offer 33 | 34 | :param command_uuid: The global unique ID (UUID) for this specific command 35 | :type command_uuid: :py:class:`str` 36 | :param offer_id: The unique id for the offer. 37 | :type offer_id: :py:class:`str` 38 | :param body: The request's body, Command input data. 39 | Note that the amount field must be transferred as a string to avoid rounding errors. 40 | A currency must be provided as a 3-letter code as defined in ISO 4217. 41 | (https://en.wikipedia.org/wiki/ISO_4217#Active_codes) 42 | :type body: :py:class:`dict` 43 | :return: The JSON response from API or error or None (if 204) 44 | :rtype: :py:class:`dict` or :py:data:`none` 45 | """ 46 | 47 | self.offer_id = offer_id 48 | self.command_uuid = command_uuid 49 | if not isinstance(body, dict): 50 | raise KeyError('The command must have a data') 51 | return self._a_client._put(url=self._build_path(offer_id, 'change-price-commands'), json=body, headers=self._headers) 52 | 53 | # def get(self, command_uuid): 54 | # pass 55 | 56 | -------------------------------------------------------------------------------- /allegroapi/entities/saleofferpublicationcommands.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | [BETA] The Sale Offer Publication Commands endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | from .saleofferpublicationcommandtasks import SaleOfferPublicationCommandTasks 12 | 13 | 14 | class SaleOfferPublicationCommands(BaseApi): 15 | """ 16 | Manage offer commands 17 | """ 18 | def __init__(self, *args, **kwargs): 19 | """ 20 | Initialize the endpoint 21 | """ 22 | super(SaleOfferPublicationCommands, self).__init__(*args, **kwargs) 23 | self.endpoint = 'sale/offer-publication-commands' 24 | self.command_uuid = None 25 | self.tasks = SaleOfferPublicationCommandTasks(self) 26 | 27 | # Custom header for this endpoint 28 | self._headers = {'Accept': 'application/vnd.allegro.public.v1+json', 29 | 'Content-type': 'application/vnd.allegro.public.v1+json'} 30 | 31 | def create(self, command_uuid, body): 32 | """ 33 | Allows modification of multiple offers' publication at once. 34 | 35 | :param command_uuid: The global unique ID (UUID) for this specific command 36 | :type command_uuid: :py:class:`str` 37 | :param body: The request body parameters 38 | :type body: :py:class:`dict` 39 | :return: The JSON response from API or error or None (if 204) 40 | :rtype: :py:class:`dict` or :py:data:`none` 41 | """ 42 | 43 | self.command_uuid = command_uuid 44 | if not isinstance(body, dict): 45 | raise KeyError('The command must have a data') 46 | return self._a_client._put(url=self._build_path(command_uuid), json=body, headers=self._headers) 47 | 48 | def get(self, command_uuid): 49 | """ 50 | Provides report summary for given command id 51 | 52 | :param command_uuid: The global unique command ID (UUID) 53 | :type command_uuid: :py:class:`str` 54 | :return: The JSON response from API or error or None (if 204) 55 | :rtype: :py:class:`dict` or :py:data:`none` 56 | """ 57 | 58 | self.command_uuid = command_uuid 59 | return self._a_client._get(url=self._build_path(command_uuid), headers=self._headers) 60 | 61 | -------------------------------------------------------------------------------- /allegroapi/entities/salecategories.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | [BETA] The Sale Category endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | from .salecategoryparameters import SaleCategoryParameters 12 | 13 | 14 | class SaleCategories(BaseApi): 15 | """ 16 | Manage category tree 17 | """ 18 | def __init__(self, *args, **kwargs): 19 | """ 20 | Initialize the endpoint 21 | """ 22 | super(SaleCategories, self).__init__(*args, **kwargs) 23 | self.endpoint = 'sale/categories' 24 | self.parent_id = None 25 | self.category_id = None 26 | self.parameters = SaleCategoryParameters(self) 27 | 28 | # Custom header for this endpoint 29 | self._headers = {'Accept': 'application/vnd.allegro.public.v1+json', 30 | 'Content-type': 'application/vnd.allegro.public.v1+json'} 31 | 32 | def get(self, category_id): 33 | """ 34 | Get information about specific category 35 | 36 | :param category_id: The unique id (UUID) for the category. 37 | :type category_id: :py:class:`str` 38 | :return: The JSON response from API or error or None (if 204) 39 | :rtype: :py:class:`dict` or :py:data:`none` 40 | """ 41 | 42 | self.category_id = category_id 43 | self.parent_id = None 44 | return self._a_client._get(url=self._build_path(category_id), headers=self._headers) 45 | 46 | def all(self, parent_id=None, limit=100, offset=0): 47 | """ 48 | Get a list of categories 49 | 50 | :param parent_id: The unique id (UUID) for the parent category. 51 | :type parent_id: :py:class:`str` 52 | :param limit: Limit for page 53 | :type limit: :py:class:`int` 54 | :param offset: Offset position 55 | :type offset: :py:class:`int` 56 | :return: The JSON response from API or error or None (if 204) 57 | :rtype: :py:class:`dict` or :py:data:`none` 58 | """ 59 | 60 | self.parent_id = parent_id 61 | self.category_id = None 62 | _params = {'parent.id': parent_id, 'limit': limit, 'offset': offset} 63 | # TODO: Maybe iterate or pagination? 64 | return self._a_client._get(url=self._build_path(), params=_params, headers=self._headers) 65 | 66 | -------------------------------------------------------------------------------- /allegroapi/entities/offerrenewcommands.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | [BETA] The Offer Renew Commands endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | 12 | 13 | class OfferRenewCommands(BaseApi): 14 | """ 15 | Manage offer renew commands 16 | """ 17 | def __init__(self, *args, **kwargs): 18 | """ 19 | Initialize the endpoint 20 | """ 21 | super(OfferRenewCommands, self).__init__(*args, **kwargs) 22 | self.endpoint = 'offers' 23 | self.command_uuid = None 24 | self.offer_id = None 25 | 26 | # Custom header for this endpoint 27 | self._headers = {'Accept': 'application/vnd.allegro.public.v1+json', 28 | 'Content-type': 'application/vnd.allegro.public.v1+json'} 29 | 30 | def create(self, offer_id, command_uuid, body): 31 | """ 32 | Renew a single offer by id. 33 | 34 | :param offer_id: The unique id for the offer. 35 | :type offer_id: :py:class:`str` 36 | :param command_uuid: The global unique ID (UUID) for this specific command 37 | :type command_uuid: :py:class:`str` 38 | :param body: The request's body 39 | :type body: :py:class:`dict` 40 | :return: The JSON response from API or error or None (if 204) 41 | :rtype: :py:class:`dict` or :py:data:`none` 42 | """ 43 | 44 | self.offer_id = offer_id 45 | self.command_uuid = command_uuid 46 | if not isinstance(body, dict): 47 | raise KeyError('The command must have a data') 48 | return self._a_client._put(url=self._build_path(offer_id, 'renew-commands', command_uuid), 49 | json=body, 50 | headers=self._headers) 51 | 52 | def get(self, offer_id, command_uuid): 53 | """ 54 | Provides report summary for given command id 55 | 56 | :param offer_id: The unique id for the offer. 57 | :type offer_id: :py:class:`str` 58 | :param command_uuid: The global unique command ID (UUID) 59 | :type command_uuid: :py:class:`str` 60 | :return: The JSON response from API or error or None (if 204) 61 | :rtype: :py:class:`dict` or :py:data:`none` 62 | """ 63 | 64 | self.offer_id = offer_id 65 | self.command_uuid = command_uuid 66 | return self._a_client._get(url=self._build_path(offer_id, 'renew-commands', command_uuid), 67 | headers=self._headers) 68 | 69 | 70 | -------------------------------------------------------------------------------- /allegroapi/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # allegroapi 4 | # Copyright 2018 Pawel Chaniewski 5 | # See LICENSE for details. 6 | 7 | """ 8 | Allegro.pl REST API Wrapper 9 | """ 10 | __version__ = '1.0.0' 11 | __author__ = 'Pawel Chaniewski' 12 | __license__ = 'MIT' 13 | 14 | # API Client 15 | from .allegroclient import AllegroClient 16 | 17 | # API Root 18 | from .entities.root import Root 19 | # Offers 20 | from .entities.offers import Offers 21 | from .entities.offerchangepricecommands import OfferChangePriceCommands 22 | from .entities.offerrenewcommands import OfferRenewCommands 23 | # Sale 24 | from .entities.sale import Sale 25 | from .entities.saleoffers import SaleOffers 26 | from .entities.salecategories import SaleCategories 27 | from .entities.salecategoryparameters import SaleCategoryParameters 28 | from .entities.saleshippingrates import SaleShippingRates 29 | from .entities.saleoffervariants import SaleOfferVariants 30 | from .entities.saleofferpublicationcommands import SaleOfferPublicationCommands 31 | from .entities.saleofferpublicationcommandtasks import SaleOfferPublicationCommandTasks 32 | # Sale images 33 | from .entities.saleimages import SaleImages 34 | # After-sale 35 | from .entities.aftersalesserviceconditions import AfterSaleServiceConditions 36 | from .entities.aftersalesserviceconditionswarranties import AfterSaleServiceConditionWarranties 37 | from .entities.aftersalesserviceconditionsimpliedwarranties import AfterSaleServiceConditionImpliedWarranties 38 | from .entities.aftersalesserviceconditionreturnpolicies import AfterSaleServiceConditionReturnPolicies 39 | # Users 40 | from .entities.users import Users 41 | from .entities.userratingssummary import UserRatingsSummary 42 | 43 | 44 | class Allegro(AllegroClient): 45 | """ 46 | Allegro.pl class to communicate with the REST API 47 | """ 48 | 49 | def __init__(self, *args, **kwargs): 50 | """ 51 | Initialize the class with your api_key and user_id and attach all of 52 | the endpoints 53 | """ 54 | super(Allegro, self).__init__(*args, **kwargs) 55 | # API Root 56 | self.root = self.api_root = Root(self) 57 | # Offers 58 | self.offers = Offers(self) 59 | self.offers.change_price_commands = OfferChangePriceCommands(self) 60 | self.offers.renew_commands = OfferRenewCommands(self) 61 | # Sale 62 | self.sale = Sale(self) 63 | self.sale.offers = SaleOffers(self) 64 | self.sale.offer_variants = SaleOfferVariants(self) 65 | self.sale.categories = SaleCategories(self) 66 | self.sale.categories.parameters = SaleCategoryParameters(self) 67 | self.sale.shipping_rates = SaleShippingRates(self) 68 | # Sale Command 69 | self.sale.offer_publication_commands = SaleOfferPublicationCommands(self) 70 | self.sale.offer_publication_commands.tasks = SaleOfferPublicationCommandTasks(self) 71 | # Sale Images 72 | self.sale.images = SaleImages(self) 73 | # After-sale 74 | self.after_sale_conditions = AfterSaleServiceConditions(self) 75 | self.after_sale_conditions.warranties = AfterSaleServiceConditionWarranties(self) 76 | self.after_sale_conditions.implied_warranties = AfterSaleServiceConditionImpliedWarranties(self) 77 | self.after_sale_conditions.return_policies = AfterSaleServiceConditionReturnPolicies(self) 78 | # Users 79 | self.users = Users(self) 80 | self.users.ratings_summary = UserRatingsSummary(self) 81 | -------------------------------------------------------------------------------- /allegroapi/entities/saleoffervariants.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | [BETA] The Sale Offer Variants endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | 12 | 13 | class SaleOfferVariants(BaseApi): 14 | """ 15 | Manage offer variants (aukcje wielowariantowe) 16 | """ 17 | def __init__(self, *args, **kwargs): 18 | """ 19 | Initialize the endpoint 20 | """ 21 | super(SaleOfferVariants, self).__init__(*args, **kwargs) 22 | self.endpoint = 'sale/offer-variants' 23 | self.set_id = None 24 | 25 | # Custom header for this endpoint 26 | self._headers = {'Accept': 'application/vnd.allegro.public.v1+json', 27 | 'Content-type': 'application/vnd.allegro.public.v1+json'} 28 | 29 | def get(self, set_id): 30 | """ 31 | Use this resource to get variant set by set id. 32 | 33 | :param set_id: The unique id (UUID) for the variant set 34 | :type set_id: :py:class:`str` 35 | :return: The JSON response from API or error or None (if 204) 36 | :rtype: :py:class:`dict` or :py:data:`none` 37 | """ 38 | 39 | self.set_id = set_id 40 | return self._a_client._get(url=self._build_path(set_id), headers=self._headers) 41 | 42 | def create(self, set_id, body): 43 | """ 44 | Use this resource to create or update variant set. 45 | 46 | :param set_id: The global unique ID (UUID) for this specific command 47 | :type set_id: :py:class:`str` 48 | :param body: The request body parameters 49 | :type body: :py:class:`dict` 50 | :return: The JSON response from API or error or None (if 204) 51 | :rtype: :py:class:`dict` or :py:data:`none` 52 | 53 | A valid variant set must consist of three required elements: 54 | 55 | name: it can't be blank and must not be longer than 50 characters 56 | 57 | parameters: it should contain parameter identifiers used for offer grouping 58 | parameter identifiers from the offers and special color/pattern value (for grouping via image) are permitted 59 | it must contain at least one element (up to 2) 60 | 61 | offers: it must contain at least 2 offers (500 at most) 62 | 63 | colorPattern value must be set for every offer if color/pattern parameter is used 64 | colorPattern value can't be blank and must not be longer than 50 characters 65 | colorPattern can take arbitrary string value like red, b323592c-522f-4ec1-b9ea-3764538e0ac4 (UUID), etc. 66 | offers having the same image should have identical colorPattern value 67 | """ 68 | 69 | self.set_id = set_id 70 | if not isinstance(body, dict): 71 | raise KeyError('The command must have a data') 72 | return self._a_client._put(url=self._build_path(set_id), json=body, headers=self._headers) 73 | 74 | def delete(self, set_id): 75 | """ 76 | Use this resource to delete variant set by id. 77 | Offers included in variant set will not be stopped or modified by this operation. 78 | 79 | :param set_id: The unique id (UUID) for the variant set 80 | :type set_id: :py:class:`str` 81 | :return: The JSON response from API or error or None (if 204) 82 | :rtype: :py:class:`dict` or :py:data:`none` 83 | """ 84 | 85 | self.set_id = set_id 86 | if not set_id: 87 | raise KeyError('Variant set ID not specified!') 88 | return self._a_client._delete(url=self._build_path(set_id), headers=self._headers) 89 | 90 | -------------------------------------------------------------------------------- /allegroapi/entities/saleoffers.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | [BETA] The Sale Offer endpoint 4 | 5 | Documentation: https://developer.allegro.pl/documentation/#/ 6 | Schema: 7 | """ 8 | from __future__ import unicode_literals 9 | 10 | from ..base import BaseApi 11 | 12 | 13 | class SaleOffers(BaseApi): 14 | """ 15 | Manage Offers 16 | """ 17 | def __init__(self, *args, **kwargs): 18 | """ 19 | Initialize the endpoint 20 | """ 21 | super(SaleOffers, self).__init__(*args, **kwargs) 22 | self.endpoint = 'sale/offers' 23 | self.offer_id = None 24 | 25 | # Custom header for this endpoint 26 | self._headers = {'Accept': 'application/vnd.allegro.public.v1+json', 27 | 'Content-type': 'application/vnd.allegro.public.v1+json'} 28 | 29 | def get(self, offer_id): 30 | """ 31 | Get information about a single offer from Your Allegro.pl account 32 | 33 | :param offer_id: The unique id for the offer. 34 | :type offer_id: :py:class:`str` 35 | :return: The JSON response from API or error or None (if 204) 36 | :rtype: :py:class:`dict` or :py:data:`none` 37 | """ 38 | 39 | self.offer_id = offer_id 40 | return self._a_client._get(url=self._build_path(offer_id), headers=self._headers) 41 | 42 | def update(self, offer_id, body): 43 | """ 44 | Update a single offer (auction or draft) by id. 45 | 46 | Caution! If updating ongoing offer - you must provide ALL fields in `data` parameter 47 | (even for single field update!) 48 | 49 | :param offer_id: The unique id for the offer. 50 | :type offer_id: :py:class:`str` 51 | :param body: The request's body (when updating ongoing offer - provide whole structure of the offer) 52 | :type body: :py:class:`dict` 53 | :return: The JSON response from API or error or None (if 204) 54 | :rtype: :py:class:`dict` or :py:data:`none` 55 | """ 56 | 57 | self.offer_id = offer_id 58 | if not isinstance(body, dict): 59 | raise KeyError('The offer must have a data') 60 | return self._a_client._put(url=self._build_path(offer_id), json=body, headers=self._headers) 61 | 62 | def create(self, body): 63 | """ 64 | Create a new offer or draft. 65 | 66 | :param body: The request body parameters 67 | :type body: :py:class:`dict` 68 | :return: The JSON response from API or error or None (if 204) 69 | :rtype: :py:class:`dict` or :py:data:`none` 70 | """ 71 | if 'name' not in body: 72 | raise KeyError('The offer must have a name') 73 | if 'category' not in body: 74 | raise KeyError('The offer must have category') 75 | if 'id' not in body['category']: 76 | raise KeyError('The offer must have category id') 77 | 78 | response = self._a_client._post(url=self._build_path(), json=body, headers=self._headers) 79 | if response is not None: 80 | self.offer_id = response['id'] 81 | else: 82 | self.offer_id = None 83 | return response 84 | 85 | def all(self, text_query=None, offer_status='ACTIVE', offer_format='BUY_NOW', limit=100, offset=0): 86 | """ 87 | [BETA] Get list of offers from Your Allegro.pl account 88 | 89 | :param text_query: Search query (in offer title). 90 | :type text_query: :py:class:`str 91 | :param offer_status: Publication statuses may contain more than one comma separated values (ex. 'INACTIVE, ACTIVE, ACTIVATING, ENDED') 92 | :type offer_status: :py:class:`str 93 | :param offer_format: Selling mode may contain more than one comma separated values (ex. 'BUY_NOW, ADVERTISEMENT, AUCTION') 94 | :type offer_format: :py:class:`str 95 | :param limit: Limit for page 96 | :type limit: :py:class:`int` 97 | :param offset: Offset position 98 | :type offset: :py:class:`int` 99 | :return: The JSON response from API or error or None (if 204) 100 | :rtype: :py:class:`dict` or :py:data:`none` 101 | """ 102 | 103 | _headers = {'Accept': 'application/vnd.allegro.beta.v1+json', 104 | 'Content-type': 'application/vnd.allegro.beta.v1+json'} 105 | 106 | _params = { 107 | 'name': text_query, 108 | 'publication.status': offer_status, 109 | 'sellingMode.type': offer_format, 110 | # TODO: Implement more filters - not tested yet! 111 | # 'sellingMode.price.amount.gte': offer_min_price, 112 | # 'sellingMode.price.amount.lte': offer_max_price, 113 | 'limit': limit, 114 | 'offset': offset 115 | } 116 | return self._a_client._get(url=self._build_path(), params=_params, headers=_headers) 117 | 118 | 119 | -------------------------------------------------------------------------------- /allegroapi/oauth.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import requests 4 | from requests.auth import HTTPBasicAuth 5 | import webbrowser 6 | 7 | # Handle library reorganisation Python 2 > Python 3. 8 | try: 9 | from http.server import BaseHTTPRequestHandler, HTTPServer 10 | except ImportError: 11 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer 12 | 13 | from .error import AllegroError 14 | 15 | 16 | class OAuth2Bearer(requests.auth.AuthBase): 17 | """ 18 | Authentication class for storing access token and api key 19 | """ 20 | def __init__(self, api_key, access_token): 21 | """ 22 | Initialize the OAuth and save the access token 23 | 24 | :param api_key: Api Key 25 | :type api_key: :py:class:`str` 26 | :param access_token: The access token provided by OAuth authentication 27 | :type access_token: :py:class:`str` 28 | """ 29 | self._api_key = api_key 30 | self._access_token = access_token 31 | 32 | def __call__(self, r): 33 | """ 34 | Authorize with the access token and api_key provided in __init__ 35 | """ 36 | # r.headers['Api-Key'] = self._api_key 37 | r.headers['Authorization'] = "Bearer {}".format(self._access_token) 38 | 39 | return r 40 | 41 | 42 | class HTTPServerHandler(BaseHTTPRequestHandler): 43 | """ 44 | HTTP Server callbacks to handle OAuth redirects 45 | """ 46 | 47 | def __init__(self, request, address, server): 48 | # For Python 3.x you can use just super().__init__(...) 49 | super(HTTPServerHandler, self).__init__(request, address, server) 50 | 51 | def do_GET(self): 52 | self.send_response(200) 53 | self.send_header('Content-type', 'text/html') 54 | self.end_headers() 55 | if 'code' in self.path: 56 | self.server.access_code = self.path.rsplit('?code=', 1)[-1] 57 | # Display to the user that they no longer need the browser window 58 | self.wfile.write(bytes('