From 36fe9795d473295363d18183efab8e579ab7883e Mon Sep 17 00:00:00 2001 From: alessiocastrica Date: Wed, 26 Jul 2023 12:17:50 +0200 Subject: [PATCH 01/13] feat: bump websockets to latest version --- poetry.lock | 45 +++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 1 + 2 files changed, 46 insertions(+) diff --git a/poetry.lock b/poetry.lock index bebd3c0a..abed9478 100644 --- a/poetry.lock +++ b/poetry.lock @@ -135,6 +135,17 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + [[package]] name = "cfgv" version = "3.4.0" @@ -281,6 +292,40 @@ pygments = ">=2.7" sphinx = ">=5.0,<7.0" sphinx-basic-ng = "*" +[[package]] +name = "gevent" +version = "22.10.2" +description = "Coroutine-based network library" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5" + +[package.dependencies] +cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} +greenlet = {version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\""} +setuptools = "*" +"zope.event" = "*" +"zope.interface" = "*" + +[package.extras] +dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] +docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput", "zope.schema"] +monitor = ["psutil (>=5.7.0)"] +recommended = ["backports.socketpair", "cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)", "selectors2"] +test = ["backports.socketpair", "cffi (>=1.12.2)", "contextvars (==2.4)", "coverage (>=5.0)", "coveralls (>=1.7.0)", "dnspython (>=1.16.0,<2.0)", "futures", "idna", "mock", "objgraph", "psutil (>=5.7.0)", "requests", "selectors2"] + +[[package]] +name = "greenlet" +version = "2.0.2" +description = "Lightweight in-process concurrent programming" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" + +[package.extras] +docs = ["Sphinx", "docutils (<0.18)"] +test = ["objgraph", "psutil"] + [[package]] name = "html5lib" version = "1.1" diff --git a/pyproject.toml b/pyproject.toml index 64872579..79707741 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ pandas = ">=1.5.3" msgpack = "^1.0.3" websockets = "^11.0.3" sseclient-py = "^1.7.2" +websocket = "^0.2.1" [tool.poetry.dev-dependencies] From 38fac28848bd50ce2fcc2671513e241523f639c1 Mon Sep 17 00:00:00 2001 From: alessiocastrica Date: Fri, 28 Jul 2023 15:07:38 +0200 Subject: [PATCH 02/13] fix: del unused websocket --- poetry.lock | 45 --------------------------------------------- pyproject.toml | 1 - 2 files changed, 46 deletions(-) diff --git a/poetry.lock b/poetry.lock index abed9478..bebd3c0a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -135,17 +135,6 @@ category = "main" optional = false python-versions = ">=3.6" -[[package]] -name = "cffi" -version = "1.15.1" -description = "Foreign Function Interface for Python calling C code." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pycparser = "*" - [[package]] name = "cfgv" version = "3.4.0" @@ -292,40 +281,6 @@ pygments = ">=2.7" sphinx = ">=5.0,<7.0" sphinx-basic-ng = "*" -[[package]] -name = "gevent" -version = "22.10.2" -description = "Coroutine-based network library" -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5" - -[package.dependencies] -cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} -greenlet = {version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\""} -setuptools = "*" -"zope.event" = "*" -"zope.interface" = "*" - -[package.extras] -dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] -docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput", "zope.schema"] -monitor = ["psutil (>=5.7.0)"] -recommended = ["backports.socketpair", "cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)", "selectors2"] -test = ["backports.socketpair", "cffi (>=1.12.2)", "contextvars (==2.4)", "coverage (>=5.0)", "coveralls (>=1.7.0)", "dnspython (>=1.16.0,<2.0)", "futures", "idna", "mock", "objgraph", "psutil (>=5.7.0)", "requests", "selectors2"] - -[[package]] -name = "greenlet" -version = "2.0.2" -description = "Lightweight in-process concurrent programming" -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" - -[package.extras] -docs = ["Sphinx", "docutils (<0.18)"] -test = ["objgraph", "psutil"] - [[package]] name = "html5lib" version = "1.1" diff --git a/pyproject.toml b/pyproject.toml index 79707741..64872579 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,6 @@ pandas = ">=1.5.3" msgpack = "^1.0.3" websockets = "^11.0.3" sseclient-py = "^1.7.2" -websocket = "^0.2.1" [tool.poetry.dev-dependencies] From 83b9aafefec78e6f659cc0b7cae578e65f30e4e7 Mon Sep 17 00:00:00 2001 From: alessiocastrica Date: Wed, 20 Sep 2023 19:15:08 +0200 Subject: [PATCH 03/13] draft: initial approach --- alpaca/broker/client.py | 7 +++--- alpaca/common/exceptions.py | 43 ++++++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/alpaca/broker/client.py b/alpaca/broker/client.py index 1556e442..0ff52900 100644 --- a/alpaca/broker/client.py +++ b/alpaca/broker/client.py @@ -658,10 +658,9 @@ def download_trade_document_for_account_by_id( except HTTPError as http_error: if response.status_code in self._retry_codes: continue - if "code" in response.text: - error = response.json() - if "code" in error: - raise APIError(error, http_error) + error = response.text + if "code" in error: + raise APIError(error, http_error) else: raise http_error diff --git a/alpaca/common/exceptions.py b/alpaca/common/exceptions.py index 613bdae3..82e8ea52 100644 --- a/alpaca/common/exceptions.py +++ b/alpaca/common/exceptions.py @@ -1,4 +1,25 @@ import json +from typing import Optional +from pydantic import BaseModel, TypeAdapter +from requests.exceptions import HTTPError +from requests import Request, Response + +class ErrorBody(BaseModel): + """ + Represent the body of an API error response. + """ + code: int + message: str + +class PDTErrorBody(ErrorBody): + """ + Represent the body of the API error in case of PDT. + """ + day_trading_buying_power: float + max_dtbp_used: float + max_dtbp_used_so_far: float + open_orders: int + symbol: str class APIError(Exception): @@ -7,32 +28,44 @@ class APIError(Exception): error.status_code will have http status code. """ - def __init__(self, error, http_error=None): + def __init__( + self, + error: str, + http_error: Optional[HTTPError] = None, + ): super().__init__(error) self._error = error self._http_error = http_error @property - def code(self): + def code(self) -> str: error = json.loads(self._error) return error["code"] @property - def status_code(self): + def status_code(self) -> int: http_error = self._http_error if http_error is not None and hasattr(http_error, "response"): return http_error.response.status_code @property - def request(self): + def request(self) -> Optional[Request]: if self._http_error is not None: return self._http_error.request @property - def response(self): + def response(self) -> Optional[Response]: if self._http_error is not None: return self._http_error.response + @property + def body(self) -> Optional[ErrorBody]: + error = json.loads(self._error) + try: + return TypeAdapter(PDTErrorBody).validate_python(error) + except Exception: + return None + class RetryException(Exception): """ From 2e55e74df91b2d4197106892c317d28e8e69e8f0 Mon Sep 17 00:00:00 2001 From: alessiocastrica Date: Thu, 21 Sep 2023 19:54:36 +0200 Subject: [PATCH 04/13] fix: refactored APIError --- alpaca/broker/client.py | 20 ++-- alpaca/common/exceptions.py | 108 +++++++++++++----- alpaca/common/rest.py | 6 +- .../trading_client/test_order_routes.py | 88 +++++++++++++- 4 files changed, 172 insertions(+), 50 deletions(-) diff --git a/alpaca/broker/client.py b/alpaca/broker/client.py index 0ff52900..9d6a0e6b 100644 --- a/alpaca/broker/client.py +++ b/alpaca/broker/client.py @@ -524,10 +524,9 @@ def _get_account_activities_iterator( # ok we made it to the end, we need to ask for the next page of results last_result = result[-1] - if "id" not in last_result: - raise APIError( - "AccountActivity didn't contain an `id` field to use for paginating results" - ) + assert ( + "id" in last_result + ), "AccountActivity didn't contain an `id` field to use for paginating results" # set the pake token to the id of the last activity so we can get the next page request_fields["page_token"] = last_result["id"] @@ -658,18 +657,15 @@ def download_trade_document_for_account_by_id( except HTTPError as http_error: if response.status_code in self._retry_codes: continue - error = response.text - if "code" in error: - raise APIError(error, http_error) - else: - raise http_error + raise APIError(http_error) from http_error # if we got here there were no issues', so response is now a value break - if response is None: - # we got here either by error or someone has mis-configured us, so we didn't even try - raise Exception("Somehow we never made a request for download!") + # we got here either by error or someone has mis-configured us, so we didn't even try + assert isinstance( + response, Response + ), "Somehow we never made a request for download!" with open(file_path, "wb") as f: # we specify chunk_size none which is okay since we set stream to true above, so chunks will be as we diff --git a/alpaca/common/exceptions.py b/alpaca/common/exceptions.py index 82e8ea52..4a75dff5 100644 --- a/alpaca/common/exceptions.py +++ b/alpaca/common/exceptions.py @@ -1,20 +1,36 @@ import json -from typing import Optional -from pydantic import BaseModel, TypeAdapter +from typing import Any, Dict, List, Optional, Union +from pydantic import BaseModel, TypeAdapter, ValidationError from requests.exceptions import HTTPError from requests import Request, Response +from enum import Enum + class ErrorBody(BaseModel): """ - Represent the body of an API error response. + Represent the body of an API error response. """ + code: int message: str + class PDTErrorBody(ErrorBody): """ - Represent the body of the API error in case of PDT. + Represent the body of the API error in case of PDT. + + Example: + { + "code":40310000, + "day_trading_buying_power":"43251.29", + "max_dtbp_used":"43370.52", + "max_dtbp_used_so_far":"12805.28", + "message":"day trading margin call protection", + "open_orders":"283", + "symbol":"AAPL" + } """ + day_trading_buying_power: float max_dtbp_used: float max_dtbp_used_so_far: float @@ -22,49 +38,81 @@ class PDTErrorBody(ErrorBody): symbol: str +class BuyingPowerErrorBody(ErrorBody): + """ + Represent the body of the API error in case of insufficient buying power. + + Example: + { + "buying_power": "0", + "code": 40310000, + "cost_basis": "1", + "message": "insufficient buying power" + } + """ + + buying_power: float + cost_basis: float + + class APIError(Exception): """ - Represent API related error. - error.status_code will have http status code. + Represent API related error coming from an HTTP request to one of Alpaca's APIs. + + Attributes: + request (Request): will be the HTTP request. + response (Response): will be the HTTP response. + status_code (int): will be the HTTP status_code. + body (Union[ErrorBody, Dict[str, Any]]): will have the body of the response, + it will be a base model or the raw data if the pydantic validation fails. + code (int): will be the Alpaca error code from the response body. """ def __init__( self, - error: str, - http_error: Optional[HTTPError] = None, - ): - super().__init__(error) - self._error = error + http_error: HTTPError, + ) -> None: + super().__init__(http_error) self._http_error = http_error @property - def code(self) -> str: - error = json.loads(self._error) - return error["code"] + def request(self) -> Request: + assert isinstance(self._http_error.request, Request) + return self._http_error.request @property - def status_code(self) -> int: - http_error = self._http_error - if http_error is not None and hasattr(http_error, "response"): - return http_error.response.status_code + def response(self) -> Response: + assert isinstance(self._http_error.response, Response) + return self._http_error.response @property - def request(self) -> Optional[Request]: - if self._http_error is not None: - return self._http_error.request + def status_code(self) -> int: + return self.response.status_code @property - def response(self) -> Optional[Response]: - if self._http_error is not None: - return self._http_error.response + def body(self) -> Union[ErrorBody, Dict[str, Any]]: + _body: Dict[str, Any] = json.loads(self.response.content) + _models: List[ErrorBody] = [ + ErrorBody, + BuyingPowerErrorBody, + PDTErrorBody, + ] + for base_model in _models: + if set(base_model.model_fields.keys()) == set(_body.keys()): + try: + return TypeAdapter(base_model).validate_python(_body) + except ValidationError: + return _body + return _body @property - def body(self) -> Optional[ErrorBody]: - error = json.loads(self._error) - try: - return TypeAdapter(PDTErrorBody).validate_python(error) - except Exception: - return None + def code(self) -> int: + if isinstance(self.body, ErrorBody): + return self.body.code + elif isinstance(self.body, dict): + return int(self.body.get("code")) + else: + return int(json.loads(self.response.content)["code"]) class RetryException(Exception): diff --git a/alpaca/common/rest.py b/alpaca/common/rest.py index adbc5660..77ee9bf6 100644 --- a/alpaca/common/rest.py +++ b/alpaca/common/rest.py @@ -197,12 +197,10 @@ def _one_request(self, method: str, url: str, opts: dict, retry: int) -> dict: except HTTPError as http_error: # retry if we hit Rate Limit if response.status_code in self._retry_codes and retry > 0: - raise RetryException() + raise RetryException() from http_error # raise API error for all other errors - error = response.text - - raise APIError(error, http_error) + raise APIError(http_error) from http_error if response.text != "": return response.json() diff --git a/tests/trading/trading_client/test_order_routes.py b/tests/trading/trading_client/test_order_routes.py index b1fca028..d0ca5265 100644 --- a/tests/trading/trading_client/test_order_routes.py +++ b/tests/trading/trading_client/test_order_routes.py @@ -1,4 +1,4 @@ -from alpaca.common.exceptions import APIError +from alpaca.common.exceptions import APIError, BuyingPowerErrorBody from alpaca.trading.requests import ( GetOrderByIdRequest, GetOrdersRequest, @@ -15,7 +15,7 @@ import pytest -def test_market_order(reqmock, trading_client): +def test_market_order(reqmock, trading_client: TradingClient): reqmock.post( f"{BaseURL.TRADING_PAPER.value}/v2/orders", text=""" @@ -292,6 +292,12 @@ def test_cancel_order_throws_uncancelable_error(reqmock, trading_client: Trading reqmock.delete( f"{BaseURL.TRADING_PAPER.value}/v2/orders/{order_id}", status_code=status_code, + text=""" + { + "code": 40410000, + "message": "order not found" + } + """ ) with pytest.raises(APIError): @@ -307,12 +313,19 @@ def test_cancel_order_throws_not_found_error(reqmock, trading_client: TradingCli reqmock.delete( f"{BaseURL.TRADING_PAPER.value}/v2/orders/{order_id}", status_code=status_code, + text=""" + { + "code": 40410000, + "message": "order not found" + } + """ ) - with pytest.raises(APIError): + with pytest.raises(APIError) as error: trading_client.cancel_order_by_id(order_id) assert reqmock.called_once + assert error.value.body.message == "order not found" def test_cancel_orders(reqmock, trading_client: TradingClient): @@ -338,7 +351,7 @@ def test_cancel_orders(reqmock, trading_client: TradingClient): assert response[0].status == 200 -def test_limit_order(reqmock, trading_client): +def test_limit_order(reqmock, trading_client: TradingClient): reqmock.post( f"{BaseURL.TRADING_PAPER.value}/v2/orders", text=""" @@ -391,3 +404,70 @@ def test_limit_order(reqmock, trading_client): lo_response = trading_client.submit_order(lo) assert lo_response.status == OrderStatus.ACCEPTED + + +def test_insufficient_buying_power(reqmock, trading_client: TradingClient): + status_code = 403 + reqmock.post( + f"{BaseURL.TRADING_PAPER.value}/v2/orders", + status_code=status_code, + text=""" + { + "buying_power": "0", + "code": 40310000, + "cost_basis": "1", + "message": "insufficient buying power" + } + """, + ) + + # Market Order + mo = MarketOrderRequest( + symbol="SPY", + side=OrderSide.BUY, + time_in_force=TimeInForce.DAY, + notional=1, + ) + + with pytest.raises(APIError) as error: + trading_client.submit_order(mo) + + api_error = error.value + assert isinstance(api_error, APIError) + assert api_error.code == 40310000 + assert api_error.status_code == status_code + assert isinstance(api_error.body, BuyingPowerErrorBody) + + +def test_insufficient_buying_power_pydantic_error( + reqmock, trading_client: TradingClient +): + status_code = 403 + reqmock.post( + f"{BaseURL.TRADING_PAPER.value}/v2/orders", + status_code=status_code, + text=""" + { + "buying_power": "0", + "code": 40310000, + "cost_basis": "1" + } + """, + ) + + # Market Order + mo = MarketOrderRequest( + symbol="SPY", + side=OrderSide.BUY, + time_in_force=TimeInForce.DAY, + notional=1, + ) + + with pytest.raises(APIError) as error: + trading_client.submit_order(mo) + + api_error = error.value + assert isinstance(api_error, APIError) + assert api_error.code == 40310000 + assert api_error.status_code == status_code + assert isinstance(api_error.body, dict) From 2943165d4d2f271c00224ceef5b7cd9b5aec3f6a Mon Sep 17 00:00:00 2001 From: alessiocastrica Date: Thu, 21 Sep 2023 19:58:12 +0200 Subject: [PATCH 05/13] fix: poetry lock --- poetry.lock | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/poetry.lock b/poetry.lock index bebd3c0a..d45da346 100644 --- a/poetry.lock +++ b/poetry.lock @@ -5,17 +5,10 @@ description = "A configurable sidebar-enabled Sphinx theme" category = "dev" optional = false python-versions = ">=3.6" - -[[package]] -name = "annotated-types" -version = "0.5.0" -description = "Reusable constraint types to use with typing.Annotated" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] [[package]] name = "apeye" From 44e50582b2bd99989c03ab77df835071a85c8aba Mon Sep 17 00:00:00 2001 From: alessiocastrica Date: Thu, 21 Sep 2023 20:01:55 +0200 Subject: [PATCH 06/13] fix: broken poetry lock after rebase --- poetry.lock | 4 ---- 1 file changed, 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index d45da346..cca6350a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -5,10 +5,6 @@ description = "A configurable sidebar-enabled Sphinx theme" category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, -] [[package]] name = "apeye" From 2537962e0a035fbe9d00f417881a407a5f2054ab Mon Sep 17 00:00:00 2001 From: alessiocastrica Date: Thu, 21 Sep 2023 20:05:40 +0200 Subject: [PATCH 07/13] fix: black --- tests/trading/trading_client/test_order_routes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/trading/trading_client/test_order_routes.py b/tests/trading/trading_client/test_order_routes.py index d0ca5265..c9b88986 100644 --- a/tests/trading/trading_client/test_order_routes.py +++ b/tests/trading/trading_client/test_order_routes.py @@ -297,7 +297,7 @@ def test_cancel_order_throws_uncancelable_error(reqmock, trading_client: Trading "code": 40410000, "message": "order not found" } - """ + """, ) with pytest.raises(APIError): @@ -318,7 +318,7 @@ def test_cancel_order_throws_not_found_error(reqmock, trading_client: TradingCli "code": 40410000, "message": "order not found" } - """ + """, ) with pytest.raises(APIError) as error: From 5910e73531cbcfd7259507e564816b60b5f3a778 Mon Sep 17 00:00:00 2001 From: alessiocastrica Date: Thu, 21 Sep 2023 20:38:26 +0200 Subject: [PATCH 08/13] fix: workaround for doc check --- alpaca/common/exceptions.py | 22 +--------------------- alpaca/common/models.py | 5 ++++- alpaca/data/models/base.py | 1 - 3 files changed, 5 insertions(+), 23 deletions(-) diff --git a/alpaca/common/exceptions.py b/alpaca/common/exceptions.py index 4a75dff5..803558bb 100644 --- a/alpaca/common/exceptions.py +++ b/alpaca/common/exceptions.py @@ -1,9 +1,8 @@ import json -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Union from pydantic import BaseModel, TypeAdapter, ValidationError from requests.exceptions import HTTPError from requests import Request, Response -from enum import Enum class ErrorBody(BaseModel): @@ -18,17 +17,6 @@ class ErrorBody(BaseModel): class PDTErrorBody(ErrorBody): """ Represent the body of the API error in case of PDT. - - Example: - { - "code":40310000, - "day_trading_buying_power":"43251.29", - "max_dtbp_used":"43370.52", - "max_dtbp_used_so_far":"12805.28", - "message":"day trading margin call protection", - "open_orders":"283", - "symbol":"AAPL" - } """ day_trading_buying_power: float @@ -41,14 +29,6 @@ class PDTErrorBody(ErrorBody): class BuyingPowerErrorBody(ErrorBody): """ Represent the body of the API error in case of insufficient buying power. - - Example: - { - "buying_power": "0", - "code": 40310000, - "cost_basis": "1", - "message": "insufficient buying power" - } """ buying_power: float diff --git a/alpaca/common/models.py b/alpaca/common/models.py index 8d734c7c..eab90de7 100644 --- a/alpaca/common/models.py +++ b/alpaca/common/models.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict import pprint @@ -8,5 +8,8 @@ class ValidateBaseModel(BaseModel, validate_assignment=True): it or forget to specify it in our models where we want assignment validation """ + # ignoring the ('model_',) protected namespace may temporarly fix the docs build + model_config = ConfigDict(protected_namespaces=tuple()) + def __repr__(self): return pprint.pformat(self.model_dump(), indent=4) diff --git a/alpaca/data/models/base.py b/alpaca/data/models/base.py index 68a0457f..7057bc3e 100644 --- a/alpaca/data/models/base.py +++ b/alpaca/data/models/base.py @@ -1,5 +1,4 @@ import itertools -import pprint from typing import Any, Dict, List import pandas as pd From 1c5acd12bb565235908f309076131ee2057be4d0 Mon Sep 17 00:00:00 2001 From: alessiocastrica Date: Fri, 22 Sep 2023 16:16:22 +0200 Subject: [PATCH 09/13] fix: remove workaround for doc check --- alpaca/common/models.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/alpaca/common/models.py b/alpaca/common/models.py index eab90de7..63061151 100644 --- a/alpaca/common/models.py +++ b/alpaca/common/models.py @@ -8,8 +8,6 @@ class ValidateBaseModel(BaseModel, validate_assignment=True): it or forget to specify it in our models where we want assignment validation """ - # ignoring the ('model_',) protected namespace may temporarly fix the docs build - model_config = ConfigDict(protected_namespaces=tuple()) def __repr__(self): return pprint.pformat(self.model_dump(), indent=4) From 6d491263ef838764289247110d4824c9fa1165a5 Mon Sep 17 00:00:00 2001 From: alessiocastrica Date: Fri, 6 Oct 2023 11:50:57 +0200 Subject: [PATCH 10/13] fix: formatting --- alpaca/common/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/alpaca/common/models.py b/alpaca/common/models.py index 63061151..1102dbd8 100644 --- a/alpaca/common/models.py +++ b/alpaca/common/models.py @@ -8,6 +8,5 @@ class ValidateBaseModel(BaseModel, validate_assignment=True): it or forget to specify it in our models where we want assignment validation """ - def __repr__(self): return pprint.pformat(self.model_dump(), indent=4) From 1e84933b9c69884da2656d01b79f8d48c340293b Mon Sep 17 00:00:00 2001 From: alessiocastrica Date: Fri, 6 Oct 2023 11:55:35 +0200 Subject: [PATCH 11/13] fix: lock --- poetry.lock | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/poetry.lock b/poetry.lock index cca6350a..bebd3c0a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6,6 +6,17 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "annotated-types" +version = "0.5.0" +description = "Reusable constraint types to use with typing.Annotated" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + [[package]] name = "apeye" version = "1.4.1" From e3ad16e378f0453162cdce133d1ff8fffd73da9e Mon Sep 17 00:00:00 2001 From: alessiocastrica Date: Tue, 10 Oct 2023 08:39:21 +0200 Subject: [PATCH 12/13] fix: comments --- alpaca/broker/client.py | 7 ++++--- alpaca/common/models.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/alpaca/broker/client.py b/alpaca/broker/client.py index 9d6a0e6b..022ebe53 100644 --- a/alpaca/broker/client.py +++ b/alpaca/broker/client.py @@ -524,9 +524,10 @@ def _get_account_activities_iterator( # ok we made it to the end, we need to ask for the next page of results last_result = result[-1] - assert ( - "id" in last_result - ), "AccountActivity didn't contain an `id` field to use for paginating results" + if "id" not in last_result: + raise AssertionError( + "AccountActivity didn't contain an `id` field to use for paginating results" + ) # set the pake token to the id of the last activity so we can get the next page request_fields["page_token"] = last_result["id"] diff --git a/alpaca/common/models.py b/alpaca/common/models.py index 1102dbd8..8d734c7c 100644 --- a/alpaca/common/models.py +++ b/alpaca/common/models.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel import pprint From 4d43e0b92f8ff6127414bcf32d34cca13c570af6 Mon Sep 17 00:00:00 2001 From: alessiocastrica Date: Tue, 10 Oct 2023 15:03:04 +0200 Subject: [PATCH 13/13] fix: assert to appropriate exceptions --- alpaca/broker/client.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/alpaca/broker/client.py b/alpaca/broker/client.py index 022ebe53..0b2e68a6 100644 --- a/alpaca/broker/client.py +++ b/alpaca/broker/client.py @@ -525,7 +525,7 @@ def _get_account_activities_iterator( last_result = result[-1] if "id" not in last_result: - raise AssertionError( + raise AttributeError( "AccountActivity didn't contain an `id` field to use for paginating results" ) @@ -664,9 +664,8 @@ def download_trade_document_for_account_by_id( break # we got here either by error or someone has mis-configured us, so we didn't even try - assert isinstance( - response, Response - ), "Somehow we never made a request for download!" + if not isinstance(response, Response): + raise TypeError("Somehow we never made a request for download!") with open(file_path, "wb") as f: # we specify chunk_size none which is okay since we set stream to true above, so chunks will be as we