From 6bd2b5e6121f3066bb2fe09baa8e71c723aced23 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 29 Jan 2022 22:52:00 -0600 Subject: [PATCH] initial commit --- .gitignore | 3 + HISTORY.rst | 10 ++ KonoPyUtil/__init__.py | 40 ++++++ KonoPyUtil/__version__.py | 8 ++ KonoPyUtil/credentials.py | 32 +++++ KonoPyUtil/data/copa_rayados.2021.csv | 60 +++++++++ KonoPyUtil/datasets.py | 25 ++++ KonoPyUtil/dbutils.py | 86 +++++++++++++ KonoPyUtil/exceptions.py | 19 +++ KonoPyUtil/soccer/__init__.py | 1 + KonoPyUtil/soccer/elo.py | 130 +++++++++++++++++++ LICENSE | 175 ++++++++++++++++++++++++++ NOTICE | 2 + README.rst | 27 ++++ setup.py | 43 +++++++ 15 files changed, 661 insertions(+) create mode 100644 .gitignore create mode 100644 HISTORY.rst create mode 100644 KonoPyUtil/__init__.py create mode 100644 KonoPyUtil/__version__.py create mode 100644 KonoPyUtil/credentials.py create mode 100644 KonoPyUtil/data/copa_rayados.2021.csv create mode 100644 KonoPyUtil/datasets.py create mode 100644 KonoPyUtil/dbutils.py create mode 100644 KonoPyUtil/exceptions.py create mode 100644 KonoPyUtil/soccer/__init__.py create mode 100644 KonoPyUtil/soccer/elo.py create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 README.rst create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f4bdd5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +*.env +__pycache__ \ No newline at end of file diff --git a/HISTORY.rst b/HISTORY.rst new file mode 100644 index 0000000..8817a73 --- /dev/null +++ b/HISTORY.rst @@ -0,0 +1,10 @@ +Release History +=============== + +0.0.1 (2022-01-29) +------------------ + +- Soccer: get_elo_season, get_elo_match +- Database: data_query, command_query, write_dataframe, get_engine +- Credentials: set_credentials +- Datasets: list_datasets, load_dataset. copa_rayados.2021.csv dataset diff --git a/KonoPyUtil/__init__.py b/KonoPyUtil/__init__.py new file mode 100644 index 0000000..c77ebfe --- /dev/null +++ b/KonoPyUtil/__init__.py @@ -0,0 +1,40 @@ +""" +KonoPyUtil Library +~~~~~~~~~~~~~~~~~~ +KonoPyUtil is a library of convenience fuctions, written in Python, for Kono Analytics and anyone else who wants to use it. + +Basic usage: + >>> import KonoPyUtil as kpu + >>> credentials = kpu.set_credentials('.env') + >>> query = "SELECT TOP 10 * FROM [mytable];" + >>> df = kpu.data_query(query) + +:license: Apache 2.0, see LICENSE for more details. +""" + + +from .__version__ import ( + __title__, + __description__, + __url__, + __version__, + __author__, + __author_email__, + __license__, + __copyright__, +) + +from .dbutils import ( + data_query, + command_query, + write_dataframe, + get_engine, +) +from .credentials import ( + set_credentials, +) +from .datasets import list_datasets, load_dataset +from .soccer import get_elo_season, get_elo_match +import logging + +logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/KonoPyUtil/__version__.py b/KonoPyUtil/__version__.py new file mode 100644 index 0000000..b254490 --- /dev/null +++ b/KonoPyUtil/__version__.py @@ -0,0 +1,8 @@ +__title__ = "KonoPyUtil" +__description__ = "Python Utilities for Kono Analytics" +__url__ = "https://konoanalytics.com" +__version__ = "0.0.1" +__author__ = "Jonathan Bennett" +__author_email__ = "info@konoanalytics.com" +__license__ = "Apache 2.0" +__copyright__ = "Copyright 2022 Jonathan Bennett" diff --git a/KonoPyUtil/credentials.py b/KonoPyUtil/credentials.py new file mode 100644 index 0000000..8eb6e2a --- /dev/null +++ b/KonoPyUtil/credentials.py @@ -0,0 +1,32 @@ +import os +import json +from environs import Env + + +def set_credentials(file_and_path_to_credential_file=".env", recurse=False): + """ + Loads credential file into O/S environment if a file_and_path_to_credential_file is not None + overwrites DB_CREDENTIALS O/S environment variable + returns DB_CREDENTIALS as a dictionary + + credential_file should be formatted as follows: + DB_CREDENTIALS='{"DBNAME": "postgres", "SCHEMA": "public", "USERID": "useridhere", "PASSWORD": "passwordhere", "HOST": "path_to_database_here", "PORT": "port_here"}' + + :param file_and_path_to_dot_env: defaults to .env, uf yi + :param recurse: Boolean = if True recursively search upward for a .env file + :return: dictionary of database credentials if present, or empty dictionary otherwise + """ + if file_and_path_to_credential_file: + # clear existing "DB_CREDENTIALS" environment variable + try: + del os.environ["DB_CREDENTIALS"] + except KeyError: + pass + finally: + env = Env() + file_and_path_formatted = os.path.abspath(file_and_path_to_credential_file) + env.read_env(file_and_path_formatted, recurse=recurse) + if "DB_CREDENTIALS" in os.environ: + return dict(json.loads(os.environ["DB_CREDENTIALS"])) + else: + return {} diff --git a/KonoPyUtil/data/copa_rayados.2021.csv b/KonoPyUtil/data/copa_rayados.2021.csv new file mode 100644 index 0000000..329d74e --- /dev/null +++ b/KonoPyUtil/data/copa_rayados.2021.csv @@ -0,0 +1,60 @@ +match,game_time,home_name,away_name,home_score,away_score,game_description +527,11/24/2021 11:00,TEXAS TIGRES 09',CHELSEA EAST 09,2,2, +561,11/24/2021 11:00,EP LOCOMOTIVE FC EP LOCOMOT,HOUSTONIANS FC 09B,0,0, +562,11/24/2021 11:00,AZTECAS FUTBOL AZTECAS FC 09B Blue,MSK RAYADOS 09B,1,0, +536,11/24/2021 11:30,BARÇA ACADEMY NASHVILLE BAR,SPORTS GROUP 1 SG1 09B,0,0, +537,11/24/2021 11:30,PRIMOS FC,AVANTI SOCCER ACADEMY 09B,0,1, +546,11/24/2021 12:15,RAPIDS SOUTH 09B ELITE,ALBION HURRICANES AHFC 09B,1,2, +547,11/24/2021 12:15,AVANTI SOCCER ACADEMY 09B WEST,ASPIRE FC 09 YELLOW,4,0, +526,11/24/2021 13:30,CADENCE SFC 2009 ACADEMY,RISE SC RISE U13B ECNL,0,1, +559,11/24/2021 14:45,AZTECAS FUTBOL AZTECAS FC 09B Blue,HOUSTONIANS FC 09B,0,2, +560,11/24/2021 15:15,IMPERIAL SC 09B,ID CHELSEA JR 09B Q,2,3, +525,11/24/2021 16:00,CHELSEA EAST 09,ID REAL HOUSTON 09B Q,0,2, +544,11/24/2021 16:00,AVANTI SOCCER ACADEMY 09B WEST,ALBION HURRICANES AHFC 09B,2,0, +535,11/24/2021 16:30,AVANTI SOCCER ACADEMY 09B,DYNAMO WOODLANDS 09 PA,1,1, +545,11/24/2021 17:15,SOCCER CENTRO 09,DDYSC HOUSTON DYNAMO YOUTH ECNL RL 09,1,1, +528,11/25/2021 08:15,CADENCE SFC 2009 ACADEMY,CHELSEA EAST 09,0,1, +530,11/25/2021 08:15,RISE SC RISE U13B ECNL,ID REAL HOUSTON 09B Q,1,4, +541,11/25/2021 08:15,BARÇA ACADEMY NASHVILLE BAR,PRIMOS FC,0,1, +563,11/25/2021 08:15,EP LOCOMOTIVE FC EP LOCOMOT,ID CHELSEA JR 09B Q,0,2, +564,11/25/2021 09:00,AZTECAS FUTBOL AZTECAS FC 09B Blue,IMPERIAL SC 09B,1,1, +551,11/25/2021 11:30,SOCCER CENTRO 09,RAPIDS SOUTH 09B ELITE,0,3, +566,11/25/2021 11:30,EP LOCOMOTIVE FC EP LOCOMOT,IMPERIAL SC 09B,3,0, +538,11/26/2021 08:00,BARÇA ACADEMY NASHVILLE BAR,AVANTI SOCCER ACADEMY 09B,1,1, +552,11/26/2021 08:00,AVANTI SOCCER ACADEMY 09B WEST,DDYSC HOUSTON DYNAMO YOUTH ECNL RL 09,1,0, +554,11/26/2021 08:00,SOCCER CENTRO 09,ALBION HURRICANES AHFC 09B,0,4, +540,11/26/2021 08:30,DYNAMO WOODLANDS 09 PA,SPORTS GROUP 1 SG1 09B,1,1, +572,11/26/2021 08:30,ID CHELSEA JR 09B Q,MSK RAYADOS 09B,0,1, +531,11/26/2021 09:00,CADENCE SFC 2009 ACADEMY,TEXAS TIGRES 09',1,5, +548,11/26/2021 09:00,RAPIDS SOUTH 09B ELITE,DDYSC HOUSTON DYNAMO YOUTH ECNL RL 09,1,0, +550,11/26/2021 09:00,ALBION HURRICANES AHFC 09B,ASPIRE FC 09 YELLOW,3,0, +568,11/26/2021 09:00,EP LOCOMOTIVE FC EP LOCOMOT,AZTECAS FUTBOL AZTECAS FC 09B Blue,6,0, +539,11/26/2021 09:45,PRIMOS FC,DYNAMO WOODLANDS 09 PA,1,0, +553,11/26/2021 11:00,SOCCER CENTRO 09,ASPIRE FC 09 YELLOW,3,0, +532,11/26/2021 11:10,CHELSEA EAST 09,RISE SC RISE U13B ECNL,0,0, +569,11/26/2021 12:15,IMPERIAL SC 09B,MSK RAYADOS 09B,5,0, +529,11/26/2021 12:20,TEXAS TIGRES 09',ID REAL HOUSTON 09B Q,0,4, +570,11/26/2021 12:30,ID CHELSEA JR 09B Q,HOUSTONIANS FC 09B,4,0, +542,11/26/2021 13:30,AVANTI SOCCER ACADEMY 09B,SPORTS GROUP 1 SG1 09B,0,1, +556,11/26/2021 13:45,RAPIDS SOUTH 09B ELITE,AVANTI SOCCER ACADEMY 09B WEST,0,0, +557,11/26/2021 15:00,DDYSC HOUSTON DYNAMO YOUTH ECNL RL 09,ASPIRE FC 09 YELLOW,2,1, +534,11/26/2021 17:15,PRIMOS FC,SPORTS GROUP 1 SG1 09B,0,1, +533,11/26/2021 17:45,BARÇA ACADEMY NASHVILLE BAR,DYNAMO WOODLANDS 09 PA,1,1, +523,11/26/2021 19:20,CADENCE SFC 2009 ACADEMY,ID REAL HOUSTON 09B Q,0,4, +524,11/26/2021 19:20,TEXAS TIGRES 09',RISE SC RISE U13B ECNL,0,0, +565,11/26/2021 21:45,MSK RAYADOS 09B,HOUSTONIANS FC 09B,0,1, +574,11/27/2021 08:15,SPORTS GROUP 1 SG1 09B,AVANTI SOCCER ACADEMY 09B WEST,0,4,gold semi +580,11/27/2021 08:15,AVANTI SOCCER ACADEMY 09B,RAPIDS SOUTH 09B ELITE,0,4,bronze semi +585,11/27/2021 08:30,CADENCE SFC 2009 ACADEMY,AZTECAS FUTBOL AZTECAS FC 09B Blue,1,5,quartz semi +586,11/27/2021 08:30,DYNAMO WOODLANDS 09 PA,SOCCER CENTRO 09,5,4,quartz semi +573,11/27/2021 10:00,ID REAL HOUSTON 09B Q,ID CHELSEA JR 09B Q,1,2,gold semi +579,11/27/2021 10:00,CHELSEA EAST 09,HOUSTONIANS FC 09B,1,3,bronze semi +582,11/27/2021 10:00,RISE SC RISE U13B ECNL,IMPERIAL SC 09B,1,2,copper semi +583,11/27/2021 10:00,BARÇA ACADEMY NASHVILLE BAR,DDYSC HOUSTON DYNAMO YOUTH ECNL RL 09,2,4,copper semi +576,11/27/2021 11:45,TEXAS TIGRES 09',EP LOCOMOTIVE FC EP LOCOMOT,0,2,silver semi +577,11/27/2021 12:00,PRIMOS FC,ALBION HURRICANES AHFC 09B,0,8,silver semi +581,11/28/2021 13:15,HOUSTONIANS FC 09B,RAPIDS SOUTH 09B ELITE,0,3,bronze final +575,11/28/2021 13:30,ID CHELSEA JR 09B Q,AVANTI SOCCER ACADEMY 09B WEST,0,1,gold final +587,11/28/2021 14:40,AZTECAS FUTBOL AZTECAS FC 09B Blue,DYNAMO WOODLANDS 09 PA,3,1,quartz final +578,11/28/2021 15:15,EP LOCOMOTIVE FC EP LOCOMOT,ALBION HURRICANES AHFC 09B,1,0,silver final +584,11/28/2021 15:15,IMPERIAL SC 09B,DDYSC HOUSTON DYNAMO YOUTH ECNL RL 09,2,0,copper final diff --git a/KonoPyUtil/datasets.py b/KonoPyUtil/datasets.py new file mode 100644 index 0000000..d447ff3 --- /dev/null +++ b/KonoPyUtil/datasets.py @@ -0,0 +1,25 @@ +import os +import pandas as pd + + +def list_datasets(): + """ + List all datasets in KonoPyUtil library + :return: a list of filenames corresponding to datasets + """ + _here = os.path.abspath(os.path.dirname(__file__)) + data_directory = os.path.join(_here, "data") + return os.listdir(data_directory) + + +def load_dataset(dataset): + """ + Loads a specific dataset in KonoPyUtil library + :param dataset: name of dataset (options can be found via list_datasets() + :return: a Pandas dataframe of the requested dataset + """ + _here = os.path.abspath(os.path.dirname(__file__)) + file_and_path = os.path.join(_here, "data", dataset) + if dataset in list_datasets(): + return pd.read_csv(file_and_path) + return pd.DataFrame() diff --git a/KonoPyUtil/dbutils.py b/KonoPyUtil/dbutils.py new file mode 100644 index 0000000..ac1108d --- /dev/null +++ b/KonoPyUtil/dbutils.py @@ -0,0 +1,86 @@ +import sqlalchemy as sa +import pandas as pd +from .credentials import set_credentials +from .exceptions import MissingCredentialsError + + +def data_query(query, engine=None, **kwargs): + """ + consumes a select query and returns a dataframe with the results + :param query: string of query + :param engine: sql alchemy engine + :param **kwargs: remaining parameters for pandas.read_sql() + :return: dataframe of results of query + """ + close_engine = False + if engine is None: + close_engine = True + engine = get_engine(**kwargs) + df = pd.read_sql(query, engine) + if close_engine: + engine.dispose() + return df + + +def command_query(query, engine=None, **kwargs): + """ + consumes a command query (update, drop, etc) + :param query: string of query + :param engine: sql alchemy engine + :return: returns result of query execution + """ + close_engine = False + if engine is None: + close_engine = True + engine = get_engine(**kwargs) + if close_engine: + engine.dispose() + return engine.execute(sa.text(query).execution_options(autocommit=True)) + + +def write_dataframe(df, tablename, engine, if_exists="append", index=False, **kwargs): + """ + Appends dataframe records to a database. (Creates table if it doesn't exist) + :param df: dataframe + :param tablename: table name + :param engine: engine + :param if_exists: {‘fail’, ‘replace’, ‘append’}, default ‘append’ + :param **kwargs: remaining parameters for pandas.to_sql() + :return: True if success, False otherwise + """ + + close_engine = False + if engine is None: + close_engine = True + engine = get_engine(**kwargs) + try: + df.to_sql(name=tablename, con=engine, if_exists=if_exists, index=index) + engine.dispose() + success = True + except Exception as ex: + template = "An exception of type {0} occurred. Arguments:\n{1!r}" + message = template.format(type(ex).__name__, ex.args) + print(message) + success = False + finally: + if close_engine: + engine.dispose() + return success + + +def get_engine(credentials=None, **kwargs): + """ + Returns a sqlalchemy engine given appropriate inputs + :param **kwargs: remaining parameters for sqlalchemy.create_engine() + """ + if not credentials: + os_credentials = set_credentials() + if not os_credentials: + raise (MissingCredentialsError()) + userid = os_credentials.get("USERID", "db_userid") + password = os_credentials.get("PASSWORD", "db_password") + host = os_credentials.get("HOST", "db_host") + port = int(os_credentials.get("PORT", "0")) + dbname = os_credentials.get("DBNAME", "db_name") + cstring = f"postgresql://{userid}:{password}@{host}:{port}/{dbname}" + return sa.create_engine(cstring, **kwargs) diff --git a/KonoPyUtil/exceptions.py b/KonoPyUtil/exceptions.py new file mode 100644 index 0000000..14bd97d --- /dev/null +++ b/KonoPyUtil/exceptions.py @@ -0,0 +1,19 @@ +class ParameterError(Exception): + def __init__(self, valid_parameters, bad_parameter): + self.valid_parameters = valid_parameters + self.bad_parameter = bad_parameter + + def __str__(self): + return repr(f"""Valid Option: {self.valid_parameters}. Option Provided: {self.bad_parameter}""") + + +class MissingCredentialsError(Exception): + def __init__( + self, + ): + pass + + def __str__(self): + return repr( + f"""Your credentials are missing. Try KonoPyUtil.set_credentials() to set an environment variable named DB_CREDENTIALS.""" + ) diff --git a/KonoPyUtil/soccer/__init__.py b/KonoPyUtil/soccer/__init__.py new file mode 100644 index 0000000..78158ab --- /dev/null +++ b/KonoPyUtil/soccer/__init__.py @@ -0,0 +1 @@ +from .elo import get_elo_season, get_elo_match diff --git a/KonoPyUtil/soccer/elo.py b/KonoPyUtil/soccer/elo.py new file mode 100644 index 0000000..e934d45 --- /dev/null +++ b/KonoPyUtil/soccer/elo.py @@ -0,0 +1,130 @@ +import math +import pandas as pd + +# Constructed From: +# https://www.geeksforgeeks.org/elo-rating-algorithm/ +# https://www.chess.com/forum/view/general/how-are-draws-calculated-in-the-elo-system + +# Function to calculate the _probability +def _probability(rating1, rating2): + return 1.0 * 1.0 / (1 + 1.0 * math.pow(10, 1.0 * (rating1 - rating2) / 400)) + + +def get_elo_match(rating_a, rating_b, k, d, mov=None): + """ + Returns new ELO ratings give the result of a match + :param rating_a: Rating for player A + :param rating_b: Rating for player B + :param k: Constant + :param d: 1 = Win for rating_a, 0 = Tie, -1 = Win for rating_b + :param mov: margin of victory. None, implies not to use margin of victory in the calculation + :return: + """ + probability_a = _probability(rating_b, rating_a) + probability_b = 1 - probability_a + if mov is None: + k_multiplier = 1.0 + else: + k_multiplier = math.log(abs(mov) + 1) + if d == 1: + # Player A won + rating_a = rating_a + k * k_multiplier * (1 - probability_a) + rating_b = rating_b + k * k_multiplier * (0 - probability_b) + elif d == -1: + # Player B won + rating_a = rating_a + k * k_multiplier * (0 - probability_a) + rating_b = rating_b + k * k_multiplier * (1 - probability_b) + else: + # Tie + rating_a = rating_a + k * k_multiplier * (0.5 - probability_a) + rating_b = rating_b + k * k_multiplier * (0.5 - probability_b) + return rating_a, rating_b + + +def _format_game_results(df): + df = df.set_index("match") + df["game_time"] = pd.to_datetime(df["game_time"]) + df = df.sort_values(by="game_time") + return df + + +def _get_current_elo(df, team_name): + + select_records = ((df["home_name"] == team_name) | (df["away_name"] == team_name)) & ( + pd.notnull(df["home_post_elo"]) + ) + last_record = df[select_records].tail(1) + if len(last_record): + if last_record["home_name"].values[0] == team_name: + return last_record["home_post_elo"].values[0] + else: + return last_record["away_post_elo"].values[0] + else: + return 1000 + + +def _format_ranking(df, verbose=False): + teams = list(set(df["home_name"]) | set(df["away_name"])) + final_rank = [] + team_name = [] + for team in teams: + final_rank.append(_get_current_elo(df, team)) + team_name.append(team) + final_elo_column_name = "Final ELO" + df_final = pd.DataFrame(data={"Team": team_name, final_elo_column_name: final_rank}).sort_values( + by="Final ELO", ascending=False + ) + df_final["rank"] = df_final[final_elo_column_name].rank(method="min", ascending=False).astype("int") + df_final = df_final.reset_index(drop=True) + if verbose: + print(df_final.to_string(index=False)) + return df_final + + +def get_elo_season(df, use_mov=True, K=30, verbose=False): + """ + This runs the ELO algorithm to rank all teams in a given season or tournament. It acceepts a dataframe of game + results. The format of this dataframe is very particular. For an example look at the copa_rayados.2021.csv file + in this library + >>> from KonoPyUtil import load_dataset + >>> df = load_dataset('copa_rayados.2021.csv') + + :param df: A dataframe of results + :param use_mov: Use margin-of-victory -- boolean + :param K: K factor for ELO + :param verbose: prints ranking if True + :return: a dictionary containing two dataframes, df_elo and df_ranking + """ + df_results = _format_game_results(df).copy() + df_elo = df_results.copy() + df_elo["home_pre_elo"] = 1000 + df_elo["home_post_elo"] = None + df_elo["away_pre_elo"] = 1000 + df_elo["away_post_elo"] = None + + for index, game in df_results.iterrows(): + if game["home_score"] > game["away_score"]: + d = 1 + if game["home_score"] < game["away_score"]: + d = -1 + if game["home_score"] == game["away_score"]: + d = 0 + + Ra_old = _get_current_elo(df_elo, game["home_name"]) + Rb_old = _get_current_elo(df_elo, game["away_name"]) + if use_mov: + score_delta = game["home_score"] - game["away_score"] + else: + score_delta = None + Ra_new, Rb_new = get_elo_match(Ra_old, Rb_old, K, d, score_delta) + df_elo.loc[index, "home_pre_elo"] = Ra_old + df_elo.loc[index, "home_post_elo"] = Ra_new + df_elo.loc[index, "away_pre_elo"] = Rb_old + df_elo.loc[index, "away_post_elo"] = Rb_new + + df_rankings = _format_ranking(df_elo, verbose=verbose) + + return { + "df_elo": df_elo, + "df_rankings": df_rankings, + } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..67db858 --- /dev/null +++ b/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..ee57dc3 --- /dev/null +++ b/NOTICE @@ -0,0 +1,2 @@ +KonoPyUtil +Copyright 2022 Jonathan Bennett \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..cb02d2b --- /dev/null +++ b/README.rst @@ -0,0 +1,27 @@ +.. image:: https://konoanalytics.com/static/website/images/Kono-Logo-White-Color-Transparent-Back.svg + :target: https://konoanalytics.com/ + + +Kono Analytics Python Utilities +=============================== +KonoPyUtil is a library of convenience fuctions, written in Python, for Kono Analytics and anyone else who wants to use it. + +You can install this utility library with pip from GitHub. + + $ pip install git+https://github.com/konoanalytics/KonoPyUtil.git + + +>>> import KonoPyUtil as kpu +>>> kpu.__version__ +>>> credentials = kpu.set_credentials('.env') +>>> query = "SELECT * FROM mytable LIMIT 10;" +>>> df = kpu.data_query(query) # return a dataframe of your query results + +=============== +Version History +=============== +======= ========== ======= ============= +Version Date Who Release Notes +======= ========== ======= ============= +0.0.1 2022-01-29 JB Initial Version +======= ========== ======= ============= diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c14fa71 --- /dev/null +++ b/setup.py @@ -0,0 +1,43 @@ +from setuptools import setup +import os +from codecs import open + + +_here = os.path.abspath(os.path.dirname(__file__)) + +with open(os.path.join(_here, "README.rst")) as f: + long_description = f.read() + +packages = ["KonoPyUtil", "KonoPyUtil/soccer"] + +install_requires = [ + "pandas>=1.4.0", + "SQLAlchemy>=1.4.31", + "environs>=9.4.0", + "psycopg2-binary>=2.9.3", +] + +about = {} +with open(os.path.join(_here, "KonoPyUtil", "__version__.py"), "r", "utf-8") as f: + exec(f.read(), about) + +setup( + name=about["__title__"], + version=about["__version__"], + description=about["__description__"], + long_description=long_description, + long_description_content_type="text/x-rst", + author=about["__author__"], + author_email=about["__author_email__"], + url=about["__url__"], + packages=packages, + package_dir={"KonoPyUtil": "KonoPyUtil"}, + package_data={"KonoPyUtil": ["data/*"]}, + include_package_data=True, + python_requires=">=3.0.*", + install_requires=install_requires, + license=about["__license__"], + project_urls={ + "Source": "https://github.com/konoanalytics/KonoPyUtil", + }, +)