1
\$\begingroup\$

I've written a program that gets the web API endpoint for some functions on a website. I'm just wondering if you guys see anything I could do to improve the code at all.

import sys
import requests
import dateutil
class KenoBase:
 '''
 Methods that are to be inherited to 'KenoAPI' class.
 Do not use this class, instead use 'KenoAPI''.
 '''
 def __init__(self, state="NT"):
 self.__state__ = state.upper()
 self.__states__ = ["ACT", "NSW", "QLD", "VIC", "WA", "NT", "SA", "TAS"]
 self.__base_url__ = "https://api-info-{}.keno.com.au".format(
 self.__state_redirect__.lower())
 def __get_url__(self, end_point="", additional_params=""):
 '''
 Private Method:
 concatenates the base URL and the endpoint together a
 long with additional parameters
 '''
 end_point = str(end_point)
 params = "?jurisdiction={}".format(
 self.__state_redirect__) + additional_params
 complete_url = self.__base_url__ + end_point + params
 return str(complete_url)
 @property
 def __state_redirect__(self):
 '''
 Private Method:
 redirects user input
 '''
 if any(x == self.__state__ for x in self.__states__) is False:
 return sys.exit(str("Check state input: '{}' - is invalid").format(
 self.__state__))
 if self.__state__ == self.__states__[4]:
 print("Keno is not available in WA-Automatically changed to NSW")
 self.__state__ = self.__states__[2]
 return self.__state__
 redirect = [self.__states__[5], self.__states__[6], self.__states__[7]]
 if any(x == self.__state__ for x in redirect) is True:
 print(str("Keno is not available in '{}', this state uses ACT ").format(
self.__state__))
 self.__state__ = self.__states__[0]
 return self.__state__
 return self.__state__
 # noinspection PyDefaultArgument
 @staticmethod
 def __nested_dict__(key=dict, additional_key=""):
 '''
 Private Method: 
 this function speeds up the lookup times for nested dictionaries
 '''
 return key.get(additional_key)
 def __transform_time__(self, _datetime):
 pass
 '''
 Private Method:
 Transforms a date in a datetime object with the correct 
 time information, it also factors in daylight savings
 
 currently working on adding tz and dst to this function
 '''
 return dateutil.parser.isoparse(_datetime).strftime("%Y-%m-%d %H:%M:%S.%f")
 def __results_selection__(self, initial_draw=1, 
 total_draws=1, start_date="2021年02月08日", page_size=1, page_number=1):
 '''
 Private Method:
 
 initial_draw: the first game number
 total_draws: how many games you want to select
 start_date: which date you would like to get the games from
 page_size: how many games are on each page
 page_number: if your page size is less than the total draws the games will be split among multiple pages
 
 Min adn Max values for a each parameter 
 game_number: Min: 0, Max: 999
 
 Number of Games: Min:1, Max:200
 
 page_size: Min:1, Max:200
 
 page_number: Min:1, Max:100
 '''
 url = self.__get_url__(end_point="/v2/info/history", additional_params="&starting_game_number={}&number_of_games={}&date={}&page_size={}&page_number={}").format(
 initial_draw, total_draws, start_date, page_size, page_number)
 return dict(requests.get(url).json())
class KenoAPI(KenoBase):
 '''
 Has all possible URL endpoints.
 To learn more visit the wiki. https://github.com/JGolafshan/KenoAPI/wiki/Keno-API-Functions
 '''
 def __init__(self, state):
 super().__init__(state)
 def game_status(self):
 '''
 Public Method:
 Desc: Retrieves information about the current and next game
 '''
 url = self.__get_url__(end_point="/v2/games/kds", additional_params="")
 retrieved = dict(requests.get(url).json())
 status_current = {
 "starting_time": self.__transform_time__(_datetime=self.__nested_dict__(key=retrieved.get("current"), additional_key="closed")),
 "game_number": self.__nested_dict__(key=retrieved.get("current"), additional_key="game-number")
 }
 status_selling = {
 "starting_time": self.__transform_time__(_datetime=self.__nested_dict__(key=retrieved.get("selling"), additional_key="closing")),
 "game_number": self.__nested_dict__(key=retrieved.get("selling"), additional_key="game-number")
 }
 status = {
 "state": self.__state__,
 "current_game": status_current,
 "next_game": status_selling
 }
 return status
 def live_draw(self):
 '''
 Public Method:
 Desc: Retrieves data from the current draw
 '''
 url = self.__get_url__(end_point="/v2/games/kds", additional_params="")
 retrieved = dict(requests.get(url).json().get("current"))
 status = str(retrieved.get("_type")).split(".")
 status_type = status[-1]
 live_draw = {
 "state": self.__state__,
 "game_number": retrieved.get("game-number"),
 "status": status_type,
 "started_at": self.__transform_time__(_datetime=retrieved.get("closed")),
 "is_finished": None,
 "draw_numbers": retrieved.get("draw"),
 "bonus": self.__nested_dict__(retrieved.get("variants"), additional_key="bonus"),
 "heads": self.__nested_dict__(retrieved.get("variants"), additional_key="heads-or-tails")["heads"],
 "tails": self.__nested_dict__(retrieved.get("variants"), additional_key="heads-or-tails")["tails"],
 "result": self.__nested_dict__(retrieved.get("variants"), additional_key="heads-or-tails")["result"]
 }
 if retrieved.get("_type") == "application/vnd.tabcorp.keno.game.complete":
 live_draw.update({"is_finished": bool(True)})
 else:
 live_draw.update({"is_finished": bool(False)})
 return live_draw
 def jackpot(self):
 '''
 Public Method:
 Desc: Retrieves MegaMillions(leveraged) and Regular jackpots
 '''
 url = self.__get_url__(
 end_point="/v2/info/jackpots", additional_params="")
 retrieved = dict(requests.get(url).json())["jackpots"]
 jackpot_regular = {
 "ten_spot": self.__nested_dict__(key=retrieved.get("ten-spot"), additional_key="base"),
 "nine_spot": self.__nested_dict__(key=retrieved.get("nine-spot"), additional_key="base"),
 "eight_spot": self.__nested_dict__(key=retrieved.get("eight-spot"), additional_key="base"),
 "seven_spot": self.__nested_dict__(key=retrieved.get("seven-spot"), additional_key="base")
 }
 jackpot_leveraged = {
 "ten_spot": self.__nested_dict__(key=retrieved.get("ten-spot-mm"), additional_key="base"),
 "nine_spot": self.__nested_dict__(key=retrieved.get("nine-spot-mm"), additional_key="base"),
 "eight_spot": self.__nested_dict__(key=retrieved.get("eight-spot-mm"), additional_key="base"),
 "seven_spot": self.__nested_dict__(key=retrieved.get("seven-spot-mm"), additional_key="base")
 }
 jackpot_combined = {
 "state": self.__state__,
 "regular": jackpot_regular,
 "leveraged": jackpot_leveraged
 }
 return jackpot_combined
 def hot_cold(self):
 '''
 Public Method:
 Desc: Retrieves trending numbers which are defined the official keno website
 '''
 url = self.__get_url__(
 end_point="/v2/info/hotCold", additional_params="")
 retrieved = dict(requests.get(url).json())
 hot_cold = {
 "cold": retrieved.get("coldNumbers"),
 "hot": retrieved.get("hotNumbers"),
 "last_updated": retrieved.get("secondsSinceLastReceived"),
 "state": self.__state__
 }
 return hot_cold
Reinderien
71k5 gold badges76 silver badges256 bronze badges
asked Jul 9, 2021 at 15:25
\$\endgroup\$
4
  • \$\begingroup\$ Can you show how this is invoked? \$\endgroup\$ Commented Jul 9, 2021 at 15:59
  • \$\begingroup\$ Calling KenoAPI('NT').game_status() fails - you're not checking for the success of your get, but it's returning a 403. \$\endgroup\$ Commented Jul 9, 2021 at 16:06
  • \$\begingroup\$ In fact the site seems entirely down, as I get a 403 no matter what URL I visit \$\endgroup\$ Commented Jul 9, 2021 at 16:24
  • \$\begingroup\$ Hey, I'm not too sure what happened to the website, When I posted this everything was working fine. Seems like bad luck that it happened when it did. \$\endgroup\$ Commented Jul 10, 2021 at 1:17

1 Answer 1

3
\$\begingroup\$

Aside the fact that I can't test anything due to the site being down, the code has some issues that could be cleaned up:

  • It's not clear why KenoBase exists - there's only one child, and I don't see this buying you any useful abstraction
  • You need to get out of the habit of __decorating__ your members. Double underscores are reserved for name mangling. Private variables in Python are very much "by convention" instead of being enforced, and use a single leading underscore rather than leading and trailing dunders.
  • The params = "?jurisdiction={}" setup you do is unneeded, and should be rewritten as a params kwarg dict passed into get() rather than being baked into the URL
  • str(complete_url) is redundant; that's already a string
  • if any(...) is False should be if not any(...); and if any(...) is True should be if any(...)
  • Consider replacing your format() calls with interpolated f-strings
  • return sys.exit makes no sense. exit will raise an exception meant to cut through the stack and terminate the program, and so certainly the return will not happen.
  • key=dict is a mystery. You're setting the default of the key argument to be a type. If you allow the default to be set, then key.get() is going to explode.
  • The pass in transform_time can be deleted
  • There is no need to re-cast the result of json() into a dict; it's already a dict

Lines like this:

dict(requests.get(url).json())

should be

with requests.get(url) as response:
 response.raise_for_status()
 retrieved = response.json()

otherwise, when the site fails (as it is now with a 403) your application will not fail as early as it should, and the error will be much less clear.

answered Jul 9, 2021 at 16:35
\$\endgroup\$
4
  • \$\begingroup\$ Hey, thanks for all the useful pointers, regarding the private methods, is there any way I can make them so they can only be used inside the class, for example when KenoAPI("NT") is called I don't want the user to have access to state_redirect \$\endgroup\$ Commented Jul 10, 2021 at 1:23
  • 1
    \$\begingroup\$ Not really, no. Convention is that privates are marked with one underscore, but they can still be accessed. \$\endgroup\$ Commented Jul 10, 2021 at 3:14
  • \$\begingroup\$ I found something regarding this, using '__decorating' hides the function or variable when calling it outside the class. \$\endgroup\$ Commented Jul 10, 2021 at 15:08
  • 1
    \$\begingroup\$ Don't do it. Read stackoverflow.com/questions/7456807/python-name-mangling \$\endgroup\$ Commented Jul 10, 2021 at 15:21

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.