diff --git a/straders_sdk/client_api.py b/straders_sdk/client_api.py index 114af64..942d4cd 100644 --- a/straders_sdk/client_api.py +++ b/straders_sdk/client_api.py @@ -29,6 +29,8 @@ from datetime import datetime logger = logging.getLogger(__name__) +COOLDOWN_OFFSET = -2 +MOVEMENT_OFFSET = -2 class SpaceTradersApiClient(SpaceTradersClient): @@ -41,17 +43,21 @@ def __init__( version=None, session: LimiterSession = None, connection=None, + request_priority_baseline=5, ) -> None: self.token = token self.config = ApiConfig(base_url, version) self.current_agent = None self.current_agent_symbol = None self.session = session + self.priority = request_priority_baseline pass def agents_view_one(self, agent_symbol: str) -> "Agent" or SpaceTradersResponse: url = f"/agents/{agent_symbol}" - resp = get_and_validate(url, headers=self._headers(), session=self.session) + resp = get_and_validate( + url, headers=self._headers(), session=self.session, priority=self.priority + ) if resp: return Agent.from_json(resp.data) return resp @@ -63,7 +69,9 @@ def set_current_agent(self, agent_symbol: str, token: str = None): def view_my_self(self) -> "Agent" or SpaceTradersResponse: url = _url("my/agent") - resp = get_and_validate(url, headers=self._headers(), session=self.session) + resp = get_and_validate( + url, headers=self._headers(), session=self.session, priority=self.priority + ) if resp: self.current_agent = Agent.from_json(resp.data) self.current_agent_symbol = self.current_agent.symbol @@ -73,7 +81,9 @@ def view_my_self(self) -> "Agent" or SpaceTradersResponse: def view_my_contracts(self) -> list["Contract"] or SpaceTradersResponse: url = _url("my/contracts") - resp = get_and_validate(url, headers=self._headers(), session=self.session) + resp = get_and_validate( + url, headers=self._headers(), session=self.session, priority=self.priority + ) if resp: return [Contract.from_json(d) for d in resp.data] return resp @@ -84,7 +94,9 @@ def waypoints_view_one( if waypoint_symbol == "": raise ValueError("waypoint_symbol cannot be empty") url = _url(f"systems/{system_symbol}/waypoints/{waypoint_symbol}") - resp = get_and_validate(url, headers=self._headers(), session=self.session) + resp = get_and_validate( + url, headers=self._headers(), session=self.session, priority=self.priority + ) if not resp: print(resp.error) return resp @@ -107,7 +119,12 @@ def waypoints_view( url = _url(f"systems/{system_symbol}/waypoints") resp = get_and_validate_paginated( - url, 20, 50, headers=self._headers(), session=self.session + url, + 20, + 50, + headers=self._headers(), + session=self.session, + priority=self.priority, ) if resp: new_wayps = {d["symbol"]: Waypoint.from_json(d) for d in resp.data} @@ -125,7 +142,9 @@ def register(self, callsign, faction="COSMIC", email=None) -> SpaceTradersRespon data = {"symbol": callsign, "faction": faction} if email is not None: data["email"] = email - resp = post_and_validate(url, data, session=self.session) + resp = post_and_validate( + url, data, session=self.session, priority=self.priority + ) if resp: self.token = resp.data.get("token") return resp @@ -135,7 +154,9 @@ def ship_orbit(self, ship: Ship): url = _url(f"my/ships/{ship.name}/orbit") if ship.nav.status == "IN_ORBIT": return LocalSpaceTradersRespose(None, 0, None, url=url) - resp = post_and_validate(url, headers=self._headers(), session=self.session) + resp = post_and_validate( + url, headers=self._headers(), session=self.session, priority=self.priority + ) if resp: self.update(resp.data) return resp @@ -145,7 +166,11 @@ def ship_patch_nav(self, ship: Ship, flight_mode: str): url = _url(f"my/ships/{ship.name}/nav") data = {"flightMode": flight_mode} resp = patch_and_validate( - url, data, headers=self._headers(), session=self.session + url, + data, + headers=self._headers(), + session=self.session, + priority=self.priority, ) if resp: self.update(resp.data) @@ -160,7 +185,11 @@ def ship_move(self, ship: Ship, dest_waypoint_symbol: str): url = _url(f"my/ships/{ship.name}/navigate") data = {"waypointSymbol": dest_waypoint_symbol} resp = post_and_validate( - url, data, headers=self._headers(), vip=True, session=self.session + url, + data, + headers=self._headers(), + session=self.session, + priority=self.priority + MOVEMENT_OFFSET, ) if resp: self.update(resp.data) @@ -171,7 +200,11 @@ def ship_jump(self, ship: Ship, dest_system_symbol: str): url = _url(f"my/ships/{ship.name}/jump") data = {"systemSymbol": dest_system_symbol} resp = post_and_validate( - url, data, headers=self._headers(), vip=True, session=self.session + url, + data, + headers=self._headers(), + session=self.session, + priority=self.priority + COOLDOWN_OFFSET, ) if resp: self.update(resp.data) @@ -180,7 +213,9 @@ def ship_jump(self, ship: Ship, dest_system_symbol: str): def ship_negotiate(self, ship: "Ship") -> "Contract" or SpaceTradersResponse: "/my/ships/{shipSymbol}/negotiate/contract" url = _url(f"my/ships/{ship.name}/negotiate/contract") - resp = post_and_validate(url, headers=self._headers(), session=self.session) + resp = post_and_validate( + url, headers=self._headers(), session=self.session, priority=self.priority + ) if resp: resp = Contract.from_json(resp.data.get("contract")) return resp @@ -218,7 +253,11 @@ def ship_extract(self, ship: Ship, survey: Survey = None) -> SpaceTradersRespons data = survey.to_json() if survey is not None else None resp = post_and_validate( - url, json=data, headers=self._headers(), vip=True, session=self.session + url, + json=data, + headers=self._headers(), + session=self.session, + priority=self.priority + COOLDOWN_OFFSET, ) if resp: self.update(resp.data) @@ -317,7 +356,10 @@ def ship_survey(self, ship: Ship) -> list[Survey] or SpaceTradersResponse: return LocalSpaceTradersRespose("Ship still on cooldown", 0, 4000) url = _url(f"my/ships/{ship.name}/survey") resp = post_and_validate( - url, headers=self._headers(), vip=True, session=self.session + url, + headers=self._headers(), + session=self.session, + priority=self.priority + COOLDOWN_OFFSET, ) self.update(resp.data) @@ -337,7 +379,11 @@ def ship_transfer_cargo( "shipSymbol": target_ship_name, } resp = post_and_validate( - url, data, headers=self._headers(), session=self.session + url, + data, + headers=self._headers(), + session=self.session, + priority=self.priority, ) self.update(resp.data) @@ -349,7 +395,11 @@ def ship_install_mount(self, ship: Ship, mount_symbol: str) -> SpaceTradersRespo url = _url(f"my/ships/{ship.name}/mounts/install") data = {"symbol": mount_symbol} resp = post_and_validate( - url, data, headers=self._headers(), session=self.session + url, + data, + headers=self._headers(), + session=self.session, + priority=self.priority, ) if resp: self.update(resp.data) @@ -364,7 +414,11 @@ def ship_remove_mount(self, ship: Ship, mount_symbol: str) -> SpaceTradersRespon url = _url(f"my/ships/{ship.name}/mounts/remove") data = {"symbol": mount_symbol} resp = post_and_validate( - url, data, headers=self._headers(), session=self.session + url, + data, + headers=self._headers(), + session=self.session, + priority=self.priority, ) if resp: self.update(resp.data) @@ -378,7 +432,9 @@ def ship_jettison_cargo( ) -> SpaceTradersResponse: url = _url(f"my/ships/{ship.name}/jettison") data = {"symbol": trade_symbol, "units": quantity} - resp = post_and_validate(url, data, headers=self._headers()) + resp = post_and_validate( + url, data, headers=self._headers(), priority=self.priority + ) if resp: ship.update(resp.data) return resp @@ -420,7 +476,11 @@ def systems_view_twenty( ) -> list["System"] or SpaceTradersResponse: url = _url("systems") resp = get_and_validate_page( - url, page_number, headers=self._headers(), session=self.session + url, + page_number, + headers=self._headers(), + session=self.session, + priority=self.priority, ) if resp: @@ -435,6 +495,7 @@ def systems_view_all(self) -> list[System] or SpaceTradersResponse: page_limit=999, headers=self._headers(), session=self.session, + priority=self.priority, ) if resp: resp = [System.from_json(d) for d in resp.data] @@ -442,7 +503,9 @@ def systems_view_all(self) -> list[System] or SpaceTradersResponse: def systems_view_one(self, system_symbol: str) -> System or SpaceTradersResponse: url = _url(f"systems/{system_symbol}") - resp = get_and_validate(url, headers=self._headers(), session=self.session) + resp = get_and_validate( + url, headers=self._headers(), session=self.session, priority=self.priority + ) if resp: return System.from_json(resp.data) return resp @@ -450,7 +513,9 @@ def systems_view_one(self, system_symbol: str) -> System or SpaceTradersResponse def system_market(self, wp: Waypoint) -> Market: # /systems/{systemSymbol}/waypoints/{waypointSymbol}/market url = _url(f"systems/{wp.system_symbol}/waypoints/{wp.symbol}/market") - resp = get_and_validate(url, headers=self._headers(), session=self.session) + resp = get_and_validate( + url, headers=self._headers(), session=self.session, priority=self.priority + ) if resp: resp = Market.from_json(resp.data) return resp @@ -466,7 +531,9 @@ def system_shipyard(self, wp: Waypoint) -> Shipyard or SpaceTradersResponse: """ url = _url(f"systems/{wp.system_symbol}/waypoints/{wp.symbol}/shipyard") - resp = get_and_validate(url, headers=self._headers(), session=self.session) + resp = get_and_validate( + url, headers=self._headers(), session=self.session, priority=self.priority + ) if resp: return Shipyard.from_json(resp.data) @@ -476,7 +543,9 @@ def system_shipyard(self, wp: Waypoint) -> Shipyard or SpaceTradersResponse: def system_jumpgate(self, wp: Waypoint) -> JumpGate or SpaceTradersResponse: """/systems/{systemSymbol}/waypoints/{waypointSymbol}/jump-gate""" url = _url(f"systems/{wp.system_symbol}/waypoints/{wp.symbol}/jump-gate") - resp = get_and_validate(url, headers=self._headers(), session=self.session) + resp = get_and_validate( + url, headers=self._headers(), session=self.session, priority=self.priority + ) if resp: gate = JumpGate.from_json(wp.symbol, resp.data) gate.waypoint_symbol = wp.symbol @@ -487,7 +556,9 @@ def ship_cooldown(self, ship: "Ship") -> SpaceTradersResponse: """/my/ships/{shipSymbol}/cooldown""" # /my/ships/{shipSymbol}/cooldown url = _url(f"my/ships/{ship.name}/cooldown") - resp = get_and_validate(url, headers=self._headers(), session=self.session) + resp = get_and_validate( + url, headers=self._headers(), session=self.session, priority=self.priority + ) if resp and "expiration" in resp.data: ship.update({"cooldown": resp.data}) else: @@ -499,7 +570,12 @@ def ships_view(self) -> dict[str:"Ship"] or SpaceTradersResponse: """/my/ships""" url = _url("my/ships") resp = get_and_validate_paginated( - url, 20, 10, headers=self._headers(), session=self.session + url, + 20, + 10, + headers=self._headers(), + session=self.session, + priority=self.priority, ) if resp: resp = {ship["symbol"]: Ship.from_json(ship) for ship in resp.data} @@ -509,7 +585,9 @@ def ships_view(self) -> dict[str:"Ship"] or SpaceTradersResponse: def ships_view_one(self, symbol: str) -> "Ship" or SpaceTradersResponse: "/my/ships/{shipSymbol}" url = _url(f"my/ships/{symbol}") - resp = get_and_validate(url, headers=self._headers(), session=self.session) + resp = get_and_validate( + url, headers=self._headers(), session=self.session, priority=self.priority + ) if resp: return Ship.from_json(resp.data) return resp @@ -520,7 +598,11 @@ def ships_purchase( url = _url("my/ships") data = {"shipType": ship_type, "waypointSymbol": shipyard_waypoint} resp = post_and_validate( - url, data, headers=self._headers(), session=self.session + url, + data, + headers=self._headers(), + session=self.session, + priority=self.priority, ) if not resp: return resp @@ -534,7 +616,9 @@ def contracts_deliver( url = _url(f"/my/contracts/{contract.id}/deliver") data = {"shipSymbol": ship.name, "tradeSymbol": trade_symbol, "units": units} headers = self._headers() - resp = post_and_validate(url, data, headers=headers, session=self.session) + resp = post_and_validate( + url, data, headers=headers, session=self.session, priority=self.priority + ) if not resp: print(f"failed to deliver to contract {resp.status_code}, {resp.error}") return resp @@ -543,7 +627,9 @@ def contracts_deliver( def contracts_fulfill(self, contract: Contract): url = _url(f"/my/contracts/{contract.id}/fulfill") headers = self._headers() - resp = post_and_validate(url, headers=headers, session=self.session) + resp = post_and_validate( + url, headers=headers, session=self.session, priority=self.priority + ) if not resp: print(f"failed to fulfill contract {resp.status_code}, {resp.error}") return resp diff --git a/straders_sdk/request_consumer.py b/straders_sdk/request_consumer.py index bdfe837..f3c02e5 100644 --- a/straders_sdk/request_consumer.py +++ b/straders_sdk/request_consumer.py @@ -38,7 +38,8 @@ def stop(self): def start(self): self.stop_flag = False - self._consumer_thread.start() + if not self._consumer_thread.is_alive(): + self._consumer_thread.start() def _consume_until_stopped(self): """this method should be tied to the _consumer_thread""" @@ -58,10 +59,13 @@ def _consume_until_stopped(self): print(e) if package.response.status_code != 429: package.event.set() + print( + f"* Completed priority {package.priority} request {package.request.url} after {datetime.now() - package.time_added}" + ) else: package.priority = 0 self.queue.put(0, package) - sleep(min(0, (next_request - datetime.now()).total_seconds())) + sleep(max(0, (next_request - datetime.now()).total_seconds())) else: sleep(interval.total_seconds()) @@ -76,3 +80,28 @@ def __init__(self, priority: float, request: PreparedRequest, event: Event): self.response = None self.event = event self.time_added = datetime.now() + + def __lt__(self, other: "PackagedRequest"): + if not isinstance(other, PackageedRequest): + return True + if self.priority < other.priority: + return True + + if self.time_added < other.time_added: + return True + + def __eq__(self, other): + if not isinstance(other, PackageedRequest): + return False + return self.priority == other.priority and self.time_added == other.time_added + + def __gt__(self, other): + if not isinstance(other, PackageedRequest): + return False + return not self < other and not self == other + + def __le__(self, other): + return self < other or self == other + + def __ge__(self, other): + return self > other or self == other diff --git a/straders_sdk/utils.py b/straders_sdk/utils.py index 497f6bc..39b4ffb 100644 --- a/straders_sdk/utils.py +++ b/straders_sdk/utils.py @@ -43,12 +43,14 @@ def __init__(self, base_url=None, version=None): def get_and_validate_page( - url, page_number, params=None, headers=None, session: Session = None + url, page_number, params=None, headers=None, session: Session = None, priority=5 ) -> SpaceTradersResponse or None: params = params or {} params["page"] = page_number params["limit"] = 20 - return get_and_validate(url, params=params, headers=headers, session=session) + return get_and_validate( + url, params=params, headers=headers, session=session, priority=priority + ) def get_and_validate_paginated( @@ -58,6 +60,7 @@ def get_and_validate_paginated( params=None, headers=None, session: Session = None, + priority=5, ) -> SpaceTradersResponse or None: params = params or {} params["limit"] = per_page @@ -65,7 +68,7 @@ def get_and_validate_paginated( for i in range(1, page_limit or 1): params["page"] = i response = get_and_validate( - url, params=params, headers=headers, session=session + url, params=params, headers=headers, session=session, priority=priority ) if response and response.data: data.extend(response.data) @@ -86,8 +89,10 @@ def rate_limit_check(response: requests.Response): def request_and_validate( - method, url, data=None, json=None, headers=None, params=None, priority=5 + method, url, data=None, json=None, headers=None, params=None, priority=6 ): + if priority == 6: + logging.warning("Priority not set in url %s", url) request = requests.Request( method, url=url, data=data, json=json, headers=headers, params=params ) @@ -97,6 +102,8 @@ def request_and_validate( ) consumer = rc.RequestConsumer() consumer.queue.put((packaged_request.priority, packaged_request)) + if not consumer._consumer_thread.is_alive(): + consumer.start() packaged_request.event.wait() return RemoteSpaceTradersRespose(packaged_request.response) @@ -141,25 +148,37 @@ def old_request_and_validate( def get_and_validate( - url, params=None, headers=None, pages=None, per_page=None, session: Session = None + url, + params=None, + headers=None, + pages=None, + per_page=None, + session: Session = None, + priority=5, ) -> SpaceTradersResponse or None: "wraps the requests.get function to make it easier to use" - return request_and_validate("GET", url, params=params, headers=headers) + return request_and_validate( + "GET", url, params=params, headers=headers, priority=priority + ) def post_and_validate( - url, data=None, json=None, headers=None, vip=False, session: Session = None + url, data=None, json=None, headers=None, priority=5, session: Session = None ) -> SpaceTradersResponse: "wraps the requests.post function to make it easier to use" - return request_and_validate("POST", url, data=data, json=json, headers=headers) + return request_and_validate( + "POST", url, data=data, json=json, headers=headers, priority=priority + ) def patch_and_validate( - url, data=None, json=None, headers=None, session: Session = None + url, data=None, json=None, headers=None, session: Session = None, priority=5 ) -> SpaceTradersResponse: - return request_and_validate("PATCH", url, data=data, json=json, headers=headers) + return request_and_validate( + "PATCH", url, data=data, json=json, headers=headers, priority=priority + ) def _url(endpoint) -> str: