Command-line NHL skaters stats tool











up vote
3
down vote

favorite












Have been playing with the undocumented NHL API for some days. I'd love to see any input about this little script. About overall structure, style, performance enhancements, etc. Not sure how to properly use long links in Python, should I even follow the PEP in this case? The same problem in a couple of other lines, it exceeds 79 symbols limit. Not sure if I should use pluses in the 'printed_info' lines, it behaves the same way without it, but some say it's more explicit, so it's better.



import requests
import json
import os
import collections
import timeit
import datetime


def basic_bio():
api_basic_bio = "http://www.nhl.com/stats/rest/skaters?isAggregate=false&reportType=basic&isGame=false&reportName=skaterpercentages&cayenneExp=gameTypeId=2%20and%20seasonId=20182019"

json_data = requests.get(api_basic_bio).json()
basic_full_bio_list = json_data["data"]
return basic_full_bio_list


def lastnames():
list_lastnames =
players_range = range(len(basic_bio()))
json_bios = basic_bio()

for number in players_range:
list_lastnames.append((json_bios[number]["playerLastName"]).lower())

return list_lastnames


def duplicate_last_names():
last_names_count = collections.Counter(lastnames())
dup_last_names = {k: v for k, v in last_names_count.items() if v > 1}
return dup_last_names


def get_id_name_dict():
players_id_dict = {}
players_range = range(len(basic_bio()))
dups = duplicate_last_names()
skaters = basic_bio()

for number in players_range:
if (skaters[number]["playerLastName"]).lower() in dups:
players_id_dict[skaters[number]["playerName"].lower()] = skaters[number]["playerId"]
else:
players_id_dict[skaters[number]["playerLastName"].lower()] = skaters[number]["playerId"]

with open('nhl_skaters_id.json', 'w') as file:
file.write(json.dumps(players_id_dict, indent=4, sort_keys=True))


def get_id(player_lastname):
with open('nhl_skaters_id.json') as file:
data = json.load(file)
dups = duplicate_last_names()

if player_lastname in dups:
player_firstname = input("Enter a player's firstname: ").lower()
return data[player_firstname + ' ' + player_lastname]
else:
return data[player_lastname]


def get_stats(id):
api_base = 'https://statsapi.web.nhl.com/api/v1/people/'
api_end = '?hydrate=stats(splits=statsSingleSeason)'
url = ''.join([api_base, str(id), api_end])
json_data = requests.get(url).json()
stats = json_data["people"]
return stats


def show_stats(data):
path_stats = data[0]['stats'][0]['splits'][0]['stat']
path_bio = data[0]

printed_info = (f"Name: {path_bio['fullName']}nn" +
f"Birth Date: {path_bio['birthDate']}n" +
f"Height: {path_bio['height']}n" +
f"Weight: {path_bio['weight']}nn" +
f"Games: {path_stats['games']}n" +
f"Goals: {path_stats['goals']}n" +
f"Assists: {path_stats['assists']}n" +
f"Points: {path_stats['points']}n" +
f"PP Points: {path_stats['powerPlayPoints']}n" +
f"Plus-minus: {path_stats['plusMinus']}n" +
f"Shots: {path_stats['shots']}n" +
f"Hits: {path_stats['hits']}n" +
f"Blocks: {path_stats['blocked']}n" +
f"Penalty Minutes: {path_stats['pim']}n" +
f"TOI per Game: {path_stats['timeOnIcePerGame']}n" +
f"PP TOI per Game: {path_stats['powerPlayTimeOnIcePerGame']}n" +
f"SH TOI per Game: {path_stats['shortHandedTimeOnIcePerGame']}n")

print(printed_info)


def data_old(file_name):
one_day_ago = datetime.datetime.now() - datetime.timedelta(days=1)
filetime = datetime.datetime.fromtimestamp(os.path.getmtime(file_name))

if filetime < one_day_ago:
return True
else:
return False


player_lastname = input("Enter a player's lastname: ").lower()

if os.path.isfile('nhl_skaters_id.json') == False or data_old('nhl_skaters_id.json') == True:
get_id_name_dict()

try:
data = get_stats(get_id(player_lastname))
print('-----------------------------')
show_stats(data)
except Exception:
print(f"{player_lastname.capitalize()} did't play any NHL games this season")









share|improve this question




























    up vote
    3
    down vote

    favorite












    Have been playing with the undocumented NHL API for some days. I'd love to see any input about this little script. About overall structure, style, performance enhancements, etc. Not sure how to properly use long links in Python, should I even follow the PEP in this case? The same problem in a couple of other lines, it exceeds 79 symbols limit. Not sure if I should use pluses in the 'printed_info' lines, it behaves the same way without it, but some say it's more explicit, so it's better.



    import requests
    import json
    import os
    import collections
    import timeit
    import datetime


    def basic_bio():
    api_basic_bio = "http://www.nhl.com/stats/rest/skaters?isAggregate=false&reportType=basic&isGame=false&reportName=skaterpercentages&cayenneExp=gameTypeId=2%20and%20seasonId=20182019"

    json_data = requests.get(api_basic_bio).json()
    basic_full_bio_list = json_data["data"]
    return basic_full_bio_list


    def lastnames():
    list_lastnames =
    players_range = range(len(basic_bio()))
    json_bios = basic_bio()

    for number in players_range:
    list_lastnames.append((json_bios[number]["playerLastName"]).lower())

    return list_lastnames


    def duplicate_last_names():
    last_names_count = collections.Counter(lastnames())
    dup_last_names = {k: v for k, v in last_names_count.items() if v > 1}
    return dup_last_names


    def get_id_name_dict():
    players_id_dict = {}
    players_range = range(len(basic_bio()))
    dups = duplicate_last_names()
    skaters = basic_bio()

    for number in players_range:
    if (skaters[number]["playerLastName"]).lower() in dups:
    players_id_dict[skaters[number]["playerName"].lower()] = skaters[number]["playerId"]
    else:
    players_id_dict[skaters[number]["playerLastName"].lower()] = skaters[number]["playerId"]

    with open('nhl_skaters_id.json', 'w') as file:
    file.write(json.dumps(players_id_dict, indent=4, sort_keys=True))


    def get_id(player_lastname):
    with open('nhl_skaters_id.json') as file:
    data = json.load(file)
    dups = duplicate_last_names()

    if player_lastname in dups:
    player_firstname = input("Enter a player's firstname: ").lower()
    return data[player_firstname + ' ' + player_lastname]
    else:
    return data[player_lastname]


    def get_stats(id):
    api_base = 'https://statsapi.web.nhl.com/api/v1/people/'
    api_end = '?hydrate=stats(splits=statsSingleSeason)'
    url = ''.join([api_base, str(id), api_end])
    json_data = requests.get(url).json()
    stats = json_data["people"]
    return stats


    def show_stats(data):
    path_stats = data[0]['stats'][0]['splits'][0]['stat']
    path_bio = data[0]

    printed_info = (f"Name: {path_bio['fullName']}nn" +
    f"Birth Date: {path_bio['birthDate']}n" +
    f"Height: {path_bio['height']}n" +
    f"Weight: {path_bio['weight']}nn" +
    f"Games: {path_stats['games']}n" +
    f"Goals: {path_stats['goals']}n" +
    f"Assists: {path_stats['assists']}n" +
    f"Points: {path_stats['points']}n" +
    f"PP Points: {path_stats['powerPlayPoints']}n" +
    f"Plus-minus: {path_stats['plusMinus']}n" +
    f"Shots: {path_stats['shots']}n" +
    f"Hits: {path_stats['hits']}n" +
    f"Blocks: {path_stats['blocked']}n" +
    f"Penalty Minutes: {path_stats['pim']}n" +
    f"TOI per Game: {path_stats['timeOnIcePerGame']}n" +
    f"PP TOI per Game: {path_stats['powerPlayTimeOnIcePerGame']}n" +
    f"SH TOI per Game: {path_stats['shortHandedTimeOnIcePerGame']}n")

    print(printed_info)


    def data_old(file_name):
    one_day_ago = datetime.datetime.now() - datetime.timedelta(days=1)
    filetime = datetime.datetime.fromtimestamp(os.path.getmtime(file_name))

    if filetime < one_day_ago:
    return True
    else:
    return False


    player_lastname = input("Enter a player's lastname: ").lower()

    if os.path.isfile('nhl_skaters_id.json') == False or data_old('nhl_skaters_id.json') == True:
    get_id_name_dict()

    try:
    data = get_stats(get_id(player_lastname))
    print('-----------------------------')
    show_stats(data)
    except Exception:
    print(f"{player_lastname.capitalize()} did't play any NHL games this season")









    share|improve this question


























      up vote
      3
      down vote

      favorite









      up vote
      3
      down vote

      favorite











      Have been playing with the undocumented NHL API for some days. I'd love to see any input about this little script. About overall structure, style, performance enhancements, etc. Not sure how to properly use long links in Python, should I even follow the PEP in this case? The same problem in a couple of other lines, it exceeds 79 symbols limit. Not sure if I should use pluses in the 'printed_info' lines, it behaves the same way without it, but some say it's more explicit, so it's better.



      import requests
      import json
      import os
      import collections
      import timeit
      import datetime


      def basic_bio():
      api_basic_bio = "http://www.nhl.com/stats/rest/skaters?isAggregate=false&reportType=basic&isGame=false&reportName=skaterpercentages&cayenneExp=gameTypeId=2%20and%20seasonId=20182019"

      json_data = requests.get(api_basic_bio).json()
      basic_full_bio_list = json_data["data"]
      return basic_full_bio_list


      def lastnames():
      list_lastnames =
      players_range = range(len(basic_bio()))
      json_bios = basic_bio()

      for number in players_range:
      list_lastnames.append((json_bios[number]["playerLastName"]).lower())

      return list_lastnames


      def duplicate_last_names():
      last_names_count = collections.Counter(lastnames())
      dup_last_names = {k: v for k, v in last_names_count.items() if v > 1}
      return dup_last_names


      def get_id_name_dict():
      players_id_dict = {}
      players_range = range(len(basic_bio()))
      dups = duplicate_last_names()
      skaters = basic_bio()

      for number in players_range:
      if (skaters[number]["playerLastName"]).lower() in dups:
      players_id_dict[skaters[number]["playerName"].lower()] = skaters[number]["playerId"]
      else:
      players_id_dict[skaters[number]["playerLastName"].lower()] = skaters[number]["playerId"]

      with open('nhl_skaters_id.json', 'w') as file:
      file.write(json.dumps(players_id_dict, indent=4, sort_keys=True))


      def get_id(player_lastname):
      with open('nhl_skaters_id.json') as file:
      data = json.load(file)
      dups = duplicate_last_names()

      if player_lastname in dups:
      player_firstname = input("Enter a player's firstname: ").lower()
      return data[player_firstname + ' ' + player_lastname]
      else:
      return data[player_lastname]


      def get_stats(id):
      api_base = 'https://statsapi.web.nhl.com/api/v1/people/'
      api_end = '?hydrate=stats(splits=statsSingleSeason)'
      url = ''.join([api_base, str(id), api_end])
      json_data = requests.get(url).json()
      stats = json_data["people"]
      return stats


      def show_stats(data):
      path_stats = data[0]['stats'][0]['splits'][0]['stat']
      path_bio = data[0]

      printed_info = (f"Name: {path_bio['fullName']}nn" +
      f"Birth Date: {path_bio['birthDate']}n" +
      f"Height: {path_bio['height']}n" +
      f"Weight: {path_bio['weight']}nn" +
      f"Games: {path_stats['games']}n" +
      f"Goals: {path_stats['goals']}n" +
      f"Assists: {path_stats['assists']}n" +
      f"Points: {path_stats['points']}n" +
      f"PP Points: {path_stats['powerPlayPoints']}n" +
      f"Plus-minus: {path_stats['plusMinus']}n" +
      f"Shots: {path_stats['shots']}n" +
      f"Hits: {path_stats['hits']}n" +
      f"Blocks: {path_stats['blocked']}n" +
      f"Penalty Minutes: {path_stats['pim']}n" +
      f"TOI per Game: {path_stats['timeOnIcePerGame']}n" +
      f"PP TOI per Game: {path_stats['powerPlayTimeOnIcePerGame']}n" +
      f"SH TOI per Game: {path_stats['shortHandedTimeOnIcePerGame']}n")

      print(printed_info)


      def data_old(file_name):
      one_day_ago = datetime.datetime.now() - datetime.timedelta(days=1)
      filetime = datetime.datetime.fromtimestamp(os.path.getmtime(file_name))

      if filetime < one_day_ago:
      return True
      else:
      return False


      player_lastname = input("Enter a player's lastname: ").lower()

      if os.path.isfile('nhl_skaters_id.json') == False or data_old('nhl_skaters_id.json') == True:
      get_id_name_dict()

      try:
      data = get_stats(get_id(player_lastname))
      print('-----------------------------')
      show_stats(data)
      except Exception:
      print(f"{player_lastname.capitalize()} did't play any NHL games this season")









      share|improve this question















      Have been playing with the undocumented NHL API for some days. I'd love to see any input about this little script. About overall structure, style, performance enhancements, etc. Not sure how to properly use long links in Python, should I even follow the PEP in this case? The same problem in a couple of other lines, it exceeds 79 symbols limit. Not sure if I should use pluses in the 'printed_info' lines, it behaves the same way without it, but some say it's more explicit, so it's better.



      import requests
      import json
      import os
      import collections
      import timeit
      import datetime


      def basic_bio():
      api_basic_bio = "http://www.nhl.com/stats/rest/skaters?isAggregate=false&reportType=basic&isGame=false&reportName=skaterpercentages&cayenneExp=gameTypeId=2%20and%20seasonId=20182019"

      json_data = requests.get(api_basic_bio).json()
      basic_full_bio_list = json_data["data"]
      return basic_full_bio_list


      def lastnames():
      list_lastnames =
      players_range = range(len(basic_bio()))
      json_bios = basic_bio()

      for number in players_range:
      list_lastnames.append((json_bios[number]["playerLastName"]).lower())

      return list_lastnames


      def duplicate_last_names():
      last_names_count = collections.Counter(lastnames())
      dup_last_names = {k: v for k, v in last_names_count.items() if v > 1}
      return dup_last_names


      def get_id_name_dict():
      players_id_dict = {}
      players_range = range(len(basic_bio()))
      dups = duplicate_last_names()
      skaters = basic_bio()

      for number in players_range:
      if (skaters[number]["playerLastName"]).lower() in dups:
      players_id_dict[skaters[number]["playerName"].lower()] = skaters[number]["playerId"]
      else:
      players_id_dict[skaters[number]["playerLastName"].lower()] = skaters[number]["playerId"]

      with open('nhl_skaters_id.json', 'w') as file:
      file.write(json.dumps(players_id_dict, indent=4, sort_keys=True))


      def get_id(player_lastname):
      with open('nhl_skaters_id.json') as file:
      data = json.load(file)
      dups = duplicate_last_names()

      if player_lastname in dups:
      player_firstname = input("Enter a player's firstname: ").lower()
      return data[player_firstname + ' ' + player_lastname]
      else:
      return data[player_lastname]


      def get_stats(id):
      api_base = 'https://statsapi.web.nhl.com/api/v1/people/'
      api_end = '?hydrate=stats(splits=statsSingleSeason)'
      url = ''.join([api_base, str(id), api_end])
      json_data = requests.get(url).json()
      stats = json_data["people"]
      return stats


      def show_stats(data):
      path_stats = data[0]['stats'][0]['splits'][0]['stat']
      path_bio = data[0]

      printed_info = (f"Name: {path_bio['fullName']}nn" +
      f"Birth Date: {path_bio['birthDate']}n" +
      f"Height: {path_bio['height']}n" +
      f"Weight: {path_bio['weight']}nn" +
      f"Games: {path_stats['games']}n" +
      f"Goals: {path_stats['goals']}n" +
      f"Assists: {path_stats['assists']}n" +
      f"Points: {path_stats['points']}n" +
      f"PP Points: {path_stats['powerPlayPoints']}n" +
      f"Plus-minus: {path_stats['plusMinus']}n" +
      f"Shots: {path_stats['shots']}n" +
      f"Hits: {path_stats['hits']}n" +
      f"Blocks: {path_stats['blocked']}n" +
      f"Penalty Minutes: {path_stats['pim']}n" +
      f"TOI per Game: {path_stats['timeOnIcePerGame']}n" +
      f"PP TOI per Game: {path_stats['powerPlayTimeOnIcePerGame']}n" +
      f"SH TOI per Game: {path_stats['shortHandedTimeOnIcePerGame']}n")

      print(printed_info)


      def data_old(file_name):
      one_day_ago = datetime.datetime.now() - datetime.timedelta(days=1)
      filetime = datetime.datetime.fromtimestamp(os.path.getmtime(file_name))

      if filetime < one_day_ago:
      return True
      else:
      return False


      player_lastname = input("Enter a player's lastname: ").lower()

      if os.path.isfile('nhl_skaters_id.json') == False or data_old('nhl_skaters_id.json') == True:
      get_id_name_dict()

      try:
      data = get_stats(get_id(player_lastname))
      print('-----------------------------')
      show_stats(data)
      except Exception:
      print(f"{player_lastname.capitalize()} did't play any NHL games this season")






      python beginner python-3.x api






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited yesterday









      Graipher

      22.8k53384




      22.8k53384










      asked yesterday









      Flynn84

      745




      745






















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          3
          down vote















          • One way to deal with long URLs that are long because of lots of parameters is to let requests deal with those parameters by passing a dictionary:



            URL = "http://www.nhl.com/stats/rest/{}"

            def basic_bio():
            params = {"isAggregate": "false",
            "reportType": "basic",
            "isGame": "false",
            "reportName": "skaterpercentages",
            "cayenneExp": "gameTypeId=2 and seasonId=20182019"}
            return requests.get(URL.format("skaters"), params=params).json()["data"]


            It even performs the urlencoding for you (by escaping the spaces in the last parameter, in this case).




          • Don't Repeat Yourself (DRY). In addition, always try to iterate over the elements of an iterable, instead of indices. This allows you to use iterable but not indexable things (like generators). For lastnames, you can use a list comprehension, though:



            def lastnames():
            return [player["playerLastName"].lower() for player in basic_bio()]



          • Python has multiline strings:



            PLAYER_STATS = """Name: {fullName}

            Birth Date: {birthDate}
            Height: {height}
            ...
            SH TOI per Game: {shortHandedTimeOnIcePerGame}

            """

            def show_stats(data):
            path_stats = data[0]['stats'][0]['splits'][0]['stat']
            path_bio = data[0]
            print(PLAYER_STATS.format(**path_bio, **path_stats))


            This uses the fact that in Python 3 you can keyword expand multiple mappings. It is OK if not all keys of the dictionary/ies are used.




          • Instead of comparing something directly with False or True, like in



            if os.path.isfile('nhl_skaters_id.json') == False or data_old('nhl_skaters_id.json') == True:
            # get info


            just do



            skaters_json = 'nhl_skaters_id.json'
            if not os.path.isfile(skaters_json) or data_old(skaters_json):
            # get info



          • Don't explicitly return True or False unless you really have to. Instead of



            if filetime < one_day_ago:
            return True
            else:
            return False


            Just do



            return filetime < one_day_ago


          • You should wrap your calling code in a if __name__ == "__main__": guard to allow importing from this script without executing those functions.

          • When catching exceptions, try to be as specific as possible. except Exception is already better than a bare except (because at least it does not prevent the user from exiting using Ctrl+C), but if you know the specific exception (or exceptions) you want to guard against, then use that knowledge. Here it is probably a IndexError or KeyError?






          share|improve this answer























          • Thanks a lot for your review! Now going through it. Item by item. About the first one. That way with dictionary looking good. But why is there 'data=params', shouldn't it just be 'params'?
            – Flynn84
            22 hours ago










          • And about the return line in the method. Is it a pythonic way to chain three or more functions in one line? I've used to it when I've been learning Ruby, but it seemed to me that in Python it's not always the case. In a lot of code examples, I've seen it was done in a way that there is one function in one line. The same applies for 'Don't explicitly return True or False', I've never done this in Ruby. Started to do this in Python. Maybe I've taken 'Explicit is better than implicit' way too seriously :)
            – Flynn84
            21 hours ago






          • 1




            @Flynn84 Chaining calls like this is indeed a balancing act between simplicity (no need for intermediate variables which are only used once and for which you need to find sensible names) and readability (Chaining too many calls will obfuscate what the object represents). In general I tend to chain them if the intermediate objects are used nowhere, the length of the line stays within 80 chars and the chain is relatively easy to follow (like here).
            – Graipher
            21 hours ago






          • 1




            @Flynn84 And the part about explicitly returning True, I agree here the common practice seems to disagree with the Python Zen. However, remember that there is also "Simple is better than complex." and "Flat is better than nested." In Python duck-typing is often used ("If it quacks like a duck...it's probably a duck"). This means that many methods return only truthy or falsy objects, instead of True and False and that is fine.
            – Graipher
            21 hours ago






          • 1




            Yeah, I could've read docs more thoroughly, just read about the difference between params and data.
            – Flynn84
            18 hours ago











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209002%2fcommand-line-nhl-skaters-stats-tool%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          3
          down vote















          • One way to deal with long URLs that are long because of lots of parameters is to let requests deal with those parameters by passing a dictionary:



            URL = "http://www.nhl.com/stats/rest/{}"

            def basic_bio():
            params = {"isAggregate": "false",
            "reportType": "basic",
            "isGame": "false",
            "reportName": "skaterpercentages",
            "cayenneExp": "gameTypeId=2 and seasonId=20182019"}
            return requests.get(URL.format("skaters"), params=params).json()["data"]


            It even performs the urlencoding for you (by escaping the spaces in the last parameter, in this case).




          • Don't Repeat Yourself (DRY). In addition, always try to iterate over the elements of an iterable, instead of indices. This allows you to use iterable but not indexable things (like generators). For lastnames, you can use a list comprehension, though:



            def lastnames():
            return [player["playerLastName"].lower() for player in basic_bio()]



          • Python has multiline strings:



            PLAYER_STATS = """Name: {fullName}

            Birth Date: {birthDate}
            Height: {height}
            ...
            SH TOI per Game: {shortHandedTimeOnIcePerGame}

            """

            def show_stats(data):
            path_stats = data[0]['stats'][0]['splits'][0]['stat']
            path_bio = data[0]
            print(PLAYER_STATS.format(**path_bio, **path_stats))


            This uses the fact that in Python 3 you can keyword expand multiple mappings. It is OK if not all keys of the dictionary/ies are used.




          • Instead of comparing something directly with False or True, like in



            if os.path.isfile('nhl_skaters_id.json') == False or data_old('nhl_skaters_id.json') == True:
            # get info


            just do



            skaters_json = 'nhl_skaters_id.json'
            if not os.path.isfile(skaters_json) or data_old(skaters_json):
            # get info



          • Don't explicitly return True or False unless you really have to. Instead of



            if filetime < one_day_ago:
            return True
            else:
            return False


            Just do



            return filetime < one_day_ago


          • You should wrap your calling code in a if __name__ == "__main__": guard to allow importing from this script without executing those functions.

          • When catching exceptions, try to be as specific as possible. except Exception is already better than a bare except (because at least it does not prevent the user from exiting using Ctrl+C), but if you know the specific exception (or exceptions) you want to guard against, then use that knowledge. Here it is probably a IndexError or KeyError?






          share|improve this answer























          • Thanks a lot for your review! Now going through it. Item by item. About the first one. That way with dictionary looking good. But why is there 'data=params', shouldn't it just be 'params'?
            – Flynn84
            22 hours ago










          • And about the return line in the method. Is it a pythonic way to chain three or more functions in one line? I've used to it when I've been learning Ruby, but it seemed to me that in Python it's not always the case. In a lot of code examples, I've seen it was done in a way that there is one function in one line. The same applies for 'Don't explicitly return True or False', I've never done this in Ruby. Started to do this in Python. Maybe I've taken 'Explicit is better than implicit' way too seriously :)
            – Flynn84
            21 hours ago






          • 1




            @Flynn84 Chaining calls like this is indeed a balancing act between simplicity (no need for intermediate variables which are only used once and for which you need to find sensible names) and readability (Chaining too many calls will obfuscate what the object represents). In general I tend to chain them if the intermediate objects are used nowhere, the length of the line stays within 80 chars and the chain is relatively easy to follow (like here).
            – Graipher
            21 hours ago






          • 1




            @Flynn84 And the part about explicitly returning True, I agree here the common practice seems to disagree with the Python Zen. However, remember that there is also "Simple is better than complex." and "Flat is better than nested." In Python duck-typing is often used ("If it quacks like a duck...it's probably a duck"). This means that many methods return only truthy or falsy objects, instead of True and False and that is fine.
            – Graipher
            21 hours ago






          • 1




            Yeah, I could've read docs more thoroughly, just read about the difference between params and data.
            – Flynn84
            18 hours ago















          up vote
          3
          down vote















          • One way to deal with long URLs that are long because of lots of parameters is to let requests deal with those parameters by passing a dictionary:



            URL = "http://www.nhl.com/stats/rest/{}"

            def basic_bio():
            params = {"isAggregate": "false",
            "reportType": "basic",
            "isGame": "false",
            "reportName": "skaterpercentages",
            "cayenneExp": "gameTypeId=2 and seasonId=20182019"}
            return requests.get(URL.format("skaters"), params=params).json()["data"]


            It even performs the urlencoding for you (by escaping the spaces in the last parameter, in this case).




          • Don't Repeat Yourself (DRY). In addition, always try to iterate over the elements of an iterable, instead of indices. This allows you to use iterable but not indexable things (like generators). For lastnames, you can use a list comprehension, though:



            def lastnames():
            return [player["playerLastName"].lower() for player in basic_bio()]



          • Python has multiline strings:



            PLAYER_STATS = """Name: {fullName}

            Birth Date: {birthDate}
            Height: {height}
            ...
            SH TOI per Game: {shortHandedTimeOnIcePerGame}

            """

            def show_stats(data):
            path_stats = data[0]['stats'][0]['splits'][0]['stat']
            path_bio = data[0]
            print(PLAYER_STATS.format(**path_bio, **path_stats))


            This uses the fact that in Python 3 you can keyword expand multiple mappings. It is OK if not all keys of the dictionary/ies are used.




          • Instead of comparing something directly with False or True, like in



            if os.path.isfile('nhl_skaters_id.json') == False or data_old('nhl_skaters_id.json') == True:
            # get info


            just do



            skaters_json = 'nhl_skaters_id.json'
            if not os.path.isfile(skaters_json) or data_old(skaters_json):
            # get info



          • Don't explicitly return True or False unless you really have to. Instead of



            if filetime < one_day_ago:
            return True
            else:
            return False


            Just do



            return filetime < one_day_ago


          • You should wrap your calling code in a if __name__ == "__main__": guard to allow importing from this script without executing those functions.

          • When catching exceptions, try to be as specific as possible. except Exception is already better than a bare except (because at least it does not prevent the user from exiting using Ctrl+C), but if you know the specific exception (or exceptions) you want to guard against, then use that knowledge. Here it is probably a IndexError or KeyError?






          share|improve this answer























          • Thanks a lot for your review! Now going through it. Item by item. About the first one. That way with dictionary looking good. But why is there 'data=params', shouldn't it just be 'params'?
            – Flynn84
            22 hours ago










          • And about the return line in the method. Is it a pythonic way to chain three or more functions in one line? I've used to it when I've been learning Ruby, but it seemed to me that in Python it's not always the case. In a lot of code examples, I've seen it was done in a way that there is one function in one line. The same applies for 'Don't explicitly return True or False', I've never done this in Ruby. Started to do this in Python. Maybe I've taken 'Explicit is better than implicit' way too seriously :)
            – Flynn84
            21 hours ago






          • 1




            @Flynn84 Chaining calls like this is indeed a balancing act between simplicity (no need for intermediate variables which are only used once and for which you need to find sensible names) and readability (Chaining too many calls will obfuscate what the object represents). In general I tend to chain them if the intermediate objects are used nowhere, the length of the line stays within 80 chars and the chain is relatively easy to follow (like here).
            – Graipher
            21 hours ago






          • 1




            @Flynn84 And the part about explicitly returning True, I agree here the common practice seems to disagree with the Python Zen. However, remember that there is also "Simple is better than complex." and "Flat is better than nested." In Python duck-typing is often used ("If it quacks like a duck...it's probably a duck"). This means that many methods return only truthy or falsy objects, instead of True and False and that is fine.
            – Graipher
            21 hours ago






          • 1




            Yeah, I could've read docs more thoroughly, just read about the difference between params and data.
            – Flynn84
            18 hours ago













          up vote
          3
          down vote










          up vote
          3
          down vote











          • One way to deal with long URLs that are long because of lots of parameters is to let requests deal with those parameters by passing a dictionary:



            URL = "http://www.nhl.com/stats/rest/{}"

            def basic_bio():
            params = {"isAggregate": "false",
            "reportType": "basic",
            "isGame": "false",
            "reportName": "skaterpercentages",
            "cayenneExp": "gameTypeId=2 and seasonId=20182019"}
            return requests.get(URL.format("skaters"), params=params).json()["data"]


            It even performs the urlencoding for you (by escaping the spaces in the last parameter, in this case).




          • Don't Repeat Yourself (DRY). In addition, always try to iterate over the elements of an iterable, instead of indices. This allows you to use iterable but not indexable things (like generators). For lastnames, you can use a list comprehension, though:



            def lastnames():
            return [player["playerLastName"].lower() for player in basic_bio()]



          • Python has multiline strings:



            PLAYER_STATS = """Name: {fullName}

            Birth Date: {birthDate}
            Height: {height}
            ...
            SH TOI per Game: {shortHandedTimeOnIcePerGame}

            """

            def show_stats(data):
            path_stats = data[0]['stats'][0]['splits'][0]['stat']
            path_bio = data[0]
            print(PLAYER_STATS.format(**path_bio, **path_stats))


            This uses the fact that in Python 3 you can keyword expand multiple mappings. It is OK if not all keys of the dictionary/ies are used.




          • Instead of comparing something directly with False or True, like in



            if os.path.isfile('nhl_skaters_id.json') == False or data_old('nhl_skaters_id.json') == True:
            # get info


            just do



            skaters_json = 'nhl_skaters_id.json'
            if not os.path.isfile(skaters_json) or data_old(skaters_json):
            # get info



          • Don't explicitly return True or False unless you really have to. Instead of



            if filetime < one_day_ago:
            return True
            else:
            return False


            Just do



            return filetime < one_day_ago


          • You should wrap your calling code in a if __name__ == "__main__": guard to allow importing from this script without executing those functions.

          • When catching exceptions, try to be as specific as possible. except Exception is already better than a bare except (because at least it does not prevent the user from exiting using Ctrl+C), but if you know the specific exception (or exceptions) you want to guard against, then use that knowledge. Here it is probably a IndexError or KeyError?






          share|improve this answer
















          • One way to deal with long URLs that are long because of lots of parameters is to let requests deal with those parameters by passing a dictionary:



            URL = "http://www.nhl.com/stats/rest/{}"

            def basic_bio():
            params = {"isAggregate": "false",
            "reportType": "basic",
            "isGame": "false",
            "reportName": "skaterpercentages",
            "cayenneExp": "gameTypeId=2 and seasonId=20182019"}
            return requests.get(URL.format("skaters"), params=params).json()["data"]


            It even performs the urlencoding for you (by escaping the spaces in the last parameter, in this case).




          • Don't Repeat Yourself (DRY). In addition, always try to iterate over the elements of an iterable, instead of indices. This allows you to use iterable but not indexable things (like generators). For lastnames, you can use a list comprehension, though:



            def lastnames():
            return [player["playerLastName"].lower() for player in basic_bio()]



          • Python has multiline strings:



            PLAYER_STATS = """Name: {fullName}

            Birth Date: {birthDate}
            Height: {height}
            ...
            SH TOI per Game: {shortHandedTimeOnIcePerGame}

            """

            def show_stats(data):
            path_stats = data[0]['stats'][0]['splits'][0]['stat']
            path_bio = data[0]
            print(PLAYER_STATS.format(**path_bio, **path_stats))


            This uses the fact that in Python 3 you can keyword expand multiple mappings. It is OK if not all keys of the dictionary/ies are used.




          • Instead of comparing something directly with False or True, like in



            if os.path.isfile('nhl_skaters_id.json') == False or data_old('nhl_skaters_id.json') == True:
            # get info


            just do



            skaters_json = 'nhl_skaters_id.json'
            if not os.path.isfile(skaters_json) or data_old(skaters_json):
            # get info



          • Don't explicitly return True or False unless you really have to. Instead of



            if filetime < one_day_ago:
            return True
            else:
            return False


            Just do



            return filetime < one_day_ago


          • You should wrap your calling code in a if __name__ == "__main__": guard to allow importing from this script without executing those functions.

          • When catching exceptions, try to be as specific as possible. except Exception is already better than a bare except (because at least it does not prevent the user from exiting using Ctrl+C), but if you know the specific exception (or exceptions) you want to guard against, then use that knowledge. Here it is probably a IndexError or KeyError?







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited 18 hours ago

























          answered yesterday









          Graipher

          22.8k53384




          22.8k53384












          • Thanks a lot for your review! Now going through it. Item by item. About the first one. That way with dictionary looking good. But why is there 'data=params', shouldn't it just be 'params'?
            – Flynn84
            22 hours ago










          • And about the return line in the method. Is it a pythonic way to chain three or more functions in one line? I've used to it when I've been learning Ruby, but it seemed to me that in Python it's not always the case. In a lot of code examples, I've seen it was done in a way that there is one function in one line. The same applies for 'Don't explicitly return True or False', I've never done this in Ruby. Started to do this in Python. Maybe I've taken 'Explicit is better than implicit' way too seriously :)
            – Flynn84
            21 hours ago






          • 1




            @Flynn84 Chaining calls like this is indeed a balancing act between simplicity (no need for intermediate variables which are only used once and for which you need to find sensible names) and readability (Chaining too many calls will obfuscate what the object represents). In general I tend to chain them if the intermediate objects are used nowhere, the length of the line stays within 80 chars and the chain is relatively easy to follow (like here).
            – Graipher
            21 hours ago






          • 1




            @Flynn84 And the part about explicitly returning True, I agree here the common practice seems to disagree with the Python Zen. However, remember that there is also "Simple is better than complex." and "Flat is better than nested." In Python duck-typing is often used ("If it quacks like a duck...it's probably a duck"). This means that many methods return only truthy or falsy objects, instead of True and False and that is fine.
            – Graipher
            21 hours ago






          • 1




            Yeah, I could've read docs more thoroughly, just read about the difference between params and data.
            – Flynn84
            18 hours ago


















          • Thanks a lot for your review! Now going through it. Item by item. About the first one. That way with dictionary looking good. But why is there 'data=params', shouldn't it just be 'params'?
            – Flynn84
            22 hours ago










          • And about the return line in the method. Is it a pythonic way to chain three or more functions in one line? I've used to it when I've been learning Ruby, but it seemed to me that in Python it's not always the case. In a lot of code examples, I've seen it was done in a way that there is one function in one line. The same applies for 'Don't explicitly return True or False', I've never done this in Ruby. Started to do this in Python. Maybe I've taken 'Explicit is better than implicit' way too seriously :)
            – Flynn84
            21 hours ago






          • 1




            @Flynn84 Chaining calls like this is indeed a balancing act between simplicity (no need for intermediate variables which are only used once and for which you need to find sensible names) and readability (Chaining too many calls will obfuscate what the object represents). In general I tend to chain them if the intermediate objects are used nowhere, the length of the line stays within 80 chars and the chain is relatively easy to follow (like here).
            – Graipher
            21 hours ago






          • 1




            @Flynn84 And the part about explicitly returning True, I agree here the common practice seems to disagree with the Python Zen. However, remember that there is also "Simple is better than complex." and "Flat is better than nested." In Python duck-typing is often used ("If it quacks like a duck...it's probably a duck"). This means that many methods return only truthy or falsy objects, instead of True and False and that is fine.
            – Graipher
            21 hours ago






          • 1




            Yeah, I could've read docs more thoroughly, just read about the difference between params and data.
            – Flynn84
            18 hours ago
















          Thanks a lot for your review! Now going through it. Item by item. About the first one. That way with dictionary looking good. But why is there 'data=params', shouldn't it just be 'params'?
          – Flynn84
          22 hours ago




          Thanks a lot for your review! Now going through it. Item by item. About the first one. That way with dictionary looking good. But why is there 'data=params', shouldn't it just be 'params'?
          – Flynn84
          22 hours ago












          And about the return line in the method. Is it a pythonic way to chain three or more functions in one line? I've used to it when I've been learning Ruby, but it seemed to me that in Python it's not always the case. In a lot of code examples, I've seen it was done in a way that there is one function in one line. The same applies for 'Don't explicitly return True or False', I've never done this in Ruby. Started to do this in Python. Maybe I've taken 'Explicit is better than implicit' way too seriously :)
          – Flynn84
          21 hours ago




          And about the return line in the method. Is it a pythonic way to chain three or more functions in one line? I've used to it when I've been learning Ruby, but it seemed to me that in Python it's not always the case. In a lot of code examples, I've seen it was done in a way that there is one function in one line. The same applies for 'Don't explicitly return True or False', I've never done this in Ruby. Started to do this in Python. Maybe I've taken 'Explicit is better than implicit' way too seriously :)
          – Flynn84
          21 hours ago




          1




          1




          @Flynn84 Chaining calls like this is indeed a balancing act between simplicity (no need for intermediate variables which are only used once and for which you need to find sensible names) and readability (Chaining too many calls will obfuscate what the object represents). In general I tend to chain them if the intermediate objects are used nowhere, the length of the line stays within 80 chars and the chain is relatively easy to follow (like here).
          – Graipher
          21 hours ago




          @Flynn84 Chaining calls like this is indeed a balancing act between simplicity (no need for intermediate variables which are only used once and for which you need to find sensible names) and readability (Chaining too many calls will obfuscate what the object represents). In general I tend to chain them if the intermediate objects are used nowhere, the length of the line stays within 80 chars and the chain is relatively easy to follow (like here).
          – Graipher
          21 hours ago




          1




          1




          @Flynn84 And the part about explicitly returning True, I agree here the common practice seems to disagree with the Python Zen. However, remember that there is also "Simple is better than complex." and "Flat is better than nested." In Python duck-typing is often used ("If it quacks like a duck...it's probably a duck"). This means that many methods return only truthy or falsy objects, instead of True and False and that is fine.
          – Graipher
          21 hours ago




          @Flynn84 And the part about explicitly returning True, I agree here the common practice seems to disagree with the Python Zen. However, remember that there is also "Simple is better than complex." and "Flat is better than nested." In Python duck-typing is often used ("If it quacks like a duck...it's probably a duck"). This means that many methods return only truthy or falsy objects, instead of True and False and that is fine.
          – Graipher
          21 hours ago




          1




          1




          Yeah, I could've read docs more thoroughly, just read about the difference between params and data.
          – Flynn84
          18 hours ago




          Yeah, I could've read docs more thoroughly, just read about the difference between params and data.
          – Flynn84
          18 hours ago


















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209002%2fcommand-line-nhl-skaters-stats-tool%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Quarter-circle Tiles

          build a pushdown automaton that recognizes the reverse language of a given pushdown automaton?

          Mont Emei