diff --git a/CHANGELOG.md b/CHANGELOG.md index 31cd9b8..510fb5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,33 +1,41 @@ -# Changelog +# Registro de Cambios Cliente API para operaciones con el servicio de pagos Flow.cl [FlowAPI-3.0.1](https://www.flow.cl/docs/api.html) The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [Futuros Cambios] - Customer, Plans, Subscriptions: 1.1.0 - Coupon, Invoice: 1.2.0 - Settlement, Merchant: 1.3.0 +## [1.0.6] - 2022-01-06 +### Agregado +- Excepciones + +## [1.0.5] - 2021-07-10 +### Agregado +- Documentacion + ## [1.0.4] - 2021-07-09 -### Removed +### Eliminado - Python 3.6 Support ## [1.0.3] - 2020-09-18 -### Added +### Agregado - Stable ## [1.0.2-beta] - 2020-09-18 -### Added +### Agregado - Soporte python 3.6 - Configuracion Tox ## [1.0.1-beta] -### Added +### Agregado - Cambios menores ## [1.0.0-beta] -### Added +### Agregado - Payment - Refund \ No newline at end of file diff --git a/pyflowcl/Customer.py b/pyflowcl/Customer.py deleted file mode 100644 index bd964ba..0000000 --- a/pyflowcl/Customer.py +++ /dev/null @@ -1,334 +0,0 @@ -""" -pyflowcl.Customer -~~~~~~~~~~~~~~~~~ -Modulo para operaciones con Customer -""" -from dataclasses import asdict -from typing import Any, Dict, Union, cast -from .Clients import ApiClient -import logging - -from .models import ( - Customer, - CustomerRequest, - Error, - CustomerList, - CustomerRegisterResponse, - CustomerRegisterStatusResponse, - PaymentStatus, - CustomerChargeRequest, - CollectResponse, - BatchCollectResponse, -) - - -def create( - apiclient: ApiClient, customer_data: Dict[str, Any] -) -> Union[Customer, Error]: - """Permite crear clientes para efectuarles cargos recurrentes o suscribirlos - a un planes de suscripción. Una vez creado un cliente, Flow lo identificará - por un hash denominado customerId, ejemplo: - - customerId: cus_onoolldvec - """ - url = f"{apiclient.api_url}/customer/create" - - customer = CustomerRequest.from_dict(customer_data) - if customer.apiKey is None: - customer.apiKey = apiclient.api_key - customer.s = apiclient.make_signature(asdict(customer)) - logging.debug("Before Request:" + str(customer)) - - response = apiclient.post(url, asdict(customer)) - - if response.status_code == 200: - return Customer.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 400: - return Error.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 401: - return Error.from_dict(cast(Dict[str, Any], response.json())) - else: - raise Exception(response=response) - - -def edit(apiclient: ApiClient, customer_data: Dict[str, Any]) -> Union[Customer, Error]: - """Este servicio permite editar los datos de un cliente""" - url = f"{apiclient.api_url}/customer/edit" - - customer = CustomerRequest.from_dict(customer_data) - if customer.apiKey is None: - customer.apiKey = apiclient.api_key - customer.s = apiclient.make_signature(asdict(customer)) - logging.debug("Before Request:" + str(customer)) - - response = apiclient.post(url, asdict(customer)) - - if response.status_code == 200: - return Customer.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 400: - return Error.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 401: - return Error.from_dict(cast(Dict[str, Any], response.json())) - else: - raise Exception(response=response) - - -def delete( - apiclient: ApiClient, customer_data: Dict[str, Any] -) -> Union[Customer, Error]: - """Este servicio permite editar los datos de un cliente""" - url = f"{apiclient.api_url}/customer/delete" - - customer = CustomerRequest.from_dict(customer_data) - if customer.apiKey is None: - customer.apiKey = apiclient.api_key - customer.s = apiclient.make_signature(asdict(customer)) - logging.debug("Before Request:" + str(customer)) - - response = apiclient.post(url, asdict(customer)) - - if response.status_code == 200: - return Customer.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 400: - return Error.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 401: - return Error.from_dict(cast(Dict[str, Any], response.json())) - else: - raise Exception(response=response) - - -def get( - apiclient: ApiClient, - cust_id: str, -) -> Union[Customer, Error]: - """Permite obtener los datos de un cliente en base a su customerId.""" - url = f"{apiclient.api_url}/customer/get" - - params: Dict[str, Any] = {"apiKey": apiclient.api_key, "customerId": cust_id} - signature = apiclient.make_signature(params) - params["s"] = signature - logging.debug("Before Request:" + str(params)) - response = apiclient.get(url, params) - - if response.status_code == 200: - return Customer.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 400: - return Error.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 401: - return Error.from_dict(cast(Dict[str, Any], response.json())) - else: - raise Exception(response=response) - - -def list( - apiclient: ApiClient, - filter_params: Dict[str, Any], -) -> Union[CustomerList, Error]: - """Permite obtener los datos de un cliente en base a su customerId.""" - - url = f"{apiclient.api_url}/customer/list" - - params: Dict[str, Any] = {"apiKey": apiclient.api_key} - # params.update(filter_params) retorna None, Plan B: - req_params = {**params, **filter_params} - - signature = apiclient.make_signature(req_params) - req_params["s"] = signature - logging.debug("Before Request:" + str(req_params)) - response = apiclient.get(url, req_params) - - if response.status_code == 200: - return CustomerList.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 400: - return Error.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 401: - return Error.from_dict(cast(Dict[str, Any], response.json())) - else: - raise Exception(response=response) - - -def register( - apiclient: ApiClient, customerId: str, url_return: str -) -> Union[CustomerRegisterResponse, Error]: - """Envía a un cliente a registrar su tarjeta de crédito para poder - efectuarle cargos automáticos. El servicio responde con la URL para - redirigir el browser del pagador y el token que identifica la - transacción. La url de redirección se debe formar concatenando los - valores recibidos en la respuesta de la siguiente forma: - - url + "?token=" +token - - Una vez redirigido el browser del cliente, Flow responderá por medio de - una llamada POST a la url callback del comercio indicada en el parámetro - url_return pasando como parámetro token. El comercio debe implementar - una página y capturar el parámetro token enviado por Flow para luego - consumir el servicio "customer/getRegisterStatus" para obtener el - resultado del registro. - """ - - url = f"{apiclient.api_url}/customer/register" - - params: Dict[str, Any] = { - "apiKey": apiclient.api_key, - "customerId": customerId, - "url_return": url_return, - } - - signature = apiclient.make_signature(params) - params["s"] = signature - logging.debug("Before Request:" + str(params)) - response = apiclient.post(url, params) - - if response.status_code == 200: - return CustomerRegisterResponse.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 400: - return Error.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 401: - return Error.from_dict(cast(Dict[str, Any], response.json())) - else: - raise Exception(response=response) - - -def getRegisterStatus( - apiclient: ApiClient, token: str -) -> Union[CustomerRegisterStatusResponse, Error]: - """Este servicio retorna el resultado del registro de la tarjeta de - crédito de un cliente. - """ - - url = f"{apiclient.api_url}/customer/getRegisterStatus" - - params: Dict[str, Any] = { - "apiKey": apiclient.api_key, - "token": token, - } - - signature = apiclient.make_signature(params) - params["s"] = signature - logging.debug("Before Request:" + str(params)) - response = apiclient.get(url, params) - - if response.status_code == 200: - return CustomerRegisterStatusResponse.from_dict( - cast(Dict[str, Any], response.json()) - ) - if response.status_code == 400: - return Error.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 401: - return Error.from_dict(cast(Dict[str, Any], response.json())) - else: - raise Exception(response=response) - - -def unRegister(apiclient: ApiClient, customerId: str) -> Union[Customer, Error]: - """Este servicio permite eliminar el registro de la tarjeta de crédito - de un cliente. Al eliminar el registro no se podrá hacer cargos - automáticos y Flow enviará un cobro por email. - """ - - url = f"{apiclient.api_url}/customer/unRegister" - - params: Dict[str, Any] = { - "apiKey": apiclient.api_key, - "customerId": customerId, - } - - signature = apiclient.make_signature(params) - params["s"] = signature - logging.debug("Before Request:" + str(params)) - response = apiclient.post(url, params) - - if response.status_code == 200: - return Customer.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 400: - return Error.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 401: - return Error.from_dict(cast(Dict[str, Any], response.json())) - else: - raise Exception(response=response) - - -def charge( - apiclient: ApiClient, charge_data: Dict[str, Any] -) -> Union[PaymentStatus, Error]: - """Este servicio permite efectuar un cargo automático en la tarjeta de - crédito previamente registrada por el cliente. Si el cliente no - tiene registrada una tarjeta el metodo retornará error. - """ - - url = f"{apiclient.api_url}/customer/charge" - - charge = CustomerChargeRequest.from_dict(charge_data) - if charge.apiKey is None: - charge.apiKey = apiclient.api_key - charge.s = apiclient.make_signature(asdict(charge)) - logging.debug("Before Request:" + str(charge)) - - response = apiclient.post(url, asdict(charge)) - - if response.status_code == 200: - return PaymentStatus.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 400: - return Error.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 401: - return Error.from_dict(cast(Dict[str, Any], response.json())) - else: - raise Exception(response=response) - - -def collect( - apiclient: ApiClient, collect_data: Dict[str, Any] -) -> Union[CollectResponse, Error]: - """Este servicio envía un cobro a un cliente. Si el cliente tiene - registrada una tarjeta de crédito se le hace un cargo automático, si - no tiene registrada una tarjeta de credito se genera un cobro. Si se - envía el parámetro byEmail = 1, se genera un cobro por email. - """ - - url = f"{apiclient.api_url}/customer/collect" - - collect = CollectRequest.from_dict(collect_data) - if collect.apiKey is None: - collect.apiKey = apiclient.api_key - collect.s = apiclient.make_signature(asdict(collect)) - logging.debug("Before Request:" + str(collect)) - - response = apiclient.post(url, asdict(collect)) - - if response.status_code == 200: - return CollectResponse.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 400: - return Error.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 401: - return Error.from_dict(cast(Dict[str, Any], response.json())) - else: - raise Exception(response=response) - - -def batchCollect( - apiclient: ApiClient, collect_data: Dict[str, Any] -) -> Union[BatchCollectResponse, Error]: - """Este servicio envía de forma masiva un lote de cobros a clientes. - Similar al servicio collect pero masivo y asíncrono. Este servicio - responde con un token identificador del lote y el número de filas - recibidas. - """ - - url = f"{apiclient.api_url}/customer/batchCollect" - - batch_collect = BatchCollectRequest.from_dict(collect_data) - if batch_collect.apiKey is None: - batch_collect.apiKey = apiclient.api_key - batch_collect.s = apiclient.make_signature(asdict(batch_collect)) - logging.debug("Before Request:" + str(batch_collect)) - - response = apiclient.post(url, asdict(batch_collect)) - - if response.status_code == 200: - return BatchCollectResponse.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 400: - return Error.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 401: - return Error.from_dict(cast(Dict[str, Any], response.json())) - else: - raise Exception(response=response) diff --git a/pyflowcl/Refund.py b/pyflowcl/Refund.py index a3e89d2..fc0da91 100644 --- a/pyflowcl/Refund.py +++ b/pyflowcl/Refund.py @@ -1,13 +1,11 @@ from dataclasses import asdict from typing import Any, Dict, Union, cast -from .Clients import ApiClient +from pyflowcl.Clients import ApiClient +from pyflowcl.models import RefundRequest, RefundStatus, GenericError import logging -from .models import * -def create( - apiclient: ApiClient, refund_data: Dict[str, Any] -) -> Union[RefundStatus, Error,]: +def create(apiclient: ApiClient, refund_data: Dict[str, Any]) -> RefundStatus: """ Este servicio permite crear una orden de reembolso. Una vez que el receptor del reembolso acepte o rechaze el reembolso, Flow @@ -23,23 +21,21 @@ def create( refund.apiKey = apiclient.api_key refund.s = apiclient.make_signature(asdict(refund)) logging.debug("Before Request:" + str(refund)) - response = apiclient.post(url, asdict(refund)) - if response.status_code == 200: return RefundStatus.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 400: - return Error.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 401: - return Error.from_dict(cast(Dict[str, Any], response.json())) + elif response.status_code == 400: + raise GenericError(cast(Dict[str, Any], response.json())) + elif response.status_code == 401: + raise GenericError(cast(Dict[str, Any], response.json())) else: - raise Exception(response=response) + raise GenericError({"code": response.status_code, "message": response}) def getStatus( apiclient: ApiClient, token: str, -) -> Union[RefundStatus, Error,]: +) -> RefundStatus: """ Permite obtener el estado de un reembolso solicitado. Este servicio se debe invocar desde la página del comercio que se señaló en el @@ -52,12 +48,11 @@ def getStatus( params["s"] = signature logging.debug("Before Request:" + str(params)) response = apiclient.get(url, params) - if response.status_code == 200: return RefundStatus.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 400: - return Error.from_dict(cast(Dict[str, Any], response.json())) - if response.status_code == 401: - return Error.from_dict(cast(Dict[str, Any], response.json())) + elif response.status_code == 400: + raise GenericError(cast(Dict[str, Any], response.json())) + elif response.status_code == 401: + raise GenericError(cast(Dict[str, Any], response.json())) else: - raise Exception(response=response) + raise GenericError({"code": response.status_code, "message": response}) diff --git a/pyflowcl/__init__.py b/pyflowcl/__init__.py index 68cdeee..382021f 100644 --- a/pyflowcl/__init__.py +++ b/pyflowcl/__init__.py @@ -1 +1 @@ -__version__ = "1.0.5" +__version__ = "1.0.6" diff --git a/pyflowcl/models.py b/pyflowcl/models.py index 90c47eb..6d07618 100644 --- a/pyflowcl/models.py +++ b/pyflowcl/models.py @@ -9,22 +9,14 @@ from typing import Any, Dict, Optional, List, cast -@dataclass -class Error: - """Objeto para definir un error""" - - code: Optional[float] = None - message: Optional[str] = None - - @staticmethod - def from_dict(d: Dict[str, Any]) -> Error: - code = d.get("code") - message = d.get("message") - - return Error( - code=code, - message=message, - ) +class GenericError(BaseException): + def __init__(self, data): + self.code = data.get("code") + self.message = data.get("message") + super().__init__(f"{self.code}: {self.message}") + + def __str__(self): + return f"{self.code}: {self.message}" @dataclass diff --git a/pyflowcl/models_36.py b/pyflowcl/models_36.py deleted file mode 100644 index a24e842..0000000 --- a/pyflowcl/models_36.py +++ /dev/null @@ -1,284 +0,0 @@ -from dataclasses import dataclass -from typing import Any, Dict, Optional, List - - -@dataclass -class Error: - """ Objeto para definir un error """ - - code: Optional[float] = None - message: Optional[str] = None - - @staticmethod - def from_dict(d: Dict[str, Any]) -> "Error": - code = d.get("code") - message = d.get("message") - - return Error( - code=code, - message=message, - ) - - -@dataclass -class PaymentStatus: - """ Objeto para obtener el estado de un pago """ - - flow_order: Optional[int] = None - commerce_order: Optional[str] = None - request_date: Optional[str] = None - status: Optional[int] = None - subject: Optional[str] = None - currency: Optional[str] = None - amount: Optional[float] = None - payer: Optional[str] = None - optional: Optional[str] = None - pending_info: Optional[Dict[Any, Any]] = None - payment_data: Optional[Dict[Any, Any]] = None - merchant_id: Optional[str] = None - - @staticmethod - def from_dict(d: Dict[str, Any]) -> "PaymentStatus": - flow_order = d.get("flowOrder") - commerce_order = d.get("commerceOrder") - request_date = d.get("requestDate") - status = d.get("status") - subject = d.get("subject") - currency = d.get("currency") - amount = d.get("amount") - payer = d.get("payer") - optional = d.get("optional") - pending_info = d.get("pending_info") - payment_data = d.get("paymentData") - merchant_id = d.get("merchantId") - - return PaymentStatus( - flow_order=flow_order, - commerce_order=commerce_order, - request_date=request_date, - status=status, - subject=subject, - currency=currency, - amount=amount, - payer=payer, - optional=optional, - pending_info=pending_info, - payment_data=payment_data, - merchant_id=merchant_id, - ) - - -@dataclass -class PaymentRequest: - """ Objeto para generar una URL de pago """ - - amount: float = 0 - apiKey: str = "API_KEY" - commerceOrder: str = "" - currency: Optional[str] = None - email: str = "correo@ejemplo.cl" - merchantId: Optional[str] = None - optional: Optional[str] = None - payment_currency: str = "CLP" - payment_method: Optional[int] = None - subject: str = "" - timeout: Optional[int] = None - urlConfirmation: str = "" - urlReturn: str = "" - s: str = "" - - @staticmethod - def from_dict(d: Dict[str, Any]) -> "PaymentRequest": - amount = d.get("amount") - apiKey = d.get("apiKey") - commerceOrder = d.get("commerceOrder") - currency = d.get("currency") - email = d.get("email") - merchantId = d.get("merchantId") - optional = d.get("optional") - payment_currency = d.get("payment_currency") - payment_method = d.get("payment_method") - subject = d.get("subject") - timeout = d.get("timeout") - urlConfirmation = d.get("urlConfirmation") - urlReturn = d.get("urlReturn") - s = d.get("s") - - return PaymentRequest( - amount=amount, - apiKey=apiKey, - commerceOrder=commerceOrder, - currency=currency, - email=email, - merchantId=merchantId, - optional=optional, - payment_currency=payment_currency, - payment_method=payment_method, - subject=subject, - timeout=timeout, - urlConfirmation=urlConfirmation, - urlReturn=urlReturn, - s=s, - ) - - -@dataclass -class PaymentRequestEmail: - """ Objeto para generar un correo electronico de pago """ - - amount: float = 0 - apiKey: str = "API_KEY" - commerceOrder: str = "" - currency: Optional[str] = None - email: str = "correo@ejemplo.cl" - forward_days_after: Optional[int] = None - forward_times: Optional[int] = None - merchantId: Optional[str] = None - optional: Optional[str] = None - payment_currency: Optional[str] = None - subject: Optional[str] = None - timeout: Optional[int] = None - urlConfirmation: str = "" - urlReturn: str = "" - s: str = "" - - @staticmethod - def from_dict(d: Dict[str, Any]) -> "PaymentRequestEmail": - amount = d.get("amount") - apiKey = d.get("apiKey") - commerceOrder = d.get("commerceOrder") - currency = d.get("currency") - email = d.get("email") - forward_days_after = d.get("forward_days_after") - forward_times = d.get("forward_times") - merchantId = d.get("merchantId") - optional = d.get("optional") - payment_currency = d.get("payment_currency") - subject = d.get("subject") - timeout = d.get("timeout") - urlConfirmation = d.get("urlConfirmation") - urlReturn = d.get("urlReturn") - s = d.get("s") - - return PaymentRequestEmail( - amount=amount, - apiKey=apiKey, - commerceOrder=commerceOrder, - currency=currency, - email=email, - forward_days_after=forward_days_after, - forward_times=forward_times, - merchantId=merchantId, - optional=optional, - payment_currency=payment_currency, - subject=subject, - timeout=timeout, - urlConfirmation=urlConfirmation, - urlReturn=urlReturn, - s=s, - ) - - -@dataclass -class PaymentResponse: - """ Objeto respuesta de una creacion de pago """ - - url: Optional[str] = None - token: Optional[str] = None - flowOrder: Optional[float] = None - - @staticmethod - def from_dict(d: Dict[str, Any]) -> "PaymentResponse": - url = d.get("url") - token = d.get("token") - flowOrder = d.get("flowOrder") - - return PaymentResponse( - url=url, - token=token, - flowOrder=flowOrder, - ) - - -@dataclass -class PaymentList: - """ Lista de pagos """ - - total: Optional[float] = None - hasMore: Optional[bool] = None - data: Optional[List[Dict[Any, Any]]] = None - - @staticmethod - def from_dict(d: Dict[str, Any]) -> "PaymentList": - total = d.get("total") - hasMore = d.get("hasMore") - data = d.get("data") - - return PaymentList( - total=total, - hasMore=hasMore, - data=data, - ) - - -@dataclass -class RefundRequest: - """ Refund Request object """ - - amount: float = 0 - apiKey: str = "API_KEY" - commerceTrxId: Optional[str] = None - flowTrxId: Optional[float] = None - receiverEmail: str = "correo@ejemplo.cl" - refundCommerceOrder: str = "" - urlCallBack: str = "" - s: str = "" - - @staticmethod - def from_dict(d: Dict[str, Any]) -> "RefundRequest": - amount = d.get("amount") - apiKey = d.get("apiKey") - commerceTrxId = d.get("commerceTrxId") - flowTrxId = d.get("flowTrxId") - receiverEmail = d.get("receiverEmail") - refundCommerceOrder = d.get("refundCommerceOrder") - urlCallBack = d.get("urlCallBack") - s = d.get("s") - - return RefundRequest( - amount=amount, - apiKey=apiKey, - commerceTrxId=commerceTrxId, - flowTrxId=flowTrxId, - receiverEmail=receiverEmail, - refundCommerceOrder=refundCommerceOrder, - urlCallBack=urlCallBack, - s=s, - ) - - -@dataclass -class RefundStatus: - """ Refund object """ - - flowRefundOrder: int = 0 - date: str = "" - status: str = "" - amount: float = 0 - fee: float = 0 - - @staticmethod - def from_dict(d: Dict[str, Any]) -> "RefundStatus": - flowRefundOrder = d.get("flowRefundOrder") - date = d.get("date") - status = d.get("status") - amount = d.get("amount") - fee = d.get("fee") - - return RefundStatus( - flowRefundOrder=flowRefundOrder, - date=date, - status=status, - amount=amount, - fee=fee, - ) diff --git a/pyproject.toml b/pyproject.toml index d909e98..1e1d894 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyflowcl" -version = "1.0.5" +version = "1.0.6" description = "Cliente para comunicacion con flowAPI-3 de flow.cl" authors = ["Mario Hernandez "] license = "MIT" diff --git a/tests/test_models.py b/tests/test_models.py index 8685381..03b0edb 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,10 +1,12 @@ -from pyflowcl.models import * - - -def test_model_error(): - e = Error() - assert e.code is None - assert e.message is None +from pyflowcl.models import ( + PaymentList, + PaymentResponse, + PaymentRequest, + PaymentList, + PaymentRequestEmail, + RefundRequest, + RefundStatus, +) def test_model_payment_list(): diff --git a/tests/test_pyflow.py b/tests/test_pyflow.py index 861f5bd..e2cbf24 100644 --- a/tests/test_pyflow.py +++ b/tests/test_pyflow.py @@ -2,4 +2,4 @@ def test_version(): - assert __version__ == "1.0.5" + assert __version__ == "1.0.6"